#!/usr/bin/python """Automated tests for MergeBot Run from a Trac source tree with mergebot installed system-wide. (This needs to be reworked to be less cumbersome.) """ # TODO: The testcases need to be extended to cover the following: # - using a ticket #number as a version # - using a release version number as a version # - verify inter-ticket dependency checking # - verify failure cascades through inter-ticket dependencies # - change the version of a ticket, rebranch, and merge import os import unittest import time import shutil from subprocess import call, Popen #, PIPE, STDOUT from twill.errors import TwillAssertionError from trac.tests.functional import FunctionalTestSuite, FunctionalTester, FunctionalTwillTestCaseSetup, tc, b, logfile from trac.tests.functional.svntestenv import SvnFunctionalTestEnvironment from trac.tests.contentgen import random_page, random_sentence #, random_word #class MergeBotTestEnvironment(FunctionalTestEnvironment): # """Slight change to FunctionalTestEnvironment to keep the PYTHONPATH from # our environment. # """ # def start(self): # """Starts the webserver""" # server = Popen(["python", "./trac/web/standalone.py", # "--port=%s" % self.port, "-s", # "--basic-auth=trac,%s," % self.htpasswd, # self.tracdir], # #env={'PYTHONPATH':'.'}, # stdout=logfile, stderr=logfile, # ) # self.pid = server.pid # time.sleep(1) # Give the server time to come up # # def _tracadmin(self, *args): # """Internal utility method for calling trac-admin""" # if call(["python", "./trac/admin/console.py", self.tracdir] + # list(args), # #env={'PYTHONPATH':'.'}, # stdout=logfile, stderr=logfile): # raise Exception('Failed running trac-admin with %r' % (args, )) # # #FunctionalTestEnvironment = MergeBotTestEnvironment class MergeBotFunctionalTester(FunctionalTester): """Adds some MergeBot functionality to the functional tester.""" # FIXME: the tc.find( ) checks are bogus: any ticket can # satisfy them, not just the one we're working on. def __init__(self, trac_url, repo_url): FunctionalTester.__init__(self, trac_url) self.repo_url = repo_url self.mergeboturl = self.url + '/mergebot' def wait_until_find(self, search, timeout=5): start = time.time() while time.time() - start < timeout: try: #tc.reload() # This appears to re-POST tc.go(b.get_url()) tc.find(search) return except TwillAssertionError: pass raise TwillAssertionError("Unable to find %r within %s seconds" % (search, timeout)) def wait_until_notfind(self, search, timeout=5): start = time.time() while time.time() - start < timeout: try: #tc.reload() # This appears to re-POST tc.go(b.get_url()) tc.notfind(search) return except TwillAssertionError: pass raise TwillAssertionError("Unable to notfind %r within %s seconds" % (search, timeout)) def go_to_mergebot(self): tc.go(self.mergeboturl) tc.url(self.mergeboturl) tc.notfind('No handler matched request to /mergebot') def queue_branch(self, ticket_id): self.go_to_mergebot() tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form tc.submit('Branch') def branch(self, ticket_id, component, timeout=1): """timeout is in seconds.""" self.queue_branch(ticket_id) self.wait_until_find('Nothing in the queue', timeout) tc.find('Rebranch') tc.find('Merge') tc.find('CheckMerge') self.go_to_ticket(ticket_id) tc.find('Created branch from .* for .*') retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id], stdout=logfile, stderr=logfile) if retval: raise Exception('svn ls failed with exit code %s' % retval) def queue_rebranch(self, ticket_id): self.go_to_mergebot() tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form tc.submit('Rebranch') def _rebranch(self, ticket_id, component, search, timeout=15): """timeout is in seconds.""" self.queue_rebranch(ticket_id) self.wait_until_find('Nothing in the queue', timeout) tc.find('Rebranch') tc.find('Merge') tc.find('CheckMerge') self.go_to_ticket(ticket_id) tc.find(search) retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id], stdout=logfile, stderr=logfile) if retval: raise Exception('svn ls failed with exit code %s' % retval) def rebranch(self, ticket_id, component, timeout=15): self._rebranch(ticket_id, component, 'Rebranched from .* for .*', timeout) def rebranch_conflict(self, ticket_id, component, timeout=15): self._rebranch(ticket_id, component, 'There were conflicts on rebranching', timeout) def queue_merge(self, ticket_id): self.go_to_mergebot() tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form tc.submit('Merge') def merge(self, ticket_id, component, timeout=5): self._merge(ticket_id, component, 'Merged .* to .* for', timeout) def merge_conflict(self, ticket_id, component, timeout=5): self._merge(ticket_id, component, 'Found [0-9]+ conflicts? in attempt to merge ', timeout) def _merge(self, ticket_id, component, search, timeout=5): """timeout is in seconds.""" self.queue_merge(ticket_id) self.wait_until_find('Nothing in the queue', timeout) tc.find('Branch') self.go_to_ticket(ticket_id) tc.find(search) # TODO: We may want to change this to remove the "dead" branch retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id], stdout=logfile, stderr=logfile) if retval: raise Exception('svn ls failed with exit code %s' % retval) def queue_checkmerge(self, ticket_id): self.go_to_mergebot() tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form tc.submit('CheckMerge') def checkmerge(self, ticket_id, component, timeout=5): """timeout is in seconds.""" self.queue_checkmerge(ticket_id) self.wait_until_find('Nothing in the queue', timeout) tc.find('Rebranch') tc.find('Merge') tc.find('CheckMerge') self.go_to_ticket(ticket_id) tc.find('while checking merge of') retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id], stdout=logfile, stderr=logfile) if retval: raise Exception('svn ls failed with exit code %s' % retval) def wait_for_empty_queue(self, timeout=10): self.go_to_mergebot() self.wait_until_find('Nothing in the queue', timeout) class MergeBotTestSuite(FunctionalTestSuite): def setUp(self): port = 8889 baseurl = "http://localhost:%s" % port self._testenv = SvnFunctionalTestEnvironment("testenv%s" % port, port, baseurl) # Configure mergebot env = self._testenv.get_trac_environment() env.config.set('components', 'mergebot.web_ui.mergebotmodule', 'enabled') env.config.save() os.mkdir(os.path.join("testenv%s" % port, 'trac', 'mergebot')) self._testenv._tracadmin('upgrade') # sets up the bulk of the mergebot config env.config.parse_if_needed() env.config.set('mergebot', 'repository_url', self._testenv.repo_url()) env.config.set('logging', 'log_type', 'file') env.config.save() env.config.parse_if_needed() self._testenv.start() self._tester = MergeBotFunctionalTester(baseurl, self._testenv.repo_url()) os.system('mergebotdaemon -f "%s" > %s/mergebotdaemon.log 2>&1 &' % (self._testenv.tracdir, self._testenv.tracdir)) self.fixture = (self._testenv, self._tester) # Setup some common component stuff for MergeBot's use: svnurl = self._testenv.repo_url() for component in ['stuff', 'flagship', 'submarine']: self._tester.create_component(component) if call(['svn', '-m', 'Create tree for "%s".' % component, 'mkdir', svnurl + '/' + component, svnurl + '/' + component + '/trunk', svnurl + '/' + component + '/tags', svnurl + '/' + component + '/branches'], stdout=logfile, stderr=logfile): raise Exception("svn mkdir failed") self._tester.create_version('trunk') class FunctionalSvnTestCaseSetup(FunctionalTwillTestCaseSetup): def get_workdir(self): return os.path.join(self._testenv.dirname, self.__class__.__name__) def checkout(self, ticket_id=None): """checkout a working copy of the branch for the given ticket, or trunk if none given""" if ticket_id is None: svnurl = self._testenv.repo_url() + '/stuff/trunk' else: svnurl = self._testenv.repo_url() + '/stuff/branches/ticket-%s' % ticket_id retval = call(['svn', 'checkout', svnurl, self.get_workdir()], stdout=logfile, stderr=logfile) self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval)) def cleanup(self): shutil.rmtree(self.get_workdir()) def switch(self, ticket_id=None): if ticket_id is None: svnurl = self._testenv.repo_url() + '/stuff/trunk' else: svnurl = self._testenv.repo_url() + '/stuff/branches/ticket-%s' % ticket_id retval = call(['svn', 'switch', svnurl, self.get_workdir()], stdout=logfile, stderr=logfile) self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval)) def add_new_file(self, filename=None, file_size=None): workdir = self.get_workdir() if filename is None: newfile = os.path.join(workdir, self.__class__.__name__) else: newfile = os.path.join(workdir, filename) if file_size is None: data = random_page() else: data = '' while len(data) < file_size: data += random_page() data = data[:file_size] open(newfile, 'w').write(data) retval = call(['svn', 'add', newfile], cwd=workdir, stdout=logfile, stderr=logfile) self.assertEqual(retval, 0, "svn add failed with error %s" % (retval)) def commit(self, message, files=None): if files is None: files = ['.'] commit_message = self.__class__.__name__ + ": " + message retval = call(['svn', 'commit', '-m', commit_message] + list(files), cwd=self.get_workdir(), stdout=logfile, stderr=logfile) self.assertEqual(retval, 0, "svn commit failed with error %s" % (retval)) def mv(self, oldname, newname): retval = call(['svn', 'mv', oldname, newname], cwd=self.get_workdir(), stdout=logfile, stderr=logfile) self.assertEqual(retval, 0, "svn mv failed with error %s" % (retval)) def propset(self, propname, propvalue, filename): retval = call(['svn', 'propset', propname, propvalue, filename], cwd=self.get_workdir(), stdout=logfile, stderr=logfile) self.assertEqual(retval, 0, "svn prposet failed with error %s" % (retval)) def propdel(self, propname, filename): retval = call(['svn', 'propdel', propname, filename], cwd=self.get_workdir(), stdout=logfile, stderr=logfile) self.assertEqual(retval, 0, "svn prposet failed with error %s" % (retval)) def rm(self, filename): retval = call(['svn', 'rm', filename], cwd=self.get_workdir(), stdout=logfile, stderr=logfile) self.assertEqual(retval, 0, "svn rm failed with error %s" % (retval)) class MergeBotTestEnabled(FunctionalTwillTestCaseSetup): def runTest(self): self._tester.logout() tc.go(self._tester.url) self._tester.login('admin') tc.follow('MergeBot') mergeboturl = self._tester.url + '/mergebot' tc.url(mergeboturl) tc.notfind('No handler matched request to /mergebot') class MergeBotTestNoVersion(FunctionalTwillTestCaseSetup): """Verify that if a ticket does not have the version field set, it will not appear in the MergeBot list. """ def runTest(self): ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':''}) tc.follow('MergeBot') tc.notfind(self.__class__.__name__) class MergeBotTestBranch(FunctionalTwillTestCaseSetup): def runTest(self): """Verify that the 'branch' button works""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) self._tester.branch(ticket_id, 'stuff') class MergeBotTestRebranch(FunctionalTwillTestCaseSetup): def runTest(self): """Verify that the 'rebranch' button works""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) self._tester.branch(ticket_id, 'stuff') self._tester.rebranch(ticket_id, 'stuff') class MergeBotTestMerge(FunctionalTwillTestCaseSetup): def runTest(self): """Verify that the 'merge' button works""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) self._tester.branch(ticket_id, 'stuff') self._tester.merge(ticket_id, 'stuff') class MergeBotTestMergeWithChange(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'merge' button works with changes on the branch""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) self._tester.branch(ticket_id, 'stuff') # checkout a working copy & make a change self.checkout(ticket_id) # Create & add a new file self.add_new_file() self.commit('Add a new file') self._tester.merge(ticket_id, 'stuff') self.cleanup() class MergeBotTestMergeWithChangeAndTrunkChange(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'merge' button works with changes on the branch and trunk""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) self._tester.branch(ticket_id, 'stuff') # checkout a working copy & make a change self.checkout(ticket_id) # Create & add a new file basename = self.__class__.__name__ self.add_new_file(basename + '-ticket') self.commit('Add a new file on ticket') self.switch() self.add_new_file(basename + '-trunk') self.commit('Add a new file on trunk') self._tester.merge(ticket_id, 'stuff') self.cleanup() class MergeBotTestMergeWithConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'merge' button detects conflicts between the branch and trunk""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ # create a file in which to have conflicts self.checkout() self.add_new_file(basename) self.commit('Add a new file on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # modify the file on trunk open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Modify the file on trunk') # modify the file on the branch self.switch(ticket_id) open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Modify the file on branch') # merge, make sure it shows a conflict self._tester.merge_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestMergeWithPropertyConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'merge' button detects property conflicts between the branch and trunk""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ self.checkout() self.propset('svn:ignore', basename, '.') self.commit('set property on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # modify the property on trunk self.propset('svn:ignore', basename + '\ntrunk', '.') self.commit('Modify the property on trunk') # modify the property on the branch self.switch(ticket_id) self.propset('svn:ignore', basename + '\nbranch', '.') self.commit('Modify the property on branch') # merge, make sure it shows a conflict self._tester.merge_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestMergeWithPropertyBranchDeleteConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'merge' button detects property deleted on branch and modified on trunk""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ self.checkout() self.propset('svn:ignore', basename, '.') self.commit('set property on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # modify the property on trunk self.propset('svn:ignore', basename + '\ntrunk', '.') self.commit('Modify the property on trunk') # delete the property on the branch self.switch(ticket_id) self.propdel('svn:ignore', '.') self.commit('Delete the property on branch') # merge, make sure it shows a conflict self._tester.merge_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestMergeWithPropertyTrunkDeleteConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'merge' button detects property deleted on trunk and modified on branch""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ self.checkout() self.propset('svn:ignore', basename, '.') self.commit('set property on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # delete the property on trunk self.propdel('svn:ignore', '.') self.commit('Delete the property on trunk') # delete the property on the branch self.switch(ticket_id) self.propset('svn:ignore', basename + '\nbranch', '.') self.commit('Modify the property on branch') # merge, make sure it shows a conflict self._tester.merge_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestMergeWithBranchRenameConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'merge' button detects a conflict when a file renamed on the branch was modified on trunk""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ # create a file in which to have conflicts self.checkout() self.add_new_file(basename) self.commit('Add a new file on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # modify the file on trunk open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Modify the file on trunk') # rename the file on the branch self.switch(ticket_id) self.mv(basename, basename + '-renamed') self.commit('Rename the file on the branch') self._tester.merge_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestMergeWithTrunkRenameConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'merge' button detects conflicts when a file renamed on trunk was modified on the branch""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ # create a file in which to have conflicts self.checkout() self.add_new_file(basename) self.commit('Add a new file on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # rename the file on trunk self.mv(basename, basename + '-renamed') self.commit('Rename the file on trunk') # rename the file on the branch self.switch(ticket_id) open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Modify the file on the branch') # make sure it finds the conflict self._tester.merge_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestCheckMerge(FunctionalTwillTestCaseSetup): def runTest(self): """Verify that the 'checkmerge' button works""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) self._tester.branch(ticket_id, 'stuff') self._tester.checkmerge(ticket_id, 'stuff') class MergeBotTestRebranchWithChange(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'rebranch' button works with changes on the branch""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) self._tester.branch(ticket_id, 'stuff') # checkout a working copy & make a change self.checkout(ticket_id) # Create & add a new file self.add_new_file() self.commit('Add a new file') self._tester.rebranch(ticket_id, 'stuff') self.cleanup() class MergeBotTestRebranchWithChangeAndTrunkChange(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'rebranch' button works with changes on the branch and trunk""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) self._tester.branch(ticket_id, 'stuff') # checkout a working copy & make a change self.checkout(ticket_id) # Create & add a new file basename = self.__class__.__name__ self.add_new_file(basename + '-ticket') self.commit('Add a new file on ticket') self.switch() self.add_new_file(basename + '-trunk') self.commit('Add a new file on trunk') self._tester.rebranch(ticket_id, 'stuff') self.cleanup() class MergeBotTestRebranchWithConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'rebranch' button detects conflicts between the branch and trunk""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ # create a file in which to have conflicts self.checkout() self.add_new_file(basename) self.commit('Add a new file on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # modify the file on trunk open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Modify the file on trunk') # modify the file on the branch self.switch(ticket_id) open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Modify the file on branch') # rebranch, make sure it shows a conflict self._tester.rebranch_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestRebranchWithPropertyConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'rebranch' button detects property conflicts between the branch and trunk""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ self.checkout() self.propset('svn:ignore', basename, '.') self.commit('set property on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # modify the property on trunk self.propset('svn:ignore', basename + '\ntrunk', '.') self.commit('Modify the property on trunk') # modify the property on the branch self.switch(ticket_id) self.propset('svn:ignore', basename + '\nbranch', '.') self.commit('Modify the property on branch') # rebranch, make sure it shows a conflict self._tester.rebranch_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestRebranchWithPropertyBranchDeleteConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'rebranch' button detects property deleted on branch and modified on trunk""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ self.checkout() self.propset('svn:ignore', basename, '.') self.commit('set property on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # modify the property on trunk self.propset('svn:ignore', basename + '\ntrunk', '.') self.commit('Modify the property on trunk') # delete the property on the branch self.switch(ticket_id) self.propdel('svn:ignore', '.') self.commit('Delete the property on branch') # rebranch, make sure it shows a conflict self._tester.rebranch_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestRebranchWithPropertyTrunkDeleteConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'rebranch' button detects property deleted on trunk and modified on branch""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ self.checkout() self.propset('svn:ignore', basename, '.') self.commit('set property on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # delete the property on trunk self.propdel('svn:ignore', '.') self.commit('Delete the property on trunk') # delete the property on the branch self.switch(ticket_id) self.propset('svn:ignore', basename + '\nbranch', '.') self.commit('Modify the property on branch') # rebranch, make sure it shows a conflict self._tester.rebranch_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestRebranchWithBranchRenameConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'rebranch' button detects a conflict when a file renamed on the branch was modified on trunk""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ # create a file in which to have conflicts self.checkout() self.add_new_file(basename) self.commit('Add a new file on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # modify the file on trunk open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Modify the file on trunk') # rename the file on the branch self.switch(ticket_id) self.mv(basename, basename + '-renamed') self.commit('Rename the file on the branch') self._tester.rebranch_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestRebranchWithTrunkRenameConflict(FunctionalSvnTestCaseSetup): def runTest(self): """Verify that the 'rebranch' button detects conflicts when a file renamed on trunk was modified on the branch""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ # create a file in which to have conflicts self.checkout() self.add_new_file(basename) self.commit('Add a new file on trunk') # create the branch self._tester.branch(ticket_id, 'stuff') # rename the file on trunk self.mv(basename, basename + '-renamed') self.commit('Rename the file on trunk') # rename the file on the branch self.switch(ticket_id) open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Modify the file on the branch') # make sure it finds the conflict self._tester.rebranch_conflict(ticket_id, 'stuff') self.cleanup() class MergeBotTestSingleUseCase(FunctionalSvnTestCaseSetup): def runTest(self): """Create a branch, make a change, checkmerge, and merge it.""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) self._tester.branch(ticket_id, 'stuff') # checkout a working copy & make a change self.checkout(ticket_id) # Create & add a new file self.add_new_file() self.commit('Add a new file') self._tester.checkmerge(ticket_id, 'stuff') self._tester.merge(ticket_id, 'stuff') self.cleanup() class MergeBotTestBranchReuse(FunctionalSvnTestCaseSetup): def runTest(self): """Merge a branch, branch it again, and merge it a second time.""" ticket_id = self._tester.create_ticket(summary=self.__class__.__name__, info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ self.checkout() self.add_new_file(basename) self.commit('Add a new file') self._tester.branch(ticket_id, 'stuff') self.switch(ticket_id) open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Make a modification') self._tester.merge(ticket_id, 'stuff') self._tester.branch(ticket_id, 'stuff') self.switch(ticket_id) open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Make a second modification') self._tester.merge(ticket_id, 'stuff') self.cleanup() class MergeBotTestMergeDependency(FunctionalSvnTestCaseSetup): def runTest(self): """Merge two branches to trunk; make sure the second one shows as waiting""" # This test is fundamentally racy. The size of the files has been # chosen to slow down the test long enough to get a view of the # mergebot page with one of them waiting for the other to complete. ticket_one = self._tester.create_ticket(summary=self.__class__.__name__ + " one", info={'component':'stuff', 'version':'trunk'}) ticket_two = self._tester.create_ticket(summary=self.__class__.__name__ + " two", info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ self._tester.branch(ticket_one, 'stuff') self._tester.branch(ticket_two, 'stuff') self.checkout(ticket_one) self.add_new_file(basename + '-one', 30*1024**2) self.commit('Add a new file') self.switch(ticket_two) self.add_new_file(basename + '-two', 30*1024**2) self.commit('Add a new file') self._tester.queue_merge(ticket_two) self._tester.queue_merge(ticket_one) #self._tester.go_to_mergebot() tc.find('Waiting') self._tester.wait_for_empty_queue(180) # Has to be large for slow machines self.switch() self.rm(basename + '-one') self.rm(basename + '-two') self.commit('Drop large files so later tests don\'t have to deal with them.') self.cleanup() class MergeBotTestMergeDependencyCascade(FunctionalSvnTestCaseSetup): def runTest(self): """Merge two branches to trunk; make sure the second is cancelled when the first hits merge conflicts. """ ticket_one = self._tester.create_ticket(summary=self.__class__.__name__ + " one", info={'component':'stuff', 'version':'trunk'}) ticket_two = self._tester.create_ticket(summary=self.__class__.__name__ + " two", info={'component':'stuff', 'version':'trunk'}) basename = self.__class__.__name__ self.checkout() # create a file on trunk to work with # make it large enough to give us time to queue the second ticket # before this one completes its merge self.add_new_file(basename, 30*1024**2) self.commit('Add a new file') # branch the first ticket that we will have fail to merge due to # conflicts. self._tester.branch(ticket_one, 'stuff') # modify the file on trunk open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Modify the file on trunk') # and modify the file on the branch self.switch(ticket_one) open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence()) self.commit('Modify the file on branch in conflicting manner') # create a branch for the second ticket with a non-conflicting change self._tester.branch(ticket_two, 'stuff') self.switch(ticket_two) self.add_new_file(basename + '-two') self.commit('Add a new file') # the stage is set. Now we request the merge that will conflict, # followed by the merge that won't, and wait for the merges to complete. self._tester.queue_merge(ticket_one) self._tester.queue_merge(ticket_two) self._tester.wait_for_empty_queue(180) # Has to be large for slow machines # Then we verify that one had conflicts, and one was cancelled. self._tester.go_to_ticket(ticket_one) tc.find('Found [0-9]+ conflicts? in attempt to merge ') self._tester.go_to_ticket(ticket_two) tc.notfind('Merged .* to .* for') # and clean up the mess self.cleanup() def suite(): suite = MergeBotTestSuite() suite.addTest(MergeBotTestEnabled()) suite.addTest(MergeBotTestNoVersion()) suite.addTest(MergeBotTestBranch()) suite.addTest(MergeBotTestRebranch()) suite.addTest(MergeBotTestCheckMerge()) suite.addTest(MergeBotTestMerge()) suite.addTest(MergeBotTestRebranchWithChange()) suite.addTest(MergeBotTestRebranchWithChangeAndTrunkChange()) suite.addTest(MergeBotTestRebranchWithConflict()) suite.addTest(MergeBotTestRebranchWithPropertyConflict()) suite.addTest(MergeBotTestRebranchWithBranchRenameConflict()) suite.addTest(MergeBotTestRebranchWithTrunkRenameConflict()) suite.addTest(MergeBotTestRebranchWithPropertyBranchDeleteConflict()) suite.addTest(MergeBotTestRebranchWithPropertyTrunkDeleteConflict()) suite.addTest(MergeBotTestMergeWithChange()) suite.addTest(MergeBotTestMergeWithChangeAndTrunkChange()) suite.addTest(MergeBotTestMergeWithConflict()) suite.addTest(MergeBotTestMergeWithPropertyConflict()) suite.addTest(MergeBotTestMergeWithBranchRenameConflict()) suite.addTest(MergeBotTestMergeWithTrunkRenameConflict()) suite.addTest(MergeBotTestMergeWithPropertyBranchDeleteConflict()) suite.addTest(MergeBotTestMergeWithPropertyTrunkDeleteConflict()) suite.addTest(MergeBotTestSingleUseCase()) suite.addTest(MergeBotTestBranchReuse()) suite.addTest(MergeBotTestMergeDependency()) suite.addTest(MergeBotTestMergeDependencyCascade()) return suite if __name__ == '__main__': unittest.main(defaultTest='suite')