class TestReviewBotComment(OBSLocal.TestCase): def setUp(self): super(TestReviewBotComment, self).setUp() self.api = CommentAPI(self.apiurl) self.wf = OBSLocal.FactoryWorkflow() self.wf.create_user('factory-auto') self.project = self.wf.create_project(PROJECT) # Ensure different test runs operate in unique namespace. self.bot = '::'.join([type(self).__name__, str(random.getrandbits(8))]) self.review_bot = ReviewBot(self.apiurl, logger=logging.getLogger(self.bot)) self.review_bot.bot_name = self.bot self.osc_user('factory-auto') def tearDown(self): self.api.delete_from(project_name=PROJECT) self.assertFalse(len(self.api.get_comments(project_name=PROJECT))) self.osc_user('Admin') del self.wf def test_basic_logger(self): self.assertFalse(self.comments_filtered(self.bot)[0]) # Initial comment. self.review_bot.comment_handler_add() self.review_bot.logger.info('something interesting') self.review_bot.comment_write(project=PROJECT) comment, _ = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith('something interesting')) # Second comment with extra line. self.review_bot.comment_handler_add() self.review_bot.logger.info('something interesting') self.review_bot.logger.info('something extra') self.review_bot.comment_write(project=PROJECT) comment, _ = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith('something extra')) def test_workflow(self): comment_count = len(self.api.get_comments(project_name=PROJECT)) self.assertFalse(self.comments_filtered(self.bot)[0]) # Initial comment. info = {'state': 'seen', 'result': 'failed'} info_extra = {'build': '1'} info_merged = info.copy() info_merged.update(info_extra) self.review_bot.comment_write(state='seen', result='failed', identical=True, info_extra=info_extra, info_extra_identical=False, project=PROJECT, message=COMMENT) comment, info_parsed = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith(COMMENT)) self.assertEqual(info_parsed, info_merged) # Only build change (expect no change). info_extra = {'build': '2'} self.review_bot.comment_write(state='seen', result='failed', identical=True, info_extra=info_extra, info_extra_identical=False, project=PROJECT, message=COMMENT) comment, info_parsed = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith(COMMENT)) self.assertEqual(info_parsed, info_merged) # Build and comment (except comment replacement). info_extra = {'build': '3'} info_merged.update(info_extra) self.review_bot.comment_write(state='seen', result='failed', identical=True, info_extra=info_extra, info_extra_identical=False, project=PROJECT, message=COMMENT + '3') comment, info_parsed = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith(COMMENT + '3')) self.assertEqual(info_parsed, info_merged) # Final build (except comment replacement). info_extra = {'build': '4'} info_merged.update(info_extra) self.review_bot.comment_write(state='seen', result='failed', identical=True, info_extra=info_extra, info_extra_identical=True, project=PROJECT, message=COMMENT + '4') comment, info_parsed = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith(COMMENT + '4')) self.assertEqual(info_parsed, info_merged) # Final build (except comment replacement). info = {'state': 'done', 'result': 'passed'} info_extra = {'build': '5'} info_merged = info.copy() info_merged.update(info_extra) self.review_bot.comment_write(state='done', result='passed', identical=True, info_extra=info_extra, info_extra_identical=True, only_replace=True, project=PROJECT, message=COMMENT + '5') comment, info_parsed = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith(COMMENT + '5')) self.assertEqual(info_parsed, info_merged) # Should never be more than one new comment. self.assertEqual(len(self.api.get_comments(project_name=PROJECT)), comment_count + 1) def test_only_replace_none(self): self.review_bot.comment_write(only_replace=True, project=PROJECT, message=COMMENT) self.assertFalse(self.comments_filtered(self.bot)[0]) def test_dryrun(self): # dryrun = True, no comment. self.review_bot.dryrun = True self.review_bot.comment_write(project=PROJECT, message=COMMENT) self.assertFalse(self.comments_filtered(self.bot)[0]) # dryrun = False, a comment. self.review_bot.dryrun = False self.review_bot.comment_write(project=PROJECT, message=COMMENT) self.assertTrue(self.comments_filtered(self.bot)[0]) # dryrun = True, no replacement. self.review_bot.dryrun = True self.review_bot.comment_write(state='changed', project=PROJECT, message=COMMENT) _, info = self.comments_filtered(self.bot) self.assertEqual(info['state'], 'done') # dryrun = False, replacement. self.review_bot.dryrun = False self.review_bot.comment_write(state='changed', project=PROJECT, message=COMMENT) _, info = self.comments_filtered(self.bot) self.assertEqual(info['state'], 'changed') def test_bot_name_suffix(self): suffix1 = 'suffix1' bot_suffixed1 = '::'.join([self.bot, suffix1]) suffix2 = 'suffix2' bot_suffixed2 = '::'.join([self.bot, suffix2]) self.review_bot.comment_write(bot_name_suffix=suffix1, project=PROJECT, message=COMMENT) self.assertFalse(self.comments_filtered(self.bot)[0]) self.assertTrue(self.comments_filtered(bot_suffixed1)[0]) self.assertFalse(self.comments_filtered(bot_suffixed2)[0]) self.review_bot.comment_write(bot_name_suffix=suffix2, project=PROJECT, message=COMMENT) self.assertFalse(self.comments_filtered(self.bot)[0]) self.assertTrue(self.comments_filtered(bot_suffixed1)[0]) self.assertTrue(self.comments_filtered(bot_suffixed2)[0]) self.review_bot.comment_write(bot_name_suffix=suffix1, project=PROJECT, message=COMMENT + '\nnew') comment, _ = self.comments_filtered(bot_suffixed1) self.assertTrue(comment['comment'].endswith(COMMENT + '\nnew')) comment, _ = self.comments_filtered(bot_suffixed2) self.assertTrue(comment['comment'].endswith(COMMENT)) def comments_filtered(self, bot): comments = self.api.get_comments(project_name=PROJECT) return self.api.comment_find(comments, bot)
class TestCommentOBS(OBSLocal.TestCase): def setUp(self): super(TestCommentOBS, self).setUp() self.wf = OBSLocal.FactoryWorkflow() self.wf.create_user('factory-auto') self.wf.create_user('repo-checker') self.wf.create_user('staging-bot') self.wf.create_group('factory-staging', ['staging-bot']) self.wf.create_project(PROJECT, maintainer={'groups': ['factory-staging']}) self.api = CommentAPI(self.apiurl) # Ensure different test runs operate in unique namespace. self.bot = '::'.join([type(self).__name__, str(random.getrandbits(8))]) def tearDown(self): self.osc_user('Admin') del self.wf def test_basic(self): self.osc_user('staging-bot') self.assertFalse(self.comments_filtered(self.bot)[0]) self.assertTrue( self.api.add_comment(project_name=PROJECT, comment=self.api.add_marker( COMMENT, self.bot))) comment, _ = self.comments_filtered(self.bot) self.assertTrue(comment) self.assertTrue(self.api.delete(comment['id'])) self.assertFalse(self.comments_filtered(self.bot)[0]) def test_delete_nested(self): self.osc_user('staging-bot') comment_marked = self.api.add_marker(COMMENT, self.bot) # Allow for existing comments by basing assertion on delta from initial count. comment_count = len(self.api.get_comments(project_name=PROJECT)) self.assertFalse(self.comments_filtered(self.bot)[0]) self.assertTrue( self.api.add_comment(project_name=PROJECT, comment=comment_marked)) comment, _ = self.comments_filtered(self.bot) self.assertTrue(comment) for i in range(0, 3): self.assertTrue( self.api.add_comment(project_name=PROJECT, comment=comment_marked, parent_id=comment['id'])) comments = self.api.get_comments(project_name=PROJECT) parented_count = 0 for comment in comments.values(): if comment['parent']: parented_count += 1 self.assertEqual(parented_count, 3) self.assertTrue(len(comments) == comment_count + 4) self.api.delete_from(project_name=PROJECT) self.assertFalse(len(self.api.get_comments(project_name=PROJECT))) def test_delete_batch(self): users = ['factory-auto', 'repo-checker', 'staging-bot'] for user in users: self.osc_user(user) print('logged in as ', user) bot = '::'.join([self.bot, user]) comment = self.api.add_marker(COMMENT, bot) self.assertFalse(self.comments_filtered(bot)[0]) self.assertTrue( self.api.add_comment(project_name=PROJECT, comment=comment)) self.assertTrue(self.comments_filtered(bot)[0]) # Allow for existing comments by basing assertion on delta from initial count. comment_count = len(self.api.get_comments(project_name=PROJECT)) self.assertTrue(comment_count >= len(users)) self.api.delete_from_where_user(users[0], project_name=PROJECT) self.assertTrue( len(self.api.get_comments(project_name=PROJECT)) == comment_count - 1) self.api.delete_from(project_name=PROJECT) self.assertFalse(len(self.api.get_comments(project_name=PROJECT))) def comments_filtered(self, bot): comments = self.api.get_comments(project_name=PROJECT) return self.api.comment_find(comments, bot)
class AcceptCommand(object): def __init__(self, api): self.api = api self.comment = CommentAPI(self.api.apiurl) def find_new_requests(self, project): query = "match=state/@name='new'+and+(action/target/@project='{}'+and+action/@type='submit')".format(project) url = self.api.makeurl(['search', 'request'], query) f = http_GET(url) root = ET.parse(f).getroot() rqs = [] for rq in root.findall('request'): pkgs = [] actions = rq.findall('action') for action in actions: targets = action.findall('target') for t in targets: pkgs.append(str(t.get('package'))) rqs.append({'id': int(rq.get('id')), 'packages': pkgs}) return rqs def perform(self, project, force=False): """Accept the staging project for review and submit to Factory / openSUSE 13.2 ... Then disable the build to disabled :param project: staging project we are working with """ status = self.api.check_project_status(project) if not status: print('The project "{}" is not yet acceptable.'.format(project)) if not force: return False meta = self.api.get_prj_pseudometa(project) requests = [] packages = [] for req in meta['requests']: self.api.rm_from_prj(project, request_id=req['id'], msg='ready to accept') requests.append(req['id']) packages.append(req['package']) msg = 'Accepting staging review for {}'.format(req['package']) print(msg) oldspecs = self.api.get_filelist_for_package(pkgname=req['package'], project=self.api.project, extension='spec') change_request_state(self.api.apiurl, str(req['id']), 'accepted', message='Accept to %s' % self.api.project) self.create_new_links(self.api.project, req['package'], oldspecs) # A single comment should be enough to notify everybody, since # they are already mentioned in the comments created by # select/unselect pkg_list = ", ".join(packages) cmmt = 'Project "{}" accepted.' \ ' The following packages have been submitted to {}: {}.'.format(project, self.api.project, pkg_list) self.comment.add_comment(project_name=project, comment=cmmt) # XXX CAUTION - AFAIK the 'accept' command is expected to clean the messages here. self.comment.delete_from(project_name=project) self.api.build_switch_prj(project, 'disable') if self.api.item_exists(project + ':DVD'): self.api.build_switch_prj(project + ':DVD', 'disable') return True def cleanup(self, project): if not self.api.item_exists(project): return False pkglist = self.api.list_packages(project) clean_list = set(pkglist) - set(self.api.cstaging_nocleanup) for package in clean_list: print "[cleanup] deleted %s/%s" % (project, package) delete_package(self.api.apiurl, project, package, force=True, msg="autocleanup") return True def accept_other_new(self): changed = False rqlist = self.find_new_requests(self.api.project) if self.api.cnonfree: rqlist += self.find_new_requests(self.api.cnonfree) for req in rqlist: oldspecs = self.api.get_filelist_for_package(pkgname=req['packages'][0], project=self.api.project, extension='spec') print 'Accepting request %d: %s' % (req['id'], ','.join(req['packages'])) change_request_state(self.api.apiurl, str(req['id']), 'accepted', message='Accept to %s' % self.api.project) # Check if all .spec files of the package we just accepted has a package container to build self.create_new_links(self.api.project, req['packages'][0], oldspecs) changed = True return changed def create_new_links(self, project, pkgname, oldspeclist): filelist = self.api.get_filelist_for_package(pkgname=pkgname, project=project, extension='spec') removedspecs = set(oldspeclist) - set(filelist) for spec in removedspecs: # Deleting all the packages that no longer have a .spec file url = self.api.makeurl(['source', project, spec[:-5]]) print "Deleting package %s from project %s" % (spec[:-5], project) try: http_DELETE(url) except urllib2.HTTPError, err: if err.code == 404: # the package link was not yet created, which was likely a mistake from earlier pass else: # If the package was there bug could not be delete, raise the error raise if len(filelist) > 1: # There is more than one .spec file in the package; link package containers as needed origmeta = self.api.load_file_content(project, pkgname, '_meta') for specfile in filelist: package = specfile[:-5] # stripping .spec off the filename gives the packagename if package == pkgname: # This is the original package and does not need to be linked to itself continue # Check if the target package already exists, if it does not, we get a HTTP error 404 to catch if not self.api.item_exists(project, package): print "Creating new package %s linked to %s" % (package, pkgname) # new package does not exist. Let's link it with new metadata newmeta = re.sub(r'(<package.*name=.){}'.format(pkgname), r'\1{}'.format(package), origmeta) newmeta = re.sub(r'<devel.*>', r'<devel package="{}"/>'.format(pkgname), newmeta) newmeta = re.sub(r'<bcntsynctag>.*</bcntsynctag>', r'', newmeta) newmeta = re.sub(r'</package>', r'<bcntsynctag>{}</bcntsynctag></package>'.format(pkgname), newmeta) self.api.save_file_content(project, package, '_meta', newmeta) link = "<link package=\"{}\" cicount=\"copy\" />".format(pkgname) self.api.save_file_content(project, package, '_link', link) return True
class AcceptCommand(object): def __init__(self, api): self.api = api self.comment = CommentAPI(self.api.apiurl) def find_new_requests(self, project): query = "match=state/@name='new'+and+(action/target/@project='{}'+and+action/@type='submit')".format(project) url = self.api.makeurl(['search', 'request'], query) f = http_GET(url) root = ET.parse(f).getroot() rqs = [] for rq in root.findall('request'): pkgs = [] actions = rq.findall('action') for action in actions: targets = action.findall('target') for t in targets: pkgs.append(str(t.get('package'))) rqs.append({'id': int(rq.get('id')), 'packages': pkgs}) return rqs def perform(self, project): """ Accept the staging LETTER for review and submit to factory Then disable the build to disabled :param project: staging project we are working with """ status = self.api.check_project_status(project) if not status: print('The project "{}" is not yet acceptable.'.format(project)) return False meta = self.api.get_prj_pseudometa(project) requests = [] packages = [] for req in meta['requests']: self.api.rm_from_prj(project, request_id=req['id'], msg='ready to accept') requests.append(req['id']) packages.append(req['package']) msg = 'Accepting staging review for {}'.format(req['package']) print(msg) for req in requests: change_request_state(self.api.apiurl, str(req), 'accepted', message='Accept to factory') # A single comment should be enough to notify everybody, since they are # already mentioned in the comments created by select/unselect pkg_list = ", ".join(packages) cmmt = 'Project "{}" accepted. The following packages have been submitted to factory: {}.'.format(project, pkg_list) self.comment.add_comment(project_name=project, comment=cmmt) # XXX CAUTION - AFAIK the 'accept' command is expected to clean the messages here. self.comment.delete_from(project_name=project) self.api.build_switch_prj(project, 'disable') if self.api.project_exists(project + ':DVD'): self.api.build_switch_prj(project + ':DVD', 'disable') return True def accept_other_new(self): changed = False for req in self.find_new_requests('openSUSE:{}'.format(self.api.opensuse)): print 'Accepting request %d: %s' % (req['id'], ','.join(req['packages'])) change_request_state(self.api.apiurl, str(req['id']), 'accepted', message='Accept to factory') changed = True return changed def update_factory_version(self): """Update openSUSE (Factory, 13.2, ...) version if is necessary.""" project = 'openSUSE:{}'.format(self.api.opensuse) url = self.api.makeurl(['source', project, '_product', 'openSUSE.product']) product = http_GET(url).read() curr_version = date.today().strftime('%Y%m%d') new_product = re.sub(r'<version>\d{8}</version>', '<version>%s</version>' % curr_version, product) if product != new_product: http_PUT(url + '?comment=Update+version', data=new_product)
class TestCommentOBS(OBSLocalTestCase): def setUp(self): super(TestCommentOBS, self).setUp() self.api = CommentAPI(self.apiurl) # Ensure different test runs operate in unique namespace. self.bot = '::'.join([type(self).__name__, str(random.getrandbits(8))]) def test_basic(self): self.osc_user('staging-bot') self.assertFalse(self.comments_filtered(self.bot)[0]) self.assertTrue(self.api.add_comment( project_name=PROJECT, comment=self.api.add_marker(COMMENT, self.bot))) comment, _ = self.comments_filtered(self.bot) self.assertTrue(comment) self.assertTrue(self.api.delete(comment['id'])) self.assertFalse(self.comments_filtered(self.bot)[0]) def test_delete_nested(self): self.osc_user('staging-bot') comment_marked = self.api.add_marker(COMMENT, self.bot) # Allow for existing comments by basing assertion on delta from initial count. comment_count = len(self.api.get_comments(project_name=PROJECT)) self.assertFalse(self.comments_filtered(self.bot)[0]) self.assertTrue(self.api.add_comment(project_name=PROJECT, comment=comment_marked)) comment, _ = self.comments_filtered(self.bot) self.assertTrue(comment) for i in range(0, 3): self.assertTrue(self.api.add_comment( project_name=PROJECT, comment=comment_marked, parent_id=comment['id'])) comments = self.api.get_comments(project_name=PROJECT) parented_count = 0 for comment in comments.values(): if comment['parent']: parented_count += 1 self.assertEqual(parented_count, 3) self.assertTrue(len(comments) == comment_count + 4) self.api.delete_from(project_name=PROJECT) self.assertFalse(len(self.api.get_comments(project_name=PROJECT))) def test_delete_batch(self): users = ['factory-auto', 'repo-checker', 'staging-bot'] for user in users: self.osc_user(user) from osc import conf bot = '::'.join([self.bot, user]) comment = self.api.add_marker(COMMENT, bot) self.assertFalse(self.comments_filtered(bot)[0]) self.assertTrue(self.api.add_comment(project_name=PROJECT, comment=comment)) self.assertTrue(self.comments_filtered(bot)[0]) # Allow for existing comments by basing assertion on delta from initial count. comment_count = len(self.api.get_comments(project_name=PROJECT)) self.assertTrue(comment_count >= len(users)) self.api.delete_from_where_user(users[0], project_name=PROJECT) self.assertTrue(len(self.api.get_comments(project_name=PROJECT)) == comment_count - 1) self.api.delete_from(project_name=PROJECT) self.assertFalse(len(self.api.get_comments(project_name=PROJECT))) def comments_filtered(self, bot): comments = self.api.get_comments(project_name=PROJECT) return self.api.comment_find(comments, bot)
class AcceptCommand(object): def __init__(self, api): self.api = api self.comment = CommentAPI(self.api.apiurl) def find_new_requests(self, project): query = "match=state/@name='new'+and+(action/target/@project='{}'+and+action/@type='submit')".format(project) url = self.api.makeurl(['search', 'request'], query) f = http_GET(url) root = ET.parse(f).getroot() rqs = [] for rq in root.findall('request'): pkgs = [] actions = rq.findall('action') for action in actions: targets = action.findall('target') for t in targets: pkgs.append(str(t.get('package'))) rqs.append({'id': int(rq.get('id')), 'packages': pkgs}) return rqs def perform(self, project): """Accept the staging project for review and submit to Factory / openSUSE 13.2 ... Then disable the build to disabled :param project: staging project we are working with """ status = self.api.check_project_status(project) if not status: print('The project "{}" is not yet acceptable.'.format(project)) return False meta = self.api.get_prj_pseudometa(project) requests = [] packages = [] for req in meta['requests']: self.api.rm_from_prj(project, request_id=req['id'], msg='ready to accept') requests.append(req['id']) packages.append(req['package']) msg = 'Accepting staging review for {}'.format(req['package']) print(msg) oldspecs = self.api.get_filelist_for_package(pkgname=req['package'], project=self.api.project, extension='spec') change_request_state(self.api.apiurl, str(req['id']), 'accepted', message='Accept to %s' % self.api.project) self.create_new_links(self.api.project, req['package'], oldspecs) # A single comment should be enough to notify everybody, since # they are already mentioned in the comments created by # select/unselect pkg_list = ", ".join(packages) cmmt = 'Project "{}" accepted.' \ ' The following packages have been submitted to {}: {}.'.format(project, self.api.project, pkg_list) self.comment.add_comment(project_name=project, comment=cmmt) # XXX CAUTION - AFAIK the 'accept' command is expected to clean the messages here. self.comment.delete_from(project_name=project) self.api.build_switch_prj(project, 'disable') if self.api.item_exists(project + ':DVD'): self.api.build_switch_prj(project + ':DVD', 'disable') return True def accept_other_new(self): changed = False rqlist = self.find_new_requests(self.api.project) if self.api.cnonfree: rqlist += self.find_new_requests(self.api.cnonfree) for req in rqlist: oldspecs = self.api.get_filelist_for_package(pkgname=req['packages'][0], project=self.api.project, extension='spec') print 'Accepting request %d: %s' % (req['id'], ','.join(req['packages'])) change_request_state(self.api.apiurl, str(req['id']), 'accepted', message='Accept to %s' % self.api.project) # Check if all .spec files of the package we just accepted has a package container to build self.create_new_links(self.api.project, req['packages'][0], oldspecs) changed = True return changed def create_new_links(self, project, pkgname, oldspeclist): filelist = self.api.get_filelist_for_package(pkgname=pkgname, project=project, extension='spec') removedspecs = set(oldspeclist) - set(filelist) for spec in removedspecs: # Deleting all the packages that no longer have a .spec file url = self.api.makeurl(['source', project, spec[:-5]]) print "Deleting package %s from project %s" % (spec[:-5], project) try: http_DELETE(url) except urllib2.HTTPError, err: if err.code == 404: # the package link was not yet created, which was likely a mistake from earlier pass else: # If the package was there bug could not be delete, raise the error raise if len(filelist) > 1: # There is more than one .spec file in the package; link package containers as needed origmeta = self.api.load_file_content(project, pkgname, '_meta') for specfile in filelist: package = specfile[:-5] # stripping .spec off the filename gives the packagename if package == pkgname: # This is the original package and does not need to be linked to itself continue # Check if the target package already exists, if it does not, we get a HTTP error 404 to catch if not self.api.item_exists(project, package): print "Creating new package %s linked to %s" % (package, pkgname) # new package does not exist. Let's link it with new metadata newmeta = re.sub(r'(<package.*name=.){}'.format(pkgname), r'\1{}'.format(package), origmeta) newmeta = re.sub(r'<devel.*>\$', r'<devel package=\'{}\'/>'.format(pkgname), newmeta) newmeta = re.sub(r'<bcntsynctag>.*</bcntsynctag>', r'', newmeta) newmeta = re.sub(r'</package>', r'<bcntsynctag>{}</bcntsynctag></package>'.format(pkgname), newmeta) self.api.save_file_content(project, package, '_meta', newmeta) link = "<link package=\"{}\" cicount=\"copy\" />".format(pkgname) self.api.save_file_content(project, package, '_link', link) return True
class TestReviewBotComment(OBSLocal.OBSLocalTestCase): def setUp(self): super(TestReviewBotComment, self).setUp() self.api = CommentAPI(self.apiurl) # Ensure different test runs operate in unique namespace. self.bot = '::'.join([type(self).__name__, str(random.getrandbits(8))]) self.review_bot = ReviewBot(self.apiurl, logger=logging.getLogger(self.bot)) self.review_bot.bot_name = self.bot self.osc_user('factory-auto') def tearDown(self): self.api.delete_from(project_name=PROJECT) self.assertFalse(len(self.api.get_comments(project_name=PROJECT))) def test_basic_logger(self): comment_count = len(self.api.get_comments(project_name=PROJECT)) self.assertFalse(self.comments_filtered(self.bot)[0]) # Initial comment. self.review_bot.comment_handler_add() self.review_bot.logger.info('something interesting') self.review_bot.comment_write(project=PROJECT) comment, _ = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith('something interesting')) # Second comment with extra line. self.review_bot.comment_handler_add() self.review_bot.logger.info('something interesting') self.review_bot.logger.info('something extra') self.review_bot.comment_write(project=PROJECT) comment, _ = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith('something extra')) def test_workflow(self): comment_count = len(self.api.get_comments(project_name=PROJECT)) self.assertFalse(self.comments_filtered(self.bot)[0]) # Initial comment. info = {'state': 'seen', 'result': 'failed'} info_extra = {'build': '1'} info_merged = info.copy() info_merged.update(info_extra) self.review_bot.comment_write(state='seen', result='failed', identical=True, info_extra=info_extra, info_extra_identical=False, project=PROJECT, message=COMMENT) comment, info_parsed = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith(COMMENT)) self.assertEqual(info_parsed, info_merged) # Only build change (expect no change). info_extra = {'build': '2'} self.review_bot.comment_write(state='seen', result='failed', identical=True, info_extra=info_extra, info_extra_identical=False, project=PROJECT, message=COMMENT) comment, info_parsed = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith(COMMENT)) self.assertEqual(info_parsed, info_merged) # Build and comment (except comment replacement). info_extra = {'build': '3'} info_merged.update(info_extra) self.review_bot.comment_write(state='seen', result='failed', identical=True, info_extra=info_extra, info_extra_identical=False, project=PROJECT, message=COMMENT + '3') comment, info_parsed = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith(COMMENT + '3')) self.assertEqual(info_parsed, info_merged) # Final build (except comment replacement). info_extra = {'build': '4'} info_merged.update(info_extra) self.review_bot.comment_write(state='seen', result='failed', identical=True, info_extra=info_extra, info_extra_identical=True, project=PROJECT, message=COMMENT + '4') comment, info_parsed = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith(COMMENT + '4')) self.assertEqual(info_parsed, info_merged) # Final build (except comment replacement). info = {'state': 'done', 'result': 'passed'} info_extra = {'build': '5'} info_merged = info.copy() info_merged.update(info_extra) self.review_bot.comment_write(state='done', result='passed', identical=True, info_extra=info_extra, info_extra_identical=True, only_replace=True, project=PROJECT, message=COMMENT + '5') comment, info_parsed = self.comments_filtered(self.bot) self.assertTrue(comment['comment'].endswith(COMMENT + '5')) self.assertEqual(info_parsed, info_merged) # Should never be more than one new comment. self.assertEqual(len(self.api.get_comments(project_name=PROJECT)), comment_count + 1) def test_only_replace_none(self): self.review_bot.comment_write(only_replace=True, project=PROJECT, message=COMMENT) self.assertFalse(self.comments_filtered(self.bot)[0]) def test_dryrun(self): # dryrun = True, no comment. self.review_bot.dryrun = True self.review_bot.comment_write(project=PROJECT, message=COMMENT) self.assertFalse(self.comments_filtered(self.bot)[0]) # dryrun = False, a comment. self.review_bot.dryrun = False self.review_bot.comment_write(project=PROJECT, message=COMMENT) self.assertTrue(self.comments_filtered(self.bot)[0]) # dryrun = True, no replacement. self.review_bot.dryrun = True self.review_bot.comment_write(state='changed', project=PROJECT, message=COMMENT) _, info = self.comments_filtered(self.bot) self.assertEqual(info['state'], 'done') # dryrun = False, replacement. self.review_bot.dryrun = False self.review_bot.comment_write(state='changed', project=PROJECT, message=COMMENT) _, info = self.comments_filtered(self.bot) self.assertEqual(info['state'], 'changed') def test_bot_name_suffix(self): suffix1 = 'suffix1' bot_suffixed1 = '::'.join([self.bot, suffix1]) suffix2 = 'suffix2' bot_suffixed2 = '::'.join([self.bot, suffix2]) self.review_bot.comment_write(bot_name_suffix=suffix1, project=PROJECT, message=COMMENT) self.assertFalse(self.comments_filtered(self.bot)[0]) self.assertTrue(self.comments_filtered(bot_suffixed1)[0]) self.assertFalse(self.comments_filtered(bot_suffixed2)[0]) self.review_bot.comment_write(bot_name_suffix=suffix2, project=PROJECT, message=COMMENT) self.assertFalse(self.comments_filtered(self.bot)[0]) self.assertTrue(self.comments_filtered(bot_suffixed1)[0]) self.assertTrue(self.comments_filtered(bot_suffixed2)[0]) self.review_bot.comment_write(bot_name_suffix=suffix1, project=PROJECT, message=COMMENT + '\nnew') comment, _ = self.comments_filtered(bot_suffixed1) self.assertTrue(comment['comment'].endswith(COMMENT + '\nnew')) comment, _ = self.comments_filtered(bot_suffixed2) self.assertTrue(comment['comment'].endswith(COMMENT)) def comments_filtered(self, bot): comments = self.api.get_comments(project_name=PROJECT) return self.api.comment_find(comments, bot)
class AcceptCommand(object): def __init__(self, api): self.api = api self.comment = CommentAPI(self.api.apiurl) def find_new_requests(self, project): query = "match=state/@name='new'+and+(action/target/@project='{}'+and+action/@type='submit')".format( project) url = self.api.makeurl(['search', 'request'], query) f = http_GET(url) root = ET.parse(f).getroot() rqs = [] for rq in root.findall('request'): pkgs = [] actions = rq.findall('action') for action in actions: targets = action.findall('target') for t in targets: pkgs.append(str(t.get('package'))) rqs.append({'id': int(rq.get('id')), 'packages': pkgs}) return rqs def perform(self, project, force=False): """Accept the staging project for review and submit to Factory / openSUSE 13.2 ... Then disable the build to disabled :param project: staging project we are working with """ status = self.api.check_project_status(project) if not status: print('The project "{}" is not yet acceptable.'.format(project)) if not force: return False meta = self.api.get_prj_pseudometa(project) requests = [] packages = [] for req in meta['requests']: self.api.rm_from_prj(project, request_id=req['id'], msg='ready to accept') requests.append(req['id']) packages.append(req['package']) msg = 'Accepting staging review for {}'.format(req['package']) print(msg) oldspecs = self.api.get_filelist_for_package( pkgname=req['package'], project=self.api.project, extension='spec') change_request_state(self.api.apiurl, str(req['id']), 'accepted', message='Accept to %s' % self.api.project) self.create_new_links(self.api.project, req['package'], oldspecs) # A single comment should be enough to notify everybody, since # they are already mentioned in the comments created by # select/unselect pkg_list = ", ".join(packages) cmmt = 'Project "{}" accepted.' \ ' The following packages have been submitted to {}: {}.'.format(project, self.api.project, pkg_list) self.comment.add_comment(project_name=project, comment=cmmt) # XXX CAUTION - AFAIK the 'accept' command is expected to clean the messages here. self.comment.delete_from(project_name=project) self.api.build_switch_prj(project, 'disable') if self.api.item_exists(project + ':DVD'): self.api.build_switch_prj(project + ':DVD', 'disable') return True def cleanup(self, project): if not self.api.item_exists(project): return False pkglist = self.api.list_packages(project) clean_list = set(pkglist) - set(self.api.cstaging_nocleanup) for package in clean_list: print "[cleanup] deleted %s/%s" % (project, package) delete_package(self.api.apiurl, project, package, force=True, msg="autocleanup") # wipe Test-DVD binaries and breaks kiwi build if project.startswith('openSUSE:'): for package in pkglist: if package.startswith('Test-DVD-'): # intend to break the kiwi file arch = package.split('-')[-1] fakepkgname = 'I-am-breaks-kiwi-build' oldkiwifile = self.api.load_file_content( project, package, 'PRODUCT-' + arch + '.kiwi') if oldkiwifile is not None: newkiwifile = re.sub( r'<repopackage name="openSUSE-release"/>', '<repopackage name="%s"/>' % fakepkgname, oldkiwifile) self.api.save_file_content(project, package, 'PRODUCT-' + arch + '.kiwi', newkiwifile) # do wipe binary now query = {'cmd': 'wipe'} query['package'] = package query['repository'] = 'images' url = self.api.makeurl(['build', project], query) try: http_POST(url) except urllib2.HTTPError, err: # failed to wipe isos but we can just continue pass return True
class AcceptCommand(object): def __init__(self, api): self.api = api self.comment = CommentAPI(self.api.apiurl) def find_new_requests(self, project): query = "match=state/@name='new'+and+(action/target/@project='{}'+and+action/@type='submit')".format(project) url = self.api.makeurl(['search', 'request'], query) f = http_GET(url) root = ET.parse(f).getroot() rqs = [] for rq in root.findall('request'): pkgs = [] actions = rq.findall('action') for action in actions: targets = action.findall('target') for t in targets: pkgs.append(str(t.get('package'))) rqs.append({'id': int(rq.get('id')), 'packages': pkgs}) return rqs def perform(self, project, force=False): """Accept the staging project for review and submit to Factory / openSUSE 13.2 ... Then disable the build to disabled :param project: staging project we are working with """ status = self.api.check_project_status(project) if not status: print('The project "{}" is not yet acceptable.'.format(project)) if not force: return False meta = self.api.get_prj_pseudometa(project) requests = [] packages = [] for req in meta['requests']: self.api.rm_from_prj(project, request_id=req['id'], msg='ready to accept') requests.append(req['id']) packages.append(req['package']) msg = 'Accepting staging review for {}'.format(req['package']) print(msg) oldspecs = self.api.get_filelist_for_package(pkgname=req['package'], project=self.api.project, extension='spec') change_request_state(self.api.apiurl, str(req['id']), 'accepted', message='Accept to %s' % self.api.project) self.create_new_links(self.api.project, req['package'], oldspecs) # A single comment should be enough to notify everybody, since # they are already mentioned in the comments created by # select/unselect pkg_list = ", ".join(packages) cmmt = 'Project "{}" accepted.' \ ' The following packages have been submitted to {}: {}.'.format(project, self.api.project, pkg_list) self.comment.add_comment(project_name=project, comment=cmmt) # XXX CAUTION - AFAIK the 'accept' command is expected to clean the messages here. self.comment.delete_from(project_name=project) self.api.build_switch_prj(project, 'disable') if self.api.item_exists(project + ':DVD'): self.api.build_switch_prj(project + ':DVD', 'disable') return True def cleanup(self, project): if not self.api.item_exists(project): return False pkglist = self.api.list_packages(project) clean_list = set(pkglist) - set(self.api.cstaging_nocleanup) for package in clean_list: print "[cleanup] deleted %s/%s" % (project, package) delete_package(self.api.apiurl, project, package, force=True, msg="autocleanup") # wipe Test-DVD binaries and breaks kiwi build if project.startswith('openSUSE:'): for package in pkglist: if package.startswith('Test-DVD-'): # intend to break the kiwi file arch = package.split('-')[-1] fakepkgname = 'I-am-breaks-kiwi-build' oldkiwifile = self.api.load_file_content(project, package, 'PRODUCT-'+arch+'.kiwi') if oldkiwifile is not None: newkiwifile = re.sub(r'<repopackage name="openSUSE-release"/>', '<repopackage name="%s"/>' % fakepkgname, oldkiwifile) self.api.save_file_content(project, package, 'PRODUCT-' + arch + '.kiwi', newkiwifile) # do wipe binary now query = { 'cmd': 'wipe' } query['package'] = package query['repository'] = 'images' url = self.api.makeurl(['build', project], query) try: http_POST(url) except urllib2.HTTPError, err: # failed to wipe isos but we can just continue pass return True