Mercurial > bitten > bitten-test
changeset 436:cfbc9ee622d5
Finish the move of build configuration management into the admin interface.
author | cmlenz |
---|---|
date | Fri, 17 Aug 2007 10:43:09 +0000 |
parents | 8424a8afd1a1 |
children | 6d5ac24061dc |
files | bitten/admin.py bitten/build/ctools.py bitten/build/tests/config.py bitten/build/xmltools.py bitten/htdocs/admin.css bitten/master.py bitten/model.py bitten/slave.py bitten/templates/bitten_admin_configs.cs bitten/tests/admin.py bitten/tests/master.py bitten/tests/queue.py bitten/tests/slave.py bitten/tests/web_ui.py bitten/web_ui.py doc/install.txt |
diffstat | 16 files changed, 545 insertions(+), 880 deletions(-) [+] |
line wrap: on
line diff
--- a/bitten/admin.py +++ b/bitten/admin.py @@ -13,6 +13,7 @@ import re from trac.core import * +from trac.web.chrome import add_stylesheet try: require("TracWebAdmin") from webadmin.web_ui import IAdminPageProvider @@ -20,7 +21,7 @@ IAdminPageProvider = None from bitten.model import BuildConfig, TargetPlatform -from bitten.recipe import Recipe +from bitten.recipe import Recipe, InvalidRecipeError from bitten.util import xmlio @@ -48,6 +49,7 @@ 'adjust_timestamps': master.adjust_timestamps, 'slave_timeout': master.slave_timeout, } + add_stylesheet(req, 'bitten/admin.css') return 'bitten_admin_master.cs', None # Internal methods @@ -93,17 +95,29 @@ data = {} if config_name: - if '/' in config_name: - config_name, platform_id = config_name.split('/', 1) - platform_id = int(platform_id) + if '/' in config_name or ( + req.method == 'POST' and 'new' in req.args): + if '/' in config_name: + config_name, platform_id = config_name.split('/', 1) + platform_id = int(platform_id) + platform = TargetPlatform.fetch(self.env, platform_id) - platform = TargetPlatform.fetch(self.env, platform_id) + if req.method == 'POST': + if 'cancel' in req.args or \ + self._update_platform(req, platform): + req.redirect(req.abs_href.admin(cat, page, + config_name)) + else: + if req.method == 'POST': + if 'add' in req.args: + self._create_platform(req, config_name) + req.redirect(req.abs_href.admin(cat, page, + config_name)) + elif 'cancel' in req.args: + req.redirect(req.abs_href.admin(cat, page, + config_name)) - if req.method == 'POST': - if 'cancel' in req.args: - req.redirect(req.abs_href.admin(cat, page, config_name)) - elif self._process_platform(req, platform): - req.redirect(req.abs_href.admin(cat, page, config_name)) + platform = TargetPlatform(self.env, config=config_name) data['platform'] = { 'id': platform.id, 'name': platform.name, @@ -120,7 +134,13 @@ config=config.name)) if req.method == 'POST': - if 'save' in req.args: + if 'add' in req.args: # Add target platform + platform = self._create_platform(req, config) + req.redirect(req.abs_href.admin(cat, page, config.name)) + elif 'remove' in req.args: # Remove selected platforms + self._remove_platforms(req) + req.redirect(req.abs_href.admin(cat, page, config.name)) + elif 'save' in req.args: self._update_config(req, config) req.redirect(req.abs_href.admin(cat, page)) @@ -134,7 +154,9 @@ 'name': platform.name, 'id': platform.id, 'href': req.href.admin('bitten', 'configs', config.name, - platform.id) + platform.id), + 'rules': [{'property': propname, 'pattern': pattern} + for propname, pattern in platform.rules] } for platform in platforms] } @@ -160,6 +182,7 @@ data['configs'] = configs req.hdf['admin'] = data + add_stylesheet(req, 'bitten/admin.css') return 'bitten_admin_configs.cs', None # Internal methods @@ -173,8 +196,8 @@ active = isinstance(active, list) and active or [active] db = self.env.get_db_cnx() - for config in BuildConfig.select(self.env, db=db, - include_inactive=True): + for config in list(BuildConfig.select(self.env, db=db, + include_inactive=True)): config.active = config.name in active config.update(db=db) db.commit() @@ -182,18 +205,8 @@ def _create_config(self, req): req.perm.assert_permission('BUILD_CREATE') - name = req.args.get('name') - if not name: - raise TracError('Missing required field "name"', 'Missing field') - if not re.match(r'^[\w.-]+$', name): - raise TracError('The field "name" may only contain letters, ' - 'digits, periods, or dashes.', 'Invalid field') - config = BuildConfig(self.env) - config.name = req.args.get('name') - config.label = req.args.get('label', config.name) - config.path = req.args.get('path') - config.insert() + self._update_config(req, config) return config def _remove_configs(self, req): @@ -217,10 +230,10 @@ name = req.args.get('name') if not name: - raise TracError('Missing required field "name"', 'Missing field') + raise TracError('Missing required field "name"', 'Missing Field') if not re.match(r'^[\w.-]+$', name): raise TracError('The field "name" may only contain letters, ' - 'digits, periods, or dashes.', 'Invalid field') + 'digits, periods, or dashes.', 'Invalid Field') path = req.args.get('path', '') repos = self.env.get_repository(req.authname) @@ -229,12 +242,12 @@ node = repos.get_node(path, max_rev) assert node.isdir, '%s is not a directory' % node.path except (AssertionError, TracError), e: - raise TracError(e, 'Invalid repository path') + raise TracError(unicode(e), 'Invalid Repository Path') if req.args.get('min_rev'): try: repos.get_node(path, req.args.get('min_rev')) except TracError, e: - raise TracError(e, 'Invalid value for oldest revision') + raise TracError(unicode(e), 'Invalid Oldest Revision') recipe_xml = req.args.get('recipe', '') if recipe_xml: @@ -242,9 +255,9 @@ Recipe(xmlio.parse(recipe_xml)).validate() except xmlio.ParseError, e: raise TracError('Failure parsing recipe: %s' % e, - 'Invalid recipe') + 'Invalid Recipe') except InvalidRecipeError, e: - raise TracError(e, 'Invalid recipe') + raise TracError(unicode(e), 'Invalid Recipe') config.name = name config.path = repos.normalize_path(path) @@ -253,9 +266,40 @@ config.max_rev = req.args.get('max_rev') config.label = req.args.get('label', config.name) config.description = req.args.get('description', '') - config.update() - def _process_platform(self, req, platform): + if config.exists: + config.update() + else: + config.insert() + + def _create_platform(self, req, config_name): + req.perm.assert_permission('BUILD_MODIFY') + + name = req.args.get('name') + if not name: + raise TracError('Missing required field "name"', 'Missing field') + + platform = TargetPlatform(self.env, config=config_name, name=name) + self._update_platform(req, platform) + return platform + + def _remove_platforms(self, req): + req.perm.assert_permission('BUILD_MODIFY') + + sel = req.args.get('sel') + if not sel: + raise TracError('No platform selected') + sel = isinstance(sel, list) and sel or [sel] + + db = self.env.get_db_cnx() + for platform_id in sel: + platform = TargetPlatform.fetch(self.env, platform_id, db=db) + if not platform: + raise TracError('Target platform %r not found' % platform_id) + platform.delete(db=db) + db.commit() + + def _update_platform(self, req, platform): platform.name = req.args.get('name') properties = [int(key[9:]) for key in req.args.keys() @@ -269,6 +313,11 @@ for property, pattern in zip(properties, patterns) if req.args.get('property_%d' % property)] + if platform.exists: + platform.update() + else: + platform.insert() + add_rules = [int(key[9:]) for key in req.args.keys() if key.startswith('add_rule_')] if add_rules: @@ -277,7 +326,8 @@ rm_rules = [int(key[8:]) for key in req.args.keys() if key.startswith('rm_rule_')] if rm_rules: - del platform.rules[rm_rules[0]] + if rm_rules[0] < len(platform.rules): + del platform.rules[rm_rules[0]] return False return True
--- a/bitten/build/ctools.py +++ b/bitten/build/ctools.py @@ -13,10 +13,7 @@ import logging import re import os -try: - set -except NameError: - from sets import Set as set +import posixpath from bitten.build import CommandLine, FileSet from bitten.util import xmlio
--- a/bitten/build/tests/config.py +++ b/bitten/build/tests/config.py @@ -10,7 +10,6 @@ import platform import os -import shutil import tempfile import unittest
--- a/bitten/build/xmltools.py +++ b/bitten/build/xmltools.py @@ -13,9 +13,6 @@ import logging import os -from bitten.build import CommandLine -from bitten.util import xmlio - try: import libxml2 import libxslt
new file mode 100644 --- /dev/null +++ b/bitten/htdocs/admin.css @@ -0,0 +1,3 @@ +table.form th { text-align: right; } +div.platforms h3 { margin-top: 3em; } +table#platformlist td ul { list-style: none; margin: 0; padding: 0; }
--- a/bitten/master.py +++ b/bitten/master.py @@ -11,20 +11,11 @@ """Build master implementation.""" import calendar -from datetime import datetime, timedelta -import logging -import os import re -try: - set -except NameError: - from sets import Set as set -import sys import time from trac.config import BoolOption, IntOption from trac.core import * -from trac.env import Environment from trac.web import IRequestHandler, HTTPBadRequest, HTTPConflict, \ HTTPForbidden, HTTPMethodNotAllowed, HTTPNotFound, \ RequestDone
--- a/bitten/model.py +++ b/bitten/model.py @@ -10,11 +10,6 @@ """Model classes for objects persisted in the database.""" -try: - set -except NameError: - from sets import Set as set - from trac.db import Table, Column, Index __docformat__ = 'restructuredtext en'
--- a/bitten/slave.py +++ b/bitten/slave.py @@ -15,18 +15,13 @@ import logging import os import platform -try: - set -except NameError: - from sets import Set as set import shutil import tempfile import time -import urlparse from bitten.build import BuildError from bitten.build.config import Configuration -from bitten.recipe import Recipe, InvalidRecipeError +from bitten.recipe import Recipe from bitten.util import xmlio __all__ = ['BuildSlave', 'ExitSlave']
--- a/bitten/templates/bitten_admin_configs.cs +++ b/bitten/templates/bitten_admin_configs.cs @@ -2,7 +2,7 @@ if admin.config.name ?> <form class="mod" id="modconfig" method="post"> - <table summary=""><tr> + <table class="form" summary=""><tr> <td class="name"><label>Name:<br /> <input type="text" name="name" value="<?cs var:admin.config.name ?>" /> </label></td> @@ -14,20 +14,21 @@ <td colspan="2"><fieldset class="iefix"> <label for="description">Description (you may use <a tabindex="42" href="<?cs var:trac.href.wiki ?>/WikiFormatting">WikiFormatting</a> here):</label> - <p><textarea id="description" name="description" class="wikitext" rows="5" cols="78"><?cs + <p><textarea id="description" name="description" class="wikitext" rows="3" cols="65"><?cs var:admin.config.description ?></textarea></p> <script type="text/javascript" src="<?cs var:chrome.href ?>/common/js/wikitoolbar.js"></script> </fieldset></td> + </tr><tr> + <td colspan="2"><fieldset class="iefix"> + <label for="recipe">Recipe</label> + <p><textarea id="recipe" name="recipe" rows="8" cols="78"><?cs + var:admin.config.recipe ?></textarea></p> + </fieldset></td> </tr></table> - <fieldset id="recipe"> - <legend>Build Recipe</legend> - <textarea id="recipe" name="recipe" rows="8" cols="78"><?cs - var:admin.config.recipe ?></textarea> - </fieldset> <fieldset id="repos"> <legend>Repository Mapping</legend> - <table summary=""><tr> + <table class="form" summary=""><tr> <th><label for="path">Path:</label></th> <td colspan="3"><input type="text" name="path" size="48" value="<?cs var:admin.config.path ?>" /></td> @@ -40,61 +41,72 @@ var:admin.config.max_rev ?>" /></td> </table> </fieldset> - <fieldset> - <legend>Target Platforms</legend><?cs - if:len(admin.config.platforms) ?><ul><?cs - each:platform = admin.config.platforms ?> - <li><input type="checkbox" name="delete_platform" value="<?cs - var:platform.id ?>"> <a href="<?cs - var:platform.href ?>"><?cs var:platform.name ?></a> - </li><?cs - /each ?></ul><?cs - /if ?> - <div class="buttons"> - <input type="submit" name="new" value="Add target platform" /> - <input type="submit" name="delete" value="Delete selected platforms" /> - </div> - </fieldset> <div class="buttons"> <input type="submit" name="cancel" value="Cancel" /> <input type="submit" name="save" value="Save" /> </div> + <div class="platforms"> + <h3>Target Platforms</h3> + <table class="listing" id="platformlist"> + <thead> + <tr><th class="sel"> </th><th>Name</th><th>Rules</th></tr> + </thead><?cs each:platform = admin.config.platforms ?><tr> + <td class="sel"><input type="checkbox" name="sel" value="<?cs + var:platform.id ?>" /></td> + <td class="name"><a href="<?cs var:platform.href?>"><?cs + var:platform.name ?></a></td> + <td class="rules"><?cs if:len(platform.rules) ?><ul><?cs + each:rule = platform.rules ?><li><code> + <strong><?cs var:rule.property ?></strong> ~= <?cs var:rule.pattern ?> + </code></li><?cs + /each ?></ul><?cs + /if ?></td> + </tr><?cs + /each ?> + </table> + <div class="buttons"> + <input type="submit" name="new" value="Add platform" /> + <input type="submit" name="remove" value="Delete selected platforms" /> + </div> + </div> </form><?cs -elif admin.platform.name ?> +elif len(admin.platform) ?> <form class="mod" id="modplatform" method="post"> - <div class="field"><label>Target Platform: - <input type="text" name="name" value="<?cs var:admin.platform.name ?>" /> - </label></div> - <fieldset> - <legend>Rules</legend> - <table><thead><tr> - <th>Property name</th><th>Match pattern</th> - </tr></thead><tbody><?cs - each:rule = admin.platform.rules ?><tr> - <td><input type="text" name="property_<?cs var:name(rule) ?>" value="<?cs - var:rule.property ?>" /></td> - <td><input type="text" name="pattern_<?cs var:name(rule) ?>" value="<?cs - var:rule.pattern ?>" /></td> - <td><input type="submit" name="add_rule_<?cs - var:name(rule) ?>" value="+" /><input type="submit" name="rm_rule_<?cs - var:name(rule) ?>" value="-" /> - </td> - </tr><?cs /each ?> - </tbody></table> - </fieldset> - <div class="buttons"> - <form method="get" action=""><div> - <input type="hidden" name="action" value="<?cs - if:admin.platform.exists ?>edit<?cs else ?>new<?cs /if ?>" /> - <input type="hidden" name="platform" value="<?cs - var:admin.platform.id ?>" /> - <input type="submit" name="cancel" value="Cancel" /> - <input type="submit" name="save" value="<?cs - if:admin.platform.exists ?>Save<?cs else ?>Add<?cs - /if ?>" /> - </div></form> - </div> + <div class="field"><label>Target Platform: + <input type="text" name="name" value="<?cs var:admin.platform.name ?>" /> + </label></div> + <fieldset> + <legend>Rules</legend> + <table><thead><tr> + <th>Property name</th><th>Match pattern</th> + </tr></thead><tbody><?cs + each:rule = admin.platform.rules ?><tr> + <td><input type="text" name="property_<?cs var:name(rule) ?>" value="<?cs + var:rule.property ?>" /></td> + <td><input type="text" name="pattern_<?cs var:name(rule) ?>" value="<?cs + var:rule.pattern ?>" /></td> + <td><input type="submit" name="add_rule_<?cs + var:name(rule) ?>" value="+" /><input type="submit" name="rm_rule_<?cs + var:name(rule) ?>" value="-" /> + </td> + </tr><?cs /each ?> + </tbody></table> + </fieldset> + <div class="buttons"> + <form method="get" action=""><div> + <input type="hidden" name="<?cs + if:admin.platform.exists ?>edit<?cs else ?>new<?cs /if ?>" value="" /> + <input type="hidden" name="platform" value="<?cs + var:admin.platform.id ?>" /> + <input type="submit" name="cancel" value="Cancel" /> + <?cs if:admin.platform.exists ?> + <input type="submit" name="save" value="Save" /> + <?cs else ?> + <input type="submit" name="add" value="Add" /> + <?cs /if ?> + </div></form> + </div> </form><?cs else ?>
--- a/bitten/tests/admin.py +++ b/bitten/tests/admin.py @@ -15,12 +15,9 @@ from trac.db import DatabaseManager from trac.perm import PermissionCache, PermissionError, PermissionSystem from trac.test import EnvironmentStub, Mock -from trac.versioncontrol import Repository -from trac.web.clearsilver import HDFWrapper from trac.web.href import Href -from trac.web.main import Request, RequestDone -from bitten.main import BuildSystem -from bitten.model import BuildConfig, TargetPlatform, Build, schema +from trac.web.main import RequestDone +from bitten.model import BuildConfig, TargetPlatform, schema from bitten.admin import BuildMasterAdminPageProvider, \ BuildConfigurationsAdminPageProvider @@ -69,7 +66,7 @@ def test_process_get_request(self): data = {} - req = Mock(method='GET', hdf=data, + req = Mock(method='GET', chrome={}, hdf=data, href=Href('/'), perm=PermissionCache(self.env, 'joe')) provider = BuildMasterAdminPageProvider(self.env) @@ -153,9 +150,9 @@ req = Mock(perm=PermissionCache(self.env, 'joe')) self.assertEqual([], list(provider.get_admin_pages(req))) - def test_process_get_request_overview_empty(self): + def test_process_view_configs_empty(self): data = {} - req = Mock(method='GET', hdf=data, + req = Mock(method='GET', chrome={}, hdf=data, href=Href('/'), perm=PermissionCache(self.env, 'joe')) provider = BuildConfigurationsAdminPageProvider(self.env) @@ -174,7 +171,7 @@ min_rev='123', max_rev='456').insert() data = {} - req = Mock(method='GET', hdf=data, href=Href('/'), + req = Mock(method='GET', chrome={}, hdf=data, href=Href('/'), perm=PermissionCache(self.env, 'joe')) provider = BuildConfigurationsAdminPageProvider(self.env) @@ -203,7 +200,7 @@ TargetPlatform(self.env, config='foo', name='any').insert() data = {} - req = Mock(method='GET', hdf=data, href=Href('/'), + req = Mock(method='GET', chrome={}, hdf=data, href=Href('/'), perm=PermissionCache(self.env, 'joe')) provider = BuildConfigurationsAdminPageProvider(self.env) @@ -219,11 +216,34 @@ 'path': 'branches/foo', 'min_rev': None, 'max_rev': None, 'active': True, 'platforms': [{ 'href': '/admin/bitten/configs/foo/1', - 'name': 'any', - 'id': 1 + 'name': 'any', 'id': 1, 'rules': [] }] }, config) + def test_process_activate_config(self): + BuildConfig(self.env, name='foo', path='branches/foo').insert() + BuildConfig(self.env, name='bar', path='branches/bar').insert() + + redirected_to = [] + def redirect(url): + redirected_to.append(url) + raise RequestDone + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + abs_href=Href('http://example.org/'), redirect=redirect, + authname='joe', + args={'apply': '', 'active': ['foo']}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', '') + self.fail('Expected RequestDone') + + except RequestDone: + self.assertEqual('http://example.org/admin/bitten/configs', + redirected_to[0]) + config = BuildConfig.fetch(self.env, name='foo') + self.assertEqual(True, config.active) + def test_process_add_config(self): BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', active=True).insert() @@ -234,6 +254,7 @@ raise RequestDone req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), abs_href=Href('http://example.org/'), redirect=redirect, + authname='joe', args={'add': '', 'name': 'bar', 'label': 'Bar'}) provider = BuildConfigurationsAdminPageProvider(self.env) @@ -278,8 +299,9 @@ except TracError, e: self.assertEqual('Missing required field "name"', e.message) + self.assertEqual('Missing Field', e.title) - def test_process_add_config_no_name(self): + def test_process_add_config_invalid_name(self): req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), args={'add': '', 'name': 'no spaces allowed'}) @@ -291,6 +313,25 @@ except TracError, e: self.assertEqual('The field "name" may only contain letters, ' 'digits, periods, or dashes.', e.message) + self.assertEqual('Invalid Field', e.title) + + def test_new_config_submit_with_invalid_path(self): + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + authname='joe', + args={'add': '', 'name': 'foo', 'path': 'invalid/path'}) + + def get_node(path, rev=None): + raise TracError('No such node') + self.repos = Mock(get_node=get_node) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', '') + self.fail('Expected TracError') + + except TracError, e: + self.assertEqual('No such node', e.message) + self.assertEqual('Invalid Repository Path', e.title) def test_process_add_config_no_perms(self): BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', @@ -397,38 +438,7 @@ self.assertRaises(PermissionError, provider.process_admin_request, req, 'bitten', 'configs', '') - def test_process_update_config_no_name(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - args={'save': ''}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('Missing required field "name"', e.message) - - def test_process_update_config_invalid_name(self): - BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', - active=True).insert() - - req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), - args={'save': '', 'name': 'no spaces allowed'}) - - provider = BuildConfigurationsAdminPageProvider(self.env) - try: - provider.process_admin_request(req, 'bitten', 'configs', 'foo') - self.fail('Expected TracError') - - except TracError, e: - self.assertEqual('The field "name" may only contain letters, ' - 'digits, periods, or dashes.', e.message) - - def test_process_update_config_valid(self): + def test_process_update_config(self): BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', active=True).insert() @@ -455,6 +465,300 @@ self.assertEqual('Foobar', config.label) self.assertEqual('Thanks for all the fish!', config.description) + def test_process_update_config_no_name(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + args={'save': ''}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', 'foo') + self.fail('Expected TracError') + + except TracError, e: + self.assertEqual('Missing required field "name"', e.message) + self.assertEqual('Missing Field', e.title) + + def test_process_update_config_invalid_name(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + args={'save': '', 'name': 'no spaces allowed'}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', 'foo') + self.fail('Expected TracError') + + except TracError, e: + self.assertEqual('The field "name" may only contain letters, ' + 'digits, periods, or dashes.', e.message) + self.assertEqual('Invalid Field', e.title) + + def test_process_update_config_invalid_path(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + authname='joe', + args={'save': '', 'name': 'foo', 'path': 'invalid/path'}) + + def get_node(path, rev=None): + raise TracError('No such node') + self.repos = Mock(get_node=get_node) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', 'foo') + self.fail('Expected TracError') + + except TracError, e: + self.assertEqual('No such node', e.message) + self.assertEqual('Invalid Repository Path', e.title) + + def test_process_update_config_non_wellformed_recipe(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + authname='joe', + args={'save': '', 'name': 'foo', 'recipe': 'not_xml'}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', 'foo') + self.fail('Expected TracError') + + except TracError, e: + self.assertEqual('Failure parsing recipe: syntax error: line 1, ' + 'column 0', e.message) + self.assertEqual('Invalid Recipe', e.title) + + def test_process_update_config_invalid_recipe(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + authname='joe', + args={'save': '', 'name': 'foo', + 'recipe': '<build><step /></build>'}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', 'foo') + self.fail('Expected TracError') + + except TracError, e: + self.assertEqual('Steps must have an "id" attribute', e.message) + self.assertEqual('Invalid Recipe', e.title) + + def test_process_new_platform(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + + data = {} + req = Mock(method='POST', chrome={}, hdf=data, href=Href('/'), + perm=PermissionCache(self.env, 'joe'), + args={'new': ''}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + template_name, content_type = provider.process_admin_request( + req, 'bitten', 'configs', 'foo' + ) + + self.assertEqual('bitten_admin_configs.cs', template_name) + self.assertEqual(None, content_type) + platform = data['admin']['platform'] + self.assertEqual({ + 'id': None, 'exists': False, 'name': None, 'rules': [('', '')], + }, platform) + + def test_process_add_platform(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + + redirected_to = [] + def redirect(url): + redirected_to.append(url) + raise RequestDone + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + abs_href=Href('http://example.org/'), redirect=redirect, + authname='joe', + args={'add': '', 'new': '', 'name': 'Test', + 'property_0': 'family', 'pattern_0': 'posix'}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', 'foo') + self.fail('Expected RequestDone') + + except RequestDone: + self.assertEqual('http://example.org/admin/bitten/configs/foo', + redirected_to[0]) + platforms = list(TargetPlatform.select(self.env, config='foo')) + self.assertEqual(1, len(platforms)) + self.assertEqual('Test', platforms[0].name) + self.assertEqual([('family', 'posix')], platforms[0].rules) + + def test_process_add_platform_cancel(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + + redirected_to = [] + def redirect(url): + redirected_to.append(url) + raise RequestDone + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + abs_href=Href('http://example.org/'), redirect=redirect, + authname='joe', + args={'cancel': '', 'new': '', 'name': 'Test', + 'property_0': 'family', 'pattern_0': 'posix'}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', 'foo') + self.fail('Expected RequestDone') + + except RequestDone: + self.assertEqual('http://example.org/admin/bitten/configs/foo', + redirected_to[0]) + platforms = list(TargetPlatform.select(self.env, config='foo')) + self.assertEqual(0, len(platforms)) + + def test_process_remove_platforms(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + platform = TargetPlatform(self.env, config='foo', name='any') + platform.insert() + + redirected_to = [] + def redirect(url): + redirected_to.append(url) + raise RequestDone + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + abs_href=Href('http://example.org/'), redirect=redirect, + authname='joe', + args={'remove': '', 'sel': str(platform.id)}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', 'foo') + self.fail('Expected RequestDone') + + except RequestDone: + self.assertEqual('http://example.org/admin/bitten/configs/foo', + redirected_to[0]) + platforms = list(TargetPlatform.select(self.env, config='foo')) + self.assertEqual(0, len(platforms)) + + def test_process_remove_platforms_no_selection(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + platform = TargetPlatform(self.env, config='foo', name='any') + platform.insert() + + redirected_to = [] + def redirect(url): + redirected_to.append(url) + raise RequestDone + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + abs_href=Href('http://example.org/'), redirect=redirect, + authname='joe', + args={'remove': ''}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', 'foo') + self.fail('Expected TracError') + + except TracError, e: + self.assertEqual('No platform selected', e.message) + + def test_process_edit_platform(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + platform = TargetPlatform(self.env, config='foo', name='any') + platform.insert() + + data = {} + req = Mock(method='GET', chrome={}, hdf=data, href=Href('/'), + perm=PermissionCache(self.env, 'joe'), args={}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + template_name, content_type = provider.process_admin_request( + req, 'bitten', 'configs', 'foo/%d' % platform.id + ) + + self.assertEqual('bitten_admin_configs.cs', template_name) + self.assertEqual(None, content_type) + platform = data['admin']['platform'] + self.assertEqual({ + 'id': 1, 'exists': True, 'name': 'any', 'rules': [('', '')], + }, platform) + + def test_process_update_platform(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + platform = TargetPlatform(self.env, config='foo', name='any') + platform.insert() + + redirected_to = [] + def redirect(url): + redirected_to.append(url) + raise RequestDone + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + abs_href=Href('http://example.org/'), redirect=redirect, + authname='joe', + args={'save': '', 'edit': '', 'name': 'Test', + 'property_0': 'family', 'pattern_0': 'posix'}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', + 'foo/%d' % platform.id) + self.fail('Expected RequestDone') + + except RequestDone: + self.assertEqual('http://example.org/admin/bitten/configs/foo', + redirected_to[0]) + platforms = list(TargetPlatform.select(self.env, config='foo')) + self.assertEqual(1, len(platforms)) + self.assertEqual('Test', platforms[0].name) + self.assertEqual([('family', 'posix')], platforms[0].rules) + + def test_process_update_platform_cancel(self): + BuildConfig(self.env, name='foo', label='Foo', path='branches/foo', + active=True).insert() + platform = TargetPlatform(self.env, config='foo', name='any') + platform.insert() + + redirected_to = [] + def redirect(url): + redirected_to.append(url) + raise RequestDone + req = Mock(method='POST', perm=PermissionCache(self.env, 'joe'), + abs_href=Href('http://example.org/'), redirect=redirect, + authname='joe', + args={'cancel': '', 'edit': '', 'name': 'Changed', + 'property_0': 'family', 'pattern_0': 'posix'}) + + provider = BuildConfigurationsAdminPageProvider(self.env) + try: + provider.process_admin_request(req, 'bitten', 'configs', + 'foo/%d' % platform.id) + self.fail('Expected RequestDone') + + except RequestDone: + self.assertEqual('http://example.org/admin/bitten/configs/foo', + redirected_to[0]) + platforms = list(TargetPlatform.select(self.env, config='foo')) + self.assertEqual(1, len(platforms)) + self.assertEqual('any', platforms[0].name) + self.assertEqual([], platforms[0].rules) + def suite(): suite = unittest.TestSuite()
--- a/bitten/tests/master.py +++ b/bitten/tests/master.py @@ -8,7 +8,6 @@ # you should have received as part of this distribution. The terms # are also available at http://bitten.edgewall.org/wiki/License. -from datetime import datetime import re import shutil from StringIO import StringIO @@ -22,7 +21,6 @@ RequestDone from trac.web.href import Href -from bitten.main import BuildSystem from bitten.master import BuildMaster from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \ BuildLog, Report, schema
--- a/bitten/tests/queue.py +++ b/bitten/tests/queue.py @@ -16,7 +16,7 @@ from trac.db import DatabaseManager from trac.test import EnvironmentStub, Mock -from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, schema +from bitten.model import BuildConfig, TargetPlatform, Build, schema from bitten.queue import BuildQueue, collect_changes
--- a/bitten/tests/slave.py +++ b/bitten/tests/slave.py @@ -12,7 +12,6 @@ import shutil import tempfile import unittest -import zipfile from trac.test import Mock from bitten.slave import BuildSlave @@ -22,8 +21,7 @@ def setUp(self): self.work_dir = tempfile.mkdtemp(prefix='bitten_test') - self.slave = Slave(None, work_dir=self.work_dir) - self.handler = OrchestrationProfileHandler(Mock(session=self.slave)) + self.slave = BuildSlave(None, work_dir=self.work_dir) def tearDown(self): shutil.rmtree(self.work_dir)
--- a/bitten/tests/web_ui.py +++ b/bitten/tests/web_ui.py @@ -12,16 +12,12 @@ import tempfile import unittest -from trac.core import TracError from trac.db import DatabaseManager from trac.perm import PermissionCache, PermissionSystem from trac.test import EnvironmentStub, Mock -from trac.versioncontrol import Repository from trac.web.clearsilver import HDFWrapper from trac.web.href import Href -from trac.web.main import Request, RequestDone -from bitten.main import BuildSystem -from bitten.model import BuildConfig, TargetPlatform, Build, schema +from bitten.model import BuildConfig, TargetPlatform, schema from bitten.web_ui import BuildConfigController @@ -68,18 +64,6 @@ self.assertEqual('overview', req.hdf['page.mode']) self.assertEqual('0', req.hdf.get('build.can_create', '0')) - def test_overview_admin(self): - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='GET', base_path='', cgi_location='', - path_info='/build', href=Href('/trac'), args={}, chrome={}, - hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe')) - - module = BuildConfigController(self.env) - assert module.match_request(req) - module.process_request(req) - - self.assertEqual('1', req.hdf.get('config.can_create')) - def test_view_config(self): config = BuildConfig(self.env, name='test', path='trunk') config.insert() @@ -136,415 +120,6 @@ self.assertEqual('/trac/build/test?page=2', req.hdf.get('chrome.links.next.0.href')) - def test_view_config_admin(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='GET', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), args={}, - chrome={}, hdf=HDFWrapper(), authname='joe', - perm=PermissionCache(self.env, 'joe')) - - module = BuildConfigController(self.env) - assert module.match_request(req) - module.process_request(req) - - self.assertEqual('1', req.hdf.get('config.can_delete')) - self.assertEqual('1', req.hdf.get('config.can_modify')) - - def test_new_config(self): - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='GET', base_path='', cgi_location='', - path_info='/build', args={'action': 'new'}, hdf=HDFWrapper(), - href=Href('/trac'), chrome={}, - perm=PermissionCache(self.env, 'joe')) - - module = BuildConfigController(self.env) - assert module.match_request(req) - module.process_request(req) - - self.assertEqual('edit_config', req.hdf['page.mode']) - - def test_new_config_submit(self): - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build', href=Href('/trac'), redirect=redirect, - hdf=HDFWrapper(), authname='joe', - perm=PermissionCache(self.env, 'joe'), - args={'action': 'new', 'name': 'test', 'path': 'test/trunk', - 'label': 'Test', 'description': 'Bla bla'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(RequestDone, module.process_request, req) - self.assertEqual('/trac/build/test', redirected_to[0]) - - config = BuildConfig.fetch(self.env, 'test') - assert config.exists - assert not config.active - self.assertEqual('Test', config.label) - self.assertEqual('test/trunk', config.path) - self.assertEqual('Bla bla', config.description) - - def test_new_config_submit_without_name(self): - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build', href=Href('/trac'), hdf=HDFWrapper(), - perm=PermissionCache(self.env, 'joe'), - args={'action': 'new', 'name': '', 'path': 'test/trunk', - 'label': 'Test', 'description': 'Bla bla'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(TracError, module.process_request, req) - - def test_new_config_submit_with_invalid_name(self): - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build', href=Href('/trac'), hdf=HDFWrapper(), - perm=PermissionCache(self.env, 'joe'), - args={'action': 'new', 'name': 'Foo bar', - 'path': 'test/trunk', 'label': 'Test', - 'description': 'Bla bla'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(TracError, module.process_request, req) - - def test_new_config_submit_invalid_path(self): - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build', href=Href('/trac'), hdf=HDFWrapper(), - authname='joe', perm=PermissionCache(self.env, 'joe'), - args={'action': 'new', 'name': 'test', 'path': 'test/trunk', - 'label': 'Test', 'description': 'Bla bla'}) - - def get_node(path, rev=None): - raise TracError('No such node') - self.repos = Mock(get_node=get_node) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(TracError, module.process_request, req) - - def test_new_config_submit_with_non_wellformed_recipe(self): - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build', href=Href('/trac'), hdf=HDFWrapper(), - authname='joe', perm=PermissionCache(self.env, 'joe'), - args={'action': 'new', 'name': 'test', 'path': 'test/trunk', - 'label': 'Test', 'description': 'Bla bla', - 'recipe': '<build><step>'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(TracError, module.process_request, req) - - def test_new_config_submit_with_invalid_recipe(self): - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build', href=Href('/trac'), hdf=HDFWrapper(), - authname='joe', perm=PermissionCache(self.env, 'joe'), - args={'action': 'new', 'name': 'test', 'path': 'test/trunk', - 'label': 'Test', 'description': 'Bla bla', - 'recipe': '<build><step/></build>'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(TracError, module.process_request, req) - - def test_new_config_cancel(self): - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build', href=Href('/trac'), redirect=redirect, - hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'), - args={'action': 'new', 'cancel': '1', 'name': 'test'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(RequestDone, module.process_request, req) - self.assertEqual('/trac/build', redirected_to[0]) - - self.assertEqual(None, BuildConfig.fetch(self.env, 'test')) - - def test_delete_config(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='GET', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), chrome={}, - hdf=HDFWrapper(), perm=PermissionCache(self.env, 'joe'), - args={'action': 'delete'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - module.process_request(req) - - self.assertEqual('delete_config', req.hdf['page.mode']) - - def test_delete_config_submit(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), - redirect=redirect, hdf=HDFWrapper(), - perm=PermissionCache(self.env, 'joe'), - args={'action': 'delete'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(RequestDone, module.process_request, req) - self.assertEqual('/trac/build', redirected_to[0]) - - self.assertEqual(None, BuildConfig.fetch(self.env, 'test')) - - def test_edit_config_cancel(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), - redirect=redirect, hdf=HDFWrapper(), - perm=PermissionCache(self.env, 'joe'), - args={'action': 'delete', 'cancel': ''}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(RequestDone, module.process_request, req) - self.assertEqual('/trac/build/test', redirected_to[0]) - - self.assertEqual(True, BuildConfig.fetch(self.env, 'test').exists) - - def test_edit_config(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='GET', base_path='', cgi_location='', - path_info='/build/test', hdf=HDFWrapper(), - href=Href('/build/test'), chrome={}, - perm=PermissionCache(self.env, 'joe'), - args={'action': 'edit'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - module.process_request(req) - - self.assertEqual('edit_config', req.hdf['page.mode']) - - def test_edit_config_submit(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), - redirect=redirect, hdf=HDFWrapper(), - authname='joe', perm=PermissionCache(self.env, 'joe'), - args={'action': 'edit', 'name': 'foo', 'path': 'test/trunk', - 'label': 'Test', 'description': 'Bla bla'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(RequestDone, module.process_request, req) - self.assertEqual('/trac/build/foo', redirected_to[0]) - - self.assertEqual(None, BuildConfig.fetch(self.env, 'test')) - - config = BuildConfig.fetch(self.env, 'foo') - assert config.exists - self.assertEqual('Test', config.label) - self.assertEqual('test/trunk', config.path) - self.assertEqual('Bla bla', config.description) - - def test_edit_config_cancel(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), - redirect=redirect, hdf=HDFWrapper(), - perm=PermissionCache(self.env, 'joe'), - args={'action': 'edit', 'cancel': ''}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(RequestDone, module.process_request, req) - self.assertEqual('/trac/build/test', redirected_to[0]) - - def test_new_platform(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='GET', base_path='', cgi_location='', - path_info='/build/test', hdf=HDFWrapper(), href=Href('trac'), - chrome={}, perm=PermissionCache(self.env, 'joe'), - args={'action': 'edit', 'new': '1'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - module.process_request(req) - - self.assertEqual('edit_platform', req.hdf['page.mode']) - - def test_new_platform_submit(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), - redirect=redirect, hdf=HDFWrapper(), - perm=PermissionCache(self.env, 'joe'), - args={'action': 'new', 'name': 'Test'}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(RequestDone, module.process_request, req) - self.assertEqual('/trac/build/test?action=edit', redirected_to[0]) - - def test_new_platform_cancel(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), - redirect=redirect, hdf=HDFWrapper(), - perm=PermissionCache(self.env, 'joe'), - args={'action': 'new', 'cancel': ''}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(RequestDone, module.process_request, req) - self.assertEqual('/trac/build/test?action=edit', redirected_to[0]) - - def test_edit_platform(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - platform = TargetPlatform(self.env) - platform.config = 'test' - platform.name = 'linux' - platform.rules.append(('os', 'linux?')) - platform.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - req = Mock(method='GET', base_path='', cgi_location='', - path_info='/build/test', hdf=HDFWrapper(), - href=Href('/trac'), chrome={}, - perm=PermissionCache(self.env, 'joe'), - args={'action': 'edit', 'platform': platform.id}) - - module = BuildConfigController(self.env) - assert module.match_request(req) - module.process_request(req) - - self.assertEqual('edit_platform', req.hdf['page.mode']) - - def test_edit_platform_submit(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - platform = TargetPlatform(self.env) - platform.config = 'test' - platform.name = 'linux' - platform.rules.append(('os', 'linux?')) - platform.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), - redirect=redirect, hdf=HDFWrapper(), - args={'action': 'edit', 'platform': platform.id, - 'name': 'Test'}, - perm=PermissionCache(self.env, 'joe')) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(RequestDone, module.process_request, req) - self.assertEqual('/trac/build/test?action=edit', redirected_to[0]) - - def test_edit_platform_cancel(self): - config = BuildConfig(self.env) - config.name = 'test' - config.insert() - platform = TargetPlatform(self.env) - platform.config = 'test' - platform.name = 'linux' - platform.rules.append(('os', 'linux')) - platform.insert() - - PermissionSystem(self.env).grant_permission('joe', 'BUILD_ADMIN') - redirected_to = [] - def redirect(url): - redirected_to.append(url) - raise RequestDone - req = Mock(method='POST', base_path='', cgi_location='', - path_info='/build/test', href=Href('/trac'), - redirect=redirect, hdf=HDFWrapper(), - args={'action': 'edit', 'platform': platform.id, - 'cancel': ''}, - perm=PermissionCache(self.env, 'joe')) - - module = BuildConfigController(self.env) - assert module.match_request(req) - self.assertRaises(RequestDone, module.process_request, req) - self.assertEqual('/trac/build/test?action=edit', redirected_to[0]) - def suite(): return unittest.makeSuite(BuildConfigControllerTestCase, 'test')
--- a/bitten/web_ui.py +++ b/bitten/web_ui.py @@ -13,10 +13,6 @@ from datetime import datetime import posixpath import re -try: - set -except NameError: - from sets import Set as set from StringIO import StringIO import pkg_resources @@ -35,8 +31,6 @@ from bitten.model import BuildConfig, TargetPlatform, Build, BuildStep, \ BuildLog, Report from bitten.queue import collect_changes -from bitten.recipe import Recipe, InvalidRecipeError -from bitten.util import xmlio _status_label = {Build.PENDING: 'pending', Build.IN_PROGRESS: 'in progress', @@ -125,230 +119,18 @@ view = req.args.get('view') config = req.args.get('config') - if req.method == 'POST': - if config: - if action == 'new': - self._do_create_platform(req, config) - elif action == 'delete': - self._do_delete_config(req, config) - else: - platform_id = req.args.get('platform') - if platform_id: - if action == 'edit': - self._do_save_platform(req, config, platform_id) - elif 'delete' in req.args: - self._do_delete_platforms(req) - self._render_config_form(req, config) - elif 'new' in req.args: - platform = TargetPlatform(self.env, config=config) - self._render_platform_form(req, platform) - else: - self._do_save_config(req, config) - else: - if action == 'new': - self._do_create_config(req) + if config: + self._render_config(req, config) + elif view == 'inprogress': + self._render_inprogress(req) else: - if config: - if action == 'delete': - self._render_config_confirm(req, config) - elif action == 'edit': - platform_id = req.args.get('platform') - if platform_id: - platform = TargetPlatform.fetch(self.env, - int(platform_id)) - self._render_platform_form(req, platform) - elif 'new' in req.args: - platform = TargetPlatform(self.env, config=config) - self._render_platform_form(req, platform) - else: - self._render_config_form(req, config) - else: - self._render_config(req, config) - else: - if action == 'new': - self._render_config_form(req) - elif view == 'inprogress': - self._render_inprogress(req) - else: - self._render_overview(req) + self._render_overview(req) add_stylesheet(req, 'bitten/bitten.css') return 'bitten_config.cs', None # Internal methods - def _do_create_config(self, req): - """Create a new build configuration.""" - req.perm.assert_permission('BUILD_CREATE') - - if 'cancel' in req.args: - req.redirect(req.href.build()) - - config_name = req.args.get('name') - - if BuildConfig.fetch(self.env, config_name): - raise TracError('A build configuration with the name "%s" already ' - 'exists' % config_name, 'Duplicate name') - - config = BuildConfig(self.env) - self._process_config(req, config) - config.insert() - - req.redirect(req.href.build(config.name)) - - def _do_delete_config(self, req, config_name): - """Save changes to a build configuration.""" - req.perm.assert_permission('BUILD_DELETE') - - if 'cancel' in req.args: - req.redirect(req.href.build(config_name)) - - db = self.env.get_db_cnx() - - config = BuildConfig.fetch(self.env, config_name, db=db) - assert config, 'Build configuration "%s" does not exist' % config_name - - config.delete(db=db) - - db.commit() - - req.redirect(req.href.build()) - - def _do_save_config(self, req, config_name): - """Save changes to a build configuration.""" - req.perm.assert_permission('BUILD_MODIFY') - - if 'cancel' in req.args: - req.redirect(req.href.build(config_name)) - - config = BuildConfig.fetch(self.env, config_name) - if not config: - # FIXME: 404 - raise TracError('Build configuration "%s" does not exist' - % config_name, 'Object not found') - - if 'activate' in req.args: - config.active = True - - elif 'deactivate' in req.args: - config.active = False - - else: - self._process_config(req, config) - - config.update() - req.redirect(req.href.build(config.name)) - - def _process_config(self, req, config): - name = req.args.get('name') - if not name: - raise TracError('Missing required field "name"', 'Missing field') - if not re.match(r'^[\w.-]+$', name): - raise TracError('The field "name" may only contain letters, ' - 'digits, periods, or dashes.', 'Invalid field') - - path = req.args.get('path', '') - repos = self.env.get_repository(req.authname) - max_rev = req.args.get('max_rev') or None - try: - node = repos.get_node(path, max_rev) - assert node.isdir, '%s is not a directory' % node.path - except (AssertionError, TracError), e: - raise TracError(e, 'Invalid repository path') - if req.args.get('min_rev'): - try: - repos.get_node(path, req.args.get('min_rev')) - except TracError, e: - raise TracError(e, 'Invalid value for oldest revision') - - recipe_xml = req.args.get('recipe', '') - if recipe_xml: - try: - Recipe(xmlio.parse(recipe_xml)).validate() - except xmlio.ParseError, e: - raise TracError('Failure parsing recipe: %s' % e, - 'Invalid recipe') - except InvalidRecipeError, e: - raise TracError(e, 'Invalid recipe') - - config.name = name - config.path = repos.normalize_path(path) - config.recipe = recipe_xml - config.min_rev = req.args.get('min_rev') - config.max_rev = req.args.get('max_rev') - config.label = req.args.get('label', '') - config.description = req.args.get('description', '') - - def _do_create_platform(self, req, config_name): - """Create a new target platform.""" - req.perm.assert_permission('BUILD_MODIFY') - - if 'cancel' in req.args: - req.redirect(req.href.build(config_name, action='edit')) - - platform = TargetPlatform(self.env, config=config_name) - if self._process_platform(req, platform): - platform.insert() - req.redirect(req.href.build(config_name, action='edit')) - - def _do_delete_platforms(self, req): - """Delete selected target platforms.""" - req.perm.assert_permission('BUILD_MODIFY') - self.log.debug('_do_delete_platforms') - - db = self.env.get_db_cnx() - for platform_id in [int(id) for id in req.args.get('delete_platform')]: - platform = TargetPlatform.fetch(self.env, platform_id, db=db) - self.log.info('Deleting target platform %s of configuration %s', - platform.name, platform.config) - platform.delete(db=db) - - # FIXME: this should probably also delete all builds done for this - # platform, and all the associated reports - - db.commit() - - def _do_save_platform(self, req, config_name, platform_id): - """Save changes to a target platform.""" - req.perm.assert_permission('BUILD_MODIFY') - - if 'cancel' in req.args: - req.redirect(req.href.build(config_name, action='edit')) - - platform = TargetPlatform.fetch(self.env, platform_id) - if self._process_platform(req, platform): - platform.update() - req.redirect(req.href.build(config_name, action='edit')) - - def _process_platform(self, req, platform): - platform.name = req.args.get('name') - - properties = [int(key[9:]) for key in req.args.keys() - if key.startswith('property_')] - properties.sort() - patterns = [int(key[8:]) for key in req.args.keys() - if key.startswith('pattern_')] - patterns.sort() - platform.rules = [(req.args.get('property_%d' % property), - req.args.get('pattern_%d' % pattern)) - for property, pattern in zip(properties, patterns) - if req.args.get('property_%d' % property)] - - add_rules = [int(key[9:]) for key in req.args.keys() - if key.startswith('add_rule_')] - if add_rules: - platform.rules.insert(add_rules[0] + 1, ('', '')) - self._render_platform_form(req, platform) - return False - rm_rules = [int(key[8:]) for key in req.args.keys() - if key.startswith('rm_rule_')] - if rm_rules: - del platform.rules[rm_rules[0]] - self._render_platform_form(req, platform) - return False - - return True - def _render_overview(self, req): req.hdf['title'] = 'Build Status' show_all = False @@ -356,8 +138,6 @@ show_all = True req.hdf['config.show_all'] = show_all - add_link(req, 'views', req.href.build(view='inprogress'), 'In Progress Builds') - configs = BuildConfig.select(self.env, include_inactive=show_all) for idx, config in enumerate(configs): prefix = 'configs.%d' % idx @@ -402,7 +182,8 @@ } req.hdf['page.mode'] = 'overview' - req.hdf['config.can_create'] = req.perm.has_permission('BUILD_CREATE') + add_link(req, 'views', req.href.build(view='inprogress'), + 'In Progress Builds') def _render_inprogress(self, req): req.hdf['title'] = 'In Progress Builds' @@ -475,9 +256,7 @@ 'max_rev': config.max_rev, 'max_rev_href': req.href.changeset(config.max_rev), 'active': config.active, 'description': description, - 'browser_href': req.href.browser(config.path), - 'can_modify': req.perm.has_permission('BUILD_MODIFY'), - 'can_delete': req.perm.has_permission('BUILD_DELETE') + 'browser_href': req.href.browser(config.path) } req.hdf['page.mode'] = 'view_config' @@ -547,53 +326,6 @@ next_href = req.href.build(config.name, page=page + 1) add_link(req, 'next', next_href, 'Next Page') - def _render_config_confirm(self, req, config_name): - req.perm.assert_permission('BUILD_DELETE') - config = BuildConfig.fetch(self.env, config_name) - req.hdf['title'] = 'Delete Build Configuration "%s"' \ - % config.label or config.name - req.hdf['config'] = {'name': config.name} - req.hdf['page.mode'] = 'delete_config' - - def _render_config_form(self, req, config_name=None): - config = BuildConfig.fetch(self.env, config_name) - if config: - req.perm.assert_permission('BUILD_MODIFY') - req.hdf['config'] = { - 'name': config.name, 'exists': config.exists, - 'path': config.path, 'active': config.active, - 'recipe': config.recipe, 'min_rev': config.min_rev, - 'max_rev': config.max_rev, 'label': config.label, - 'description': config.description - } - - req.hdf['title'] = 'Edit Build Configuration "%s"' \ - % config.label or config.name - for idx, platform in enumerate(TargetPlatform.select(self.env, - config_name)): - req.hdf['config.platforms.%d' % idx] = { - 'id': platform.id, 'name': platform.name, - 'href': req.href.build(config_name, action='edit', - platform=platform.id) - } - else: - req.perm.assert_permission('BUILD_CREATE') - req.hdf['title'] = 'Create Build Configuration' - req.hdf['page.mode'] = 'edit_config' - - def _render_platform_form(self, req, platform): - req.perm.assert_permission('BUILD_MODIFY') - if platform.exists: - req.hdf['title'] = 'Edit Target Platform "%s"' % platform.name - else: - req.hdf['title'] = 'Add Target Platform' - req.hdf['platform'] = { - 'name': platform.name, 'id': platform.id, 'exists': platform.exists, - 'rules': [{'property': propname, 'pattern': pattern} - for propname, pattern in platform.rules] or [('', '')] - } - req.hdf['page.mode'] = 'edit_platform' - class BuildController(Component): """Renders the build page."""
--- a/doc/install.txt +++ b/doc/install.txt @@ -39,9 +39,12 @@ done. You might need to install software that the build of your project requires, but the Bitten build slave itself doesn't require anything extra. -For the build master and web interface, you'll need to install Trac 0.10 or -later. Please refer to the Trac documentation for information on how it is -installed. +For the build master and web interface, you'll need to install Trac_ 0.10 or +later and the TracWebAdmin_ plugin. Please refer to the Trac documentation for +information on how it is installed. + +.. _trac: http://trac.edgewall.org/ +.. _tracwebadmin: http://trac.edgewall.org/wiki/WebAdmin Build Master Configuration @@ -53,7 +56,7 @@ If you already have a Trac project environment, the Bitten plugin needs to be explicitly enabled in the Trac configuration. This is done by adding it to the -[components] section in /path/to/projenv/conf/trac.ini: +``[components]`` section in ``/path/to/projenv/conf/trac.ini``: .. code-block:: ini @@ -76,11 +79,27 @@ You should now see an additional tab labeled "Build Status" in the Trac navigation bar. This link will take you to the list of build configurations, -which at this point is of course empty. If you've set up permissions -correctly as described previously, you should see a button for adding new -build configurations. Click that button and fill out the form. Also, add -at least one target platform after saving the configuration. Last but not -least, you'll have to "activate" the build configuration. +which at this point is of course empty. + +To add build configurations, you need to have the TracWebAdmin_ plugin +installed. + +.. warning:: The TracWebAdmin needs to be installed even if you're using Trac + 0.11 or later, which basically provides a builtin web + administration interface. Make sure that you disable the plugin in + that case, though (``webadmin.* = disabled``). While somewhat + counterintuitive, this process allows the Bitten administration UI + to neatly integrate into the new web administration interface added + in Trac 0.11. + +If both TracWebAdmin_ and Bitten are installed, and you are logged in as a user +with the required permissions, you should see additional administration pages +inside the “Admin” area, under a group named “Builds”. These pages allow you to +set options of the build master, and manage build configurations. + +Add a new build configuration and fill out the form. Also, add at least one +target platform after saving the configuration. Last but not least, you'll have +to "activate" your new build configuration. Running the Build Slave