def test_reset_orphaned_builds(self): BuildConfig(self.env, 'test').insert() platform = TargetPlatform(self.env, config='test', name='Foo') platform.insert() build1 = Build(self.env, config='test', platform=platform.id, rev=123, rev_time=42, status=Build.IN_PROGRESS, slave='heinz', started=time.time() - 600) # Started ten minutes ago build1.insert() build2 = Build(self.env, config='test', platform=platform.id, rev=124, rev_time=42, status=Build.IN_PROGRESS, slave='heinz', started=time.time() - 60) # Started a minute ago build2.insert() queue = BuildQueue(self.env, timeout=300) # 5 minutes timeout build = queue.reset_orphaned_builds() self.assertEqual(Build.PENDING, Build.fetch(self.env, build1.id).status) self.assertEqual(Build.IN_PROGRESS, Build.fetch(self.env, build2.id).status)
def test_view_config(self): config = BuildConfig(self.env, name='test', path='trunk') config.insert() platform = TargetPlatform(self.env, config='test', name='any') platform.insert() PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/test', href=Href('/trac'), args={}, chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) root = Mock(get_entries=lambda: ['foo'], get_history=lambda: [('trunk', rev, 'edit') for rev in range(123, 111, -1)]) self.repos.get_node=lambda path, rev=None: root self.repos.youngest_rev=123 self.repos.get_changeset=lambda rev: Mock(author='joe', date=99) module = BuildConfigController(self.env) assert module.match_request(req) _, data, _ = module.process_request(req) self.assertEqual('view_config', data['page_mode']) assert not 'next' in req.chrome['links'] self.assertEquals(Resource('build', 'test'), data['context'].resource) self.assertEquals([], data['config']['attachments']['attachments']) self.assertEquals('/trac/attachment/build/test/', data['config']['attachments']['attach_href'])
def test_bitten_keeps_order_of_revisions_from_versioncontrol(self): # Trac's API specifies that they are sorted chronological (backwards) # We must not assume that these revision numbers can be sorted later on, # for example the mercurial plugin will return the revisions as strings # (e.g. '880:4c19fa95fb9e') config = BuildConfig(self.env, name='test', path='trunk') config.insert() platform = TargetPlatform(self.env, config='test', name='any') platform.insert() PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/'+config.name, href=Href('/trac'), args={}, chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) # revisions are intentionally not sorted in any way - bitten should just keep them! revision_ids = [5, 8, 2] revision_list = [('trunk', revision, 'edit') for revision in revision_ids] root = Mock(get_entries=lambda: ['foo'], get_history=lambda: revision_list) self.repos.get_node=lambda path, rev=None: root self.repos.youngest_rev=5 self.repos.get_changeset=lambda rev: Mock(author='joe', date=99) module = BuildConfigController(self.env) assert module.match_request(req) _, data, _ = module.process_request(req) actual_revision_ids = data['config']['revisions'] self.assertEquals(revision_ids, actual_revision_ids)
def test_view_config_paging(self): config = BuildConfig(self.env, name='test', path='trunk') config.insert() platform = TargetPlatform(self.env, config='test', name='any') platform.insert() PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/test', href=Href('/trac'), args={}, chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) root = Mock(get_entries=lambda: ['foo'], get_history=lambda: [('trunk', rev, 'edit') for rev in range(123, 110, -1)]) self.repos = Mock(get_node=lambda path, rev=None: root, sync=lambda: None, normalize_path=lambda path: path, normalize_rev=lambda rev: rev, youngest_rev=123) self.repos.authz = Mock(has_permission=lambda path: True, assert_permission=lambda path: None) module = BuildConfigController(self.env) assert module.match_request(req) _, data, _ = module.process_request(req) if req.chrome: self.assertEqual('/trac/build/test?page=2', req.chrome['links']['next'][0]['href'])
def test_view_config_paging(self): config = BuildConfig(self.env, name='test', path='trunk') config.insert() platform = TargetPlatform(self.env, config='test', name='any') platform.insert() PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/test', href=Href('/trac'), args={}, chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) root = Mock(get_entries=lambda: ['foo'], get_history=lambda: [('trunk', rev, 'edit') for rev in range(123, 110, -1)]) self.repos = Mock(get_node=lambda path, rev=None: root, sync=lambda: None, normalize_path=lambda path: path) self.repos.authz = Mock(has_permission=lambda path: True, assert_permission=lambda path: None) module = BuildConfigController(self.env) assert module.match_request(req) _, data, _ = module.process_request(req) if req.chrome: self.assertEqual('/trac/build/test?page=2', req.chrome['links']['next'][0]['href'])
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.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected TracError') except TracError, e: self.assertEqual('No platform selected', e.message)
def should_delete_build(self, build, repos): # Ignore pending builds for deactived build configs config = BuildConfig.fetch(self.env, build.config) if not config.active: target_platform = TargetPlatform.fetch(self.env, build.platform) if target_platform: target_platform_name = '"%s"' % (target_platform.name,) else: target_platform_name = 'unknown platform "%s"' % (build.platform,) log.info('Dropping build of configuration "%s" at ' 'revision [%s] on %s because the configuration is ' 'deactivated', config.name, build.rev, target_platform_name) return True # Stay within the revision limits of the build config if (config.min_rev and repos.rev_older_than(build.rev, config.min_rev)) \ or (config.max_rev and repos.rev_older_than(config.max_rev, build.rev)): # This minimum and/or maximum revision has changed since # this build was enqueued, so drop it log.info('Dropping build of configuration "%s" at revision [%s] on ' '"%s" because it is outside of the revision range of the ' 'configuration', config.name, build.rev, TargetPlatform.fetch(self.env, build.platform).name) return True return False
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() req = Mock(method='GET', chrome={}, href=Href('/'), perm=PermissionCache(self.env, 'joe'), args={}) provider = BuildConfigurationsAdminPageProvider(self.env) template_name, data = provider.render_admin_panel( req, 'bitten', 'configs', 'foo/%d' % platform.id) self.assertEqual('bitten_admin_configs.html', template_name) assert 'platform' in data platform = data['platform'] self.assertEqual( { 'id': 1, 'exists': True, 'name': 'any', 'rules': [('', '')], }, platform)
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', chrome={'warnings': [], 'notices': []}, href=Href('/'), args={'remove': '', 'sel': str(platform.id)}) provider = BuildConfigurationsAdminPageProvider(self.env) try: provider.render_admin_panel(req, 'bitten', 'configs', 'foo') self.fail('Expected RequestDone') except RequestDone: self.assertEquals(['Target Platform(s) Removed.'], req.chrome['notices']) 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_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.render_admin_panel(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 test_should_delete_build_outside_revision_range(self): messages = [] self.env.log = Mock(info=lambda msg, *args: messages.append(msg)) self.repos.rev_older_than = lambda rev1, rev2: rev1 < rev2 config = BuildConfig(self.env, 'test', active=True, min_rev=120, max_rev=123) config.insert() platform = TargetPlatform(self.env, config='test', name='stuff') platform.insert() build1 = Build(self.env, config=config.name, rev=42, platform=platform.id, rev_time=123456) build1.insert() build2 = Build(self.env, config=config.name, rev=10042, platform=platform.id, rev_time=123456) build2.insert() queue = BuildQueue(self.env, build_all=True) self.assertEqual(True, queue.should_delete_build(build1, self.repos)) self.assertEqual(True, queue.should_delete_build(build2, self.repos)) self.assert_("outside of the revision range" in messages[0]) self.assert_("outside of the revision range" in messages[1])
def setUp(self): self.env = EnvironmentStub(enable=['trac.*', 'bitten.*']) self.env.path = tempfile.mkdtemp() db = self.env.get_db_cnx() cursor = db.cursor() connector, _ = DatabaseManager(self.env)._get_connector() for table in schema: for stmt in connector.to_sql(table): cursor.execute(stmt) self.config = BuildConfig(self.env, name='test', path='somepath') self.config.insert(db=db) self.platform = TargetPlatform(self.env, config='test', name='Foo') self.platform.insert(db=db) db.commit() # Hook up a dummy repository self.repos = Mock() self.env.get_repository = lambda authname=None: self.repos # 0.11 try: # 0.12+ from trac.core import Component, implements from trac.versioncontrol.api import IRepositoryConnector, \ IRepositoryProvider class DummyRepos(Component): implements(IRepositoryConnector, IRepositoryProvider) def get_supported_types(self): yield ('dummy', 9) def get_repository(this, repos_type, repos_dir, params): return self.repos # Note: 'this' vs 'self' usage def get_repositories(self): yield ('', {'dir': 'dummy_dir', 'type': 'dummy'}) self.dummy = DummyRepos except ImportError: self.dummy = None # not supported, will use get_repository()
def test_register_slave_match_regexp_invalid(self): BuildConfig(self.env, 'test', active=True).insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('version', '8(\.\d')) platform.insert() queue = BuildQueue(self.env) platforms = queue.match_slave('foo', {'version': '7.8.1'}) self.assertEqual([], platforms)
def test_register_slave_match_simple_fail(self): BuildConfig(self.env, 'test', active=True).insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('family', 'posix')) platform.insert() queue = BuildQueue(self.env) platforms = queue.match_slave('foo', {'family': 'nt'}) self.assertEqual([], platforms)
def _create_platform(self, req, config_name): req.perm.assert_permission('BUILD_MODIFY') name = req.args.get('platform_name') if not name: raise TracError('Missing required field "name"', 'Missing field') platform = TargetPlatform(self.env, config=config_name, name=name) platform.insert() return platform
def test_initiate_build(self): config = BuildConfig(self.env, 'test', path='somepath', active=True, recipe='<build><step id="s1"></step></build>') config.insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('family', 'posix')) platform.insert() build = Build(self.env, 'test', '123', platform.id, slave='hal', rev_time=42) build.insert() outheaders = {} outbody = StringIO() req = Mock(method='GET', base_path='', path_info='/builds/%d' % build.id, href=Href('/trac'), remote_addr='127.0.0.1', args={}, authname='hal', perm=PermissionCache(self.env, 'hal'), send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, form_token="12345", incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(200, outheaders['Status']) self.assertEqual('163', outheaders['Content-Length']) self.assertEqual('application/x-bitten+xml', outheaders['Content-Type']) self.assertEqual('attachment; filename=recipe_test_r123.xml', outheaders['Content-Disposition']) self.assertEqual( '<build build="1" config="test" form_token="12345" ' 'name="hal" path="somepath" platform="Unix" ' 'reponame="" repopath="somepath" ' 'revision="123"><step id="s1"/></build>', outbody.getvalue()) # Make sure the started timestamp has been set build = Build.fetch(self.env, build.id) assert build.started
def test_register_slave_match_regexp(self): BuildConfig(self.env, 'test', active=True).insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('version', '8\.\d\.\d')) platform.insert() platform_id = platform.id queue = BuildQueue(self.env) platforms = queue.match_slave('foo', {'version': '8.2.0'}) self.assertEqual(1, len(platforms)) self.assertEqual(platform_id, platforms[0].id)
def test_match_slave_match(self): BuildConfig(self.env, 'test', active=True).insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('family', 'posix')) platform.insert() platform_id = platform.id queue = BuildQueue(self.env) platforms = queue.match_slave('foo', {'family': 'posix'}) self.assertEqual(1, len(platforms)) self.assertEqual(platform_id, platforms[0].id)
def test_delete_platform_with_pending_builds(self): """Check that deleting a platform with pending builds removes those pending builds""" db = self.env.get_db_cnx() platform = TargetPlatform(self.env, config='test', name='Linux') platform.insert() build = Build(self.env, config='test', platform=platform.id, rev='42', rev_time=12039) build.insert() platform.delete() pending = list(build.select(self.env, config='test', status=Build.PENDING)) self.assertEqual(0, len(pending))
def test_register_slave_match_case_insensitive(self): BuildConfig(self.env, 'test', active=True).insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('os', 'LiNUX')) platform.insert() platform_id = platform.id queue = BuildQueue(self.env) platforms = queue.match_slave('foo', {'os': 'linux'}) self.assertEqual(1, len(platforms)) self.assertEqual(platform_id, platforms[0].id)
def test_register_slave_match_regexp_multi(self): BuildConfig(self.env, 'test', active=True).insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('os', '^Linux')) platform.rules.append(('processor', '^[xi]\d?86$')) platform.insert() platform_id = platform.id queue = BuildQueue(self.env) platforms = queue.match_slave('foo', {'os': 'Linux', 'processor': 'i686'}) self.assertEqual(1, len(platforms)) self.assertEqual(platform_id, platforms[0].id)
def test_create_build(self): BuildConfig(self.env, 'test', path='somepath', active=True).insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('family', 'posix')) platform.insert() self.repos = Mock( get_node=lambda path, rev=None: Mock( get_entries=lambda: [Mock(), Mock()], get_history=lambda: [('somepath', 123, 'edit'), ('somepath', 121, 'edit'), ('somepath', 120, 'edit')]), get_changeset=lambda rev: Mock(date=to_datetime(42, utc)), normalize_path=lambda path: path, rev_older_than=lambda rev1, rev2: rev1 < rev2) inheaders = {'Content-Type': 'application/x-bitten+xml'} inbody = StringIO("""<slave name="hal" version="%d"> <platform>Power Macintosh</platform> <os family="posix" version="8.1.0">Darwin</os> <package name="java" version="2.4.3"/> </slave>""" % PROTOCOL_VERSION) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds', href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), get_header=lambda x: inheaders.get(x), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(201, outheaders['Status']) self.assertEqual('text/plain', outheaders['Content-Type']) location = outheaders['Location'] mo = re.match('http://example.org/trac/builds/(\d+)', location) assert mo, 'Location was %r' % location self.assertEqual('Build pending', outbody.getvalue()) build = Build.fetch(self.env, int(mo.group(1))) self.assertEqual(Build.IN_PROGRESS, build.status) self.assertEqual('hal', build.slave)
def test_should_delete_build_config_deactivated(self): messages = [] self.env.log = Mock(info=lambda msg, *args: messages.append(msg)) config = BuildConfig(self.env, 'test', active=False) config.insert() platform = TargetPlatform(self.env, config='test', name='stuff') platform.insert() build = Build(self.env, config=config.name, rev=42, platform=platform.id, rev_time=123456) build.insert() queue = BuildQueue(self.env, build_all=True) self.assertEqual(True, queue.should_delete_build(build, self.repos)) self.assert_("configuration is deactivated" in messages[0])
def test_next_pending_build_inactive_config(self): """ Make sure that builds for a deactived build config are not scheduled. """ BuildConfig(self.env, 'test').insert() platform = TargetPlatform(self.env, config='test', name='Foo') platform.insert() build = Build(self.env, config='test', platform=platform.id, rev=123, rev_time=42, status=Build.PENDING) build.insert() queue = BuildQueue(self.env) build = queue.get_build_for_slave('foobar', {}) self.assertEqual(None, build)
def test_should_delete_build_config_none(self): out = [] self.env.log = Mock( info=lambda msg, *args: out.extend([msg] + list(args))) platform = TargetPlatform(self.env, config='test', name='stuff') platform.insert() build = Build(self.env, config='does_not_exist', rev=42, platform=platform.id, rev_time=123456) build.insert() queue = BuildQueue(self.env, build_all=True) self.assertEqual(True, queue.should_delete_build(build, self.repos)) self.assertTrue("configuration is deactivated" in out[0]) self.assertEquals('unknown config "does_not_exist"', out[1])
def test_populate_not_build_all(self): self.env.get_repository = lambda authname=None: Mock( get_changeset=lambda rev: Mock(date=to_datetime(rev * 1000, utc)), get_node=lambda path, rev=None: Mock( get_entries=lambda: [Mock(), Mock()], get_history=lambda: [('somepath', 123, 'edit'), ('somepath', 121, 'edit'), ('somepath', 120, 'edit')] ), normalize_path=lambda path: path, rev_older_than=lambda rev1, rev2: rev1 < rev2 ) BuildConfig(self.env, 'test', path='somepath', active=True).insert() platform1 = TargetPlatform(self.env, config='test', name='P1') platform1.insert() platform2 = TargetPlatform(self.env, config='test', name='P2') platform2.insert() queue = BuildQueue(self.env) queue.populate() queue.populate() queue.populate() builds = list(Build.select(self.env, config='test')) builds.sort(lambda a, b: cmp(a.platform, b.platform)) self.assertEqual(2, len(builds)) self.assertEqual(platform1.id, builds[0].platform) self.assertEqual('123', builds[0].rev) self.assertEqual(platform2.id, builds[1].platform) self.assertEqual('123', builds[1].rev)
def test_create_build(self): BuildConfig(self.env, 'test', path='somepath', active=True).insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('family', 'posix')) platform.insert() self.repos = Mock( get_node=lambda path, rev=None: Mock( get_entries=lambda: [Mock(), Mock()], get_history=lambda: [('somepath', 123, 'edit'), ('somepath', 121, 'edit'), ('somepath', 120, 'edit')] ), get_changeset=lambda rev: Mock(date=to_datetime(42, utc)), normalize_path=lambda path: path, rev_older_than=lambda rev1, rev2: rev1 < rev2 ) inheaders = {'Content-Type': 'application/x-bitten+xml'} inbody = StringIO("""<slave name="hal" version="%d"> <platform>Power Macintosh</platform> <os family="posix" version="8.1.0">Darwin</os> <package name="java" version="2.4.3"/> </slave>""" % PROTOCOL_VERSION) outheaders = {} outbody = StringIO() req = Mock(method='POST', base_path='', path_info='/builds', href=Href('/trac'), abs_href=Href('http://example.org/trac'), remote_addr='127.0.0.1', args={}, perm=PermissionCache(self.env, 'hal'), get_header=lambda x: inheaders.get(x), read=inbody.read, send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(201, outheaders['Status']) self.assertEqual('text/plain', outheaders['Content-Type']) location = outheaders['Location'] mo = re.match('http://example.org/trac/builds/(\d+)', location) assert mo, 'Location was %r' % location self.assertEqual('Build pending', outbody.getvalue()) build = Build.fetch(self.env, int(mo.group(1))) self.assertEqual(Build.IN_PROGRESS, build.status) self.assertEqual('hal', build.slave)
def test_view_build(self): config = BuildConfig(self.env, name='test', path='trunk') config.insert() platform = TargetPlatform(self.env, config='test', name='any') platform.insert() build = Build(self.env, config='test', platform=1, rev=123, rev_time=42, status=Build.SUCCESS, slave='hal') build.insert() PermissionSystem(self.env).grant_permission('joe', 'BUILD_VIEW') req = Mock(method='GET', base_path='', cgi_location='', path_info='/build/test/1', href=Href('/trac'), args={}, chrome={}, authname='joe', perm=PermissionCache(self.env, 'joe')) root = Mock(get_entries=lambda: ['foo'], get_history=lambda: [('trunk', rev, 'edit') for rev in range(123, 111, -1)]) self.repos = Mock(get_node=lambda path, rev=None: root, sync=lambda: None, normalize_path=lambda path: path, normalize_rev=lambda rev: rev, get_changeset=lambda rev: Mock(author='joe')) self.repos.authz = Mock(has_permission=lambda path: True, assert_permission=lambda path: None) module = BuildController(self.env) assert module.match_request(req) _, data, _ = module.process_request(req) self.assertEqual('view_build', data['page_mode']) from trac.resource import Resource self.assertEquals(Resource('build', 'test/1'), data['context'].resource) self.assertEquals([], data['build']['attachments']['attachments']) self.assertEquals('/trac/attachment/build/test/1/', data['build']['attachments']['attach_href'])
def _process_build_initiation(self, req, config, build): self.log.info('Build slave %r initiated build %d', build.slave, build.id) build.started = int(time.time()) build.update() for listener in BuildSystem(self.env).listeners: listener.build_started(build) xml = xmlio.parse(config.recipe) xml.attr['branch'] = config.branch xml.attr['revision'] = build.rev xml.attr['config'] = config.name xml.attr['build'] = str(build.id) target_platform = TargetPlatform.fetch(self.env, build.platform) xml.attr['platform'] = target_platform.name xml.attr['name'] = build.slave body = str(xml) self.log.info('Build slave %r initiated build %d', build.slave, build.id) self._send_response(req, 200, body, headers={ 'Content-Type': 'application/x-bitten+xml', 'Content-Length': str(len(body)), 'Content-Disposition': 'attachment; filename=recipe_%s_r%s.xml' % (config.name, build.rev) })
def setUp(self): self.env = EnvironmentStub() self.env.path = tempfile.mkdtemp() db = self.env.get_db_cnx() cursor = db.cursor() connector, _ = DatabaseManager(self.env)._get_connector() for table in schema: for stmt in connector.to_sql(table): cursor.execute(stmt) self.config = BuildConfig(self.env, name='test', path='somepath') self.config.insert(db=db) self.platform = TargetPlatform(self.env, config='test', name='Foo') self.platform.insert(db=db) db.commit()
def test_get_build_for_slave(self): """ Make sure that a pending build of an activated configuration is scheduled for a slave that matches the target platform. """ BuildConfig(self.env, 'test', active=True).insert() platform = TargetPlatform(self.env, config='test', name='Foo') platform.insert() build = Build(self.env, config='test', platform=platform.id, rev=123, rev_time=42, status=Build.PENDING) build.insert() build_id = build.id queue = BuildQueue(self.env) build = queue.get_build_for_slave('foobar', {}) self.assertEqual(build_id, build.id)
def _get_build_data(env, req, build): platform = TargetPlatform.fetch(env, build.platform) data = { 'id': build.id, 'name': build.slave, 'rev': build.rev, 'status': _status_label[build.status], 'platform': getattr(platform, 'name', 'unknown'), 'cls': _status_label[build.status].replace(' ', '-'), 'href': req.href.build(build.config, build.id), 'chgset_href': req.href.changeset(build.rev) } if build.started: data['started'] = format_datetime(build.started) data['started_delta'] = pretty_timedelta(build.started) data['duration'] = pretty_timedelta(build.started) if build.stopped: data['stopped'] = format_datetime(build.stopped) data['stopped_delta'] = pretty_timedelta(build.stopped) data['duration'] = pretty_timedelta(build.stopped, build.started) data['slave'] = { 'name': build.slave, 'ipnr': build.slave_info.get(Build.IP_ADDRESS), 'os_name': build.slave_info.get(Build.OS_NAME), 'os_family': build.slave_info.get(Build.OS_FAMILY), 'os_version': build.slave_info.get(Build.OS_VERSION), 'machine': build.slave_info.get(Build.MACHINE), 'processor': build.slave_info.get(Build.PROCESSOR) } return data
def template_data(self): failed_steps = BuildStep.select(self.env, build=self.build.id, status=BuildStep.FAILURE) platform = TargetPlatform.fetch(self.env, id=self.build.platform) reposname, repos, change = self.get_change() return { 'build': { 'id': self.build.id, 'status': self.readable_states[self.build.status], 'link': self.build_link(), 'config': self.build.config, 'platform': getattr(platform, 'name', 'unknown'), 'slave': self.build.slave, 'failed_steps': [{ 'name': step.name, 'description': step.description, 'errors': step.errors, 'log_messages': self.get_all_log_messages_for_step(step), } for step in failed_steps], }, 'change': { 'rev': display_rev(repos, change.rev), 'link': self.env.abs_href.changeset(change.rev, reposname != '(default)' and reposname or None), 'author': change.author, }, }
def _process_build_initiation(self, req, config, build): self.log.info('Build slave %r initiated build %d', build.slave, build.id) build.started = int(time.time()) build.update() for listener in BuildSystem(self.env).listeners: listener.build_started(build) xml = xmlio.parse(config.recipe) xml.attr['path'] = config.path xml.attr['revision'] = build.rev xml.attr['config'] = config.name xml.attr['build'] = str(build.id) target_platform = TargetPlatform.fetch(self.env, build.platform) xml.attr['platform'] = target_platform.name body = str(xml) self.log.info('Build slave %r initiated build %d', build.slave, build.id) req.send_response(200) req.send_header('Content-Type', 'application/x-bitten+xml') req.send_header('Content-Length', str(len(body))) req.send_header('Content-Disposition', 'attachment; filename=recipe_%s_r%s.xml' % (config.name, build.rev)) req.write(body) raise RequestDone
def test_insert(self): platform = TargetPlatform(self.env, config='test', name='Windows XP') platform.rules += [(Build.OS_NAME, 'Windows'), (Build.OS_VERSION, 'XP')] platform.insert() assert platform.exists db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT config,name FROM bitten_platform " "WHERE id=%s", (platform.id,)) self.assertEqual(('test', 'Windows XP'), cursor.fetchone()) cursor.execute("SELECT propname,pattern,orderno FROM bitten_rule " "WHERE id=%s", (platform.id,)) self.assertEqual((Build.OS_NAME, 'Windows', 0), cursor.fetchone()) self.assertEqual((Build.OS_VERSION, 'XP', 1), cursor.fetchone())
def _process_build_initiation(self, req, config, build): self.log.info('Build slave %r initiated build %d', build.slave, build.id) build.started = int(time.time()) build.update() for listener in BuildSystem(self.env).listeners: listener.build_started(build) xml = xmlio.parse(config.recipe) xml.attr['branch'] = config.branch xml.attr['revision'] = build.rev xml.attr['config'] = config.name xml.attr['build'] = str(build.id) target_platform = TargetPlatform.fetch(self.env, build.platform) xml.attr['platform'] = target_platform.name xml.attr['name'] = build.slave body = str(xml) self.log.info('Build slave %r initiated build %d', build.slave, build.id) self._send_response(req, 200, body, headers={ 'Content-Type': 'application/x-bitten+xml', 'Content-Length': str(len(body)), 'Content-Disposition': 'attachment; filename=recipe_%s_r%s.xml' % (config.name, build.rev)})
def _get_build_data(env, req, build): platform = TargetPlatform.fetch(env, build.platform) data = {'id': build.id, 'name': build.slave, 'rev': build.rev, 'status': _status_label[build.status], 'platform': getattr(platform, 'name', 'unknown'), 'cls': _status_label[build.status].replace(' ', '-'), 'href': req.href.build(build.config, build.id), 'chgset_href': req.href.changeset(build.rev)} if build.started: data['started'] = format_datetime(build.started) data['started_delta'] = pretty_timedelta(build.started) data['duration'] = pretty_timedelta(build.started) if build.stopped: data['stopped'] = format_datetime(build.stopped) data['stopped_delta'] = pretty_timedelta(build.stopped) data['duration'] = pretty_timedelta(build.stopped, build.started) data['slave'] = { 'name': build.slave, 'ipnr': build.slave_info.get(Build.IP_ADDRESS), 'os_name': build.slave_info.get(Build.OS_NAME), 'os_family': build.slave_info.get(Build.OS_FAMILY), 'os_version': build.slave_info.get(Build.OS_VERSION), 'machine': build.slave_info.get(Build.MACHINE), 'processor': build.slave_info.get(Build.PROCESSOR) } return data
def should_delete_build(self, build, repos): config = BuildConfig.fetch(self.env, build.config) platform = TargetPlatform.fetch(self.env, build.platform) # Platform may or may not exist anymore - get safe name for logging platform_name = platform and platform.name \ or 'unknown platform "%s"' % build.platform # Drop build if platform no longer exists if not platform: self.log.info( 'Dropping build of configuration "%s" at ' 'revision [%s] on %s because the platform no longer ' 'exists', config.name, build.rev, platform_name) return True # Ignore pending builds for deactived build configs if not config.active: self.log.info( 'Dropping build of configuration "%s" at ' 'revision [%s] on %s because the configuration is ' 'deactivated', config.name, build.rev, platform_name) return True # If not 'build_all', drop if a more recent revision is available if not self.build_all and \ len(list(Build.select(self.env, config=build.config, min_rev_time=build.rev_time, platform=build.platform))) > 1: self.log.info( 'Dropping build of configuration "%s" at revision [%s] ' 'on "%s" because a more recent build exists', config.name, build.rev, platform_name) return True return False
def should_delete_build(self, build, repos): config = BuildConfig.fetch(self.env, build.config) platform = TargetPlatform.fetch(self.env, build.platform) # Platform may or may not exist anymore - get safe name for logging platform_name = platform and platform.name \ or 'unknown platform "%s"' % build.platform # Drop build if platform no longer exists if not platform: self.log.info('Dropping build of configuration "%s" at ' 'revision [%s] on %s because the platform no longer ' 'exists', config.name, build.rev, platform_name) return True # Ignore pending builds for deactived build configs if not config.active: self.log.info('Dropping build of configuration "%s" at ' 'revision [%s] on %s because the configuration is ' 'deactivated', config.name, build.rev, platform_name) return True # If not 'build_all', drop if a more recent revision is available if not self.build_all and \ len(list(Build.select(self.env, config=build.config, min_rev_time=build.rev_time, platform=build.platform))) > 1: self.log.info('Dropping build of configuration "%s" at revision [%s] ' 'on "%s" because a more recent build exists', config.name, build.rev, platform_name) return True return False
def test_select(self): db = self.env.get_db_cnx() cursor = db.cursor() cursor.executemany("INSERT INTO bitten_platform (config,name) " "VALUES (%s,%s)", [('test', 'Windows'), ('test', 'Mac OS X')]) platforms = list(TargetPlatform.select(self.env, config='test')) self.assertEqual(2, len(platforms))
def test_populate_thread_race_condition(self): messages = [] self.env.log = Mock(info=lambda msg, *args: messages.append(msg)) def get_history(): yield ('somepath', 123, 'edit') yield ('somepath', 121, 'edit') yield ('somepath', 120, 'edit') time.sleep(1) # sleep to make sure both threads collect self.env.get_repository = lambda authname=None: Mock( get_changeset=lambda rev: Mock(date=to_datetime(rev * 1000, utc)), get_node=lambda path, rev=None: Mock( get_entries=lambda: [Mock(), Mock()], get_history=get_history), normalize_path=lambda path: path, rev_older_than=lambda rev1, rev2: rev1 < rev2) BuildConfig(self.env, 'test', path='somepath', active=True).insert() platform1 = TargetPlatform(self.env, config='test', name='P1') platform1.insert() platform2 = TargetPlatform(self.env, config='test', name='P2') platform2.insert() def build_populator(): queue = BuildQueue(self.env, build_all=True) queue.populate() thread1 = threading.Thread(target=build_populator) thread2 = threading.Thread(target=build_populator) thread1.start() thread2.start() thread1.join() thread2.join() # check builds got added builds = list(Build.select(self.env, config='test')) builds.sort(lambda a, b: cmp(a.platform, b.platform)) self.assertEqual(6, len(builds)) self.assertEqual(platform1.id, builds[0].platform) self.assertEqual('123', builds[0].rev) self.assertEqual(platform1.id, builds[1].platform) self.assertEqual('121', builds[1].rev) self.assertEqual(platform1.id, builds[2].platform) self.assertEqual('120', builds[2].rev) self.assertEqual(platform2.id, builds[3].platform) self.assertEqual('123', builds[3].rev) self.assertEqual(platform2.id, builds[4].platform) self.assertEqual('121', builds[4].rev) self.assertEqual(platform2.id, builds[5].platform) self.assertEqual('120', builds[5].rev) # check attempts at duplicate inserts were logged. failure_messages = [ x for x in messages if x.startswith('Failed to insert build') ] self.assertEqual(6, len(failure_messages))
def test_should_delete_build_old_with_not_buildall(self): messages = [] self.env.log = Mock(info=lambda msg, *args: messages.append(msg)) config = BuildConfig(self.env, 'test', active=True) config.insert() platform = TargetPlatform(self.env, config='test', name='stuff') platform.insert() build1 = Build(self.env, config=config.name, rev=42, platform=platform.id, rev_time=123456) build1.insert() build2 = Build(self.env, config=config.name, rev=43, platform=platform.id, rev_time=123457, slave='slave') build2.insert() queue = BuildQueue(self.env, build_all=False) self.assertEqual(True, queue.should_delete_build(build1, self.repos)) self.assert_("more recent build exists" in messages[0])
def test_initiate_build(self): config = BuildConfig(self.env, 'test', path='somepath', active=True, recipe='<build><step id="s1"></step></build>') config.insert() platform = TargetPlatform(self.env, config='test', name="Unix") platform.rules.append(('family', 'posix')) platform.insert() build = Build(self.env, 'test', '123', platform.id, slave='hal', rev_time=42) build.insert() outheaders = {} outbody = StringIO() req = Mock(method='GET', base_path='', path_info='/builds/%d' % build.id, href=Href('/trac'), remote_addr='127.0.0.1', args={}, authname='hal', perm=PermissionCache(self.env, 'hal'), send_response=lambda x: outheaders.setdefault('Status', x), send_header=lambda x, y: outheaders.setdefault(x, y), write=outbody.write, form_token="12345", incookie=Cookie('trac_auth=')) module = BuildMaster(self.env) assert module.match_request(req) self.assertRaises(RequestDone, module.process_request, req) self.assertEqual(200, outheaders['Status']) self.assertEqual('163', outheaders['Content-Length']) self.assertEqual('application/x-bitten+xml', outheaders['Content-Type']) self.assertEqual('attachment; filename=recipe_test_r123.xml', outheaders['Content-Disposition']) self.assertEqual('<build build="1" config="test" form_token="12345" ' 'name="hal" path="somepath" platform="Unix" ' 'reponame="" repopath="somepath" ' 'revision="123"><step id="s1"/></build>', outbody.getvalue()) # Make sure the started timestamp has been set build = Build.fetch(self.env, build.id) assert build.started
def test_fetch(self): db = self.env.get_db_cnx() cursor = db.cursor() cursor.execute("INSERT INTO bitten_platform (config,name) " "VALUES (%s,%s)", ('test', 'Windows')) id = db.get_last_id(cursor, 'bitten_platform') platform = TargetPlatform.fetch(self.env, id) assert platform.exists self.assertEqual('test', platform.config) self.assertEqual('Windows', platform.name)