source: mergebot/trunk/utils/test.py @ 57

Last change on this file since 57 was 57, checked in by retracile, 15 years ago

Mergebot: add a testcase for one of the rename conflict cases and fix the svn 1.6 code to handle it

File size: 18.0 KB
RevLine 
[16]1#!/usr/bin/python
2"""Automated tests for MergeBot
[41]3
4Run from a Trac source tree with mergebot installed system-wide.  (This needs
5to be reworked to be less cumbersome.)
[16]6"""
7
8import os
9import unittest
10import time
11import shutil
12
13from subprocess import call, Popen #, PIPE, STDOUT
14from twill.errors import TwillAssertionError
15
16
[40]17from trac.tests.functional import FunctionalTestSuite, FunctionalTester, FunctionalTwillTestCaseSetup, tc, b, logfile
18from trac.tests.functional.svntestenv import SvnFunctionalTestEnvironment
[54]19from trac.tests.contentgen import random_page, random_sentence #, random_word
[16]20
21
[17]22#class MergeBotTestEnvironment(FunctionalTestEnvironment):
23#    """Slight change to FunctionalTestEnvironment to keep the PYTHONPATH from
24#    our environment.
25#    """
26#    def start(self):
27#        """Starts the webserver"""
28#        server = Popen(["python", "./trac/web/standalone.py",
29#                        "--port=%s" % self.port, "-s",
30#                        "--basic-auth=trac,%s," % self.htpasswd,
31#                        self.tracdir],
32#                       #env={'PYTHONPATH':'.'},
33#                       stdout=logfile, stderr=logfile,
34#                      )
35#        self.pid = server.pid
36#        time.sleep(1) # Give the server time to come up
37#
38#    def _tracadmin(self, *args):
39#        """Internal utility method for calling trac-admin"""
40#        if call(["python", "./trac/admin/console.py", self.tracdir] +
41#                list(args),
42#                #env={'PYTHONPATH':'.'},
43#                stdout=logfile, stderr=logfile):
44#            raise Exception('Failed running trac-admin with %r' % (args, ))
45#
46#
47#FunctionalTestEnvironment = MergeBotTestEnvironment
[16]48
49
50class MergeBotFunctionalTester(FunctionalTester):
51    """Adds some MergeBot functionality to the functional tester."""
52    # FIXME: the tc.find( <various actions> ) checks are bogus: any ticket can
53    # satisfy them, not just the one we're working on.
[44]54    def __init__(self, trac_url, repo_url):
55        FunctionalTester.__init__(self, trac_url)
56        self.repo_url = repo_url
[16]57        self.mergeboturl = self.url + '/mergebot'
58
59    def wait_until_find(self, search, timeout=5):
60        start = time.time()
61        while time.time() - start < timeout:
62            try:
[45]63                #tc.reload() # This appears to re-POST
64                tc.go(b.get_url())
[16]65                tc.find(search)
66                return
67            except TwillAssertionError:
68                pass
69        raise TwillAssertionError("Unable to find %r within %s seconds" % (search, timeout))
70
71    def wait_until_notfind(self, search, timeout=5):
72        start = time.time()
73        while time.time() - start < timeout:
74            try:
[45]75                #tc.reload() # This appears to re-POST
76                tc.go(b.get_url())
[16]77                tc.notfind(search)
78                return
79            except TwillAssertionError:
80                pass
81        raise TwillAssertionError("Unable to notfind %r within %s seconds" % (search, timeout))
82
83    def go_to_mergebot(self):
84        tc.go(self.mergeboturl)
85        tc.url(self.mergeboturl)
86        tc.notfind('No handler matched request to /mergebot')
87
88    def branch(self, ticket_id, component, timeout=1):
89        """timeout is in seconds."""
90        self.go_to_mergebot()
91        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
92        tc.submit('Branch')
[44]93        self.wait_until_find('Nothing in the queue', timeout)
[16]94        tc.find('Rebranch')
95        tc.find('Merge')
96        tc.find('CheckMerge')
97        self.go_to_ticket(ticket_id)
98        tc.find('Created branch from .* for .*')
99        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
100                    stdout=logfile, stderr=logfile)
101        if retval:
102            raise Exception('svn ls failed with exit code %s' % retval)
103
[57]104    def _rebranch(self, ticket_id, component, search, timeout=15):
[16]105        """timeout is in seconds."""
106        self.go_to_mergebot()
107        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
108        tc.submit('Rebranch')
[44]109        self.wait_until_find('Nothing in the queue', timeout)
[16]110        tc.find('Rebranch')
111        tc.find('Merge')
112        tc.find('CheckMerge')
113        self.go_to_ticket(ticket_id)
[57]114        tc.find(search)
[16]115        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
116                    stdout=logfile, stderr=logfile)
117        if retval:
118            raise Exception('svn ls failed with exit code %s' % retval)
119
[57]120    def rebranch(self, ticket_id, component, timeout=15):
121        self._rebranch(ticket_id, component, 'Rebranched from .* for .*', timeout)
122
123    def rebranch_conflict(self, ticket_id, component, timeout=15):
124        self._rebranch(ticket_id, component, 'There were conflicts on rebranching', timeout)
125
[16]126    def merge(self, ticket_id, component, timeout=5):
127        """timeout is in seconds."""
128        self.go_to_mergebot()
129        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
130        tc.submit('Merge')
[44]131        self.wait_until_find('Nothing in the queue', timeout)
[16]132        tc.find('Branch')
133        self.go_to_ticket(ticket_id)
134        tc.find('Merged .* to .* for')
135        # TODO: We may want to change this to remove the "dead" branch
136        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
137                    stdout=logfile, stderr=logfile)
138        if retval:
139            raise Exception('svn ls failed with exit code %s' % retval)
140
141    def checkmerge(self, ticket_id, component, timeout=5):
142        """timeout is in seconds."""
143        self.go_to_mergebot()
144        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
145        tc.submit('CheckMerge')
[44]146        self.wait_until_find('Nothing in the queue', timeout)
[16]147        tc.find('Rebranch')
148        tc.find('Merge')
149        tc.find('CheckMerge')
150        self.go_to_ticket(ticket_id)
151        tc.find('while checking merge of')
152        # TODO: We may want to change this to remove the "dead" branch
153        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
154                    stdout=logfile, stderr=logfile)
155        if retval:
156            raise Exception('svn ls failed with exit code %s' % retval)
157
158
159class MergeBotTestSuite(FunctionalTestSuite):
160    def setUp(self):
161        port = 8889
162        baseurl = "http://localhost:%s" % port
[40]163        self._testenv = SvnFunctionalTestEnvironment("testenv%s" % port, port, baseurl)
[16]164
165        # Configure mergebot
166        env = self._testenv.get_trac_environment()
167        env.config.set('components', 'mergebot.web_ui.mergebotmodule', 'enabled')
[40]168        env.config.save()
[44]169        os.mkdir(os.path.join("testenv%s" % port, 'trac', 'mergebot'))
[40]170        self._testenv._tracadmin('upgrade') # sets up the bulk of the mergebot config
171        env.config.parse_if_needed()
[16]172        env.config.set('mergebot', 'repository_url', self._testenv.repo_url())
173        env.config.set('logging', 'log_type', 'file')
174        env.config.save()
175        env.config.parse_if_needed()
176
177        self._testenv.start()
[44]178        self._tester = MergeBotFunctionalTester(baseurl, self._testenv.repo_url())
[47]179        os.system('mergebotdaemon -f "%s" > %s/mergebotdaemon.log 2>&1 &' % (self._testenv.tracdir, self._testenv.tracdir))
[40]180        self.fixture = (self._testenv, self._tester)
[16]181
182        # Setup some common component stuff for MergeBot's use:
183        svnurl = self._testenv.repo_url()
184        for component in ['stuff', 'flagship', 'submarine']:
185            self._tester.create_component(component)
186            if call(['svn', '-m', 'Create tree for "%s".' % component, 'mkdir',
187                     svnurl + '/' + component,
188                     svnurl + '/' + component + '/trunk',
189                     svnurl + '/' + component + '/tags',
190                     svnurl + '/' + component + '/branches'],
191                    stdout=logfile, stderr=logfile):
192                raise Exception("svn mkdir failed")
193
194        self._tester.create_version('trunk')
195
196
[51]197class FunctionalSvnTestCaseSetup(FunctionalTwillTestCaseSetup):
198    def get_workdir(self):
199        return os.path.join(self._testenv.dirname, self.__class__.__name__)
200
201    def checkout(self, ticket_id=None):
202        """checkout a working copy of the branch for the given ticket, or trunk if none given"""
203        if ticket_id is None:
204            svnurl = self._testenv.repo_url() + '/stuff/trunk'
205        else:
206            svnurl = self._testenv.repo_url() + '/stuff/branches/ticket-%s' % ticket_id
207        retval = call(['svn', 'checkout', svnurl, self.get_workdir()],
208            stdout=logfile, stderr=logfile)
209        self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval))
210
211    def switch(self, ticket_id=None):
212        if ticket_id is None:
213            svnurl = self._testenv.repo_url() + '/stuff/trunk'
214        else:
215            svnurl = self._testenv.repo_url() + '/stuff/branches/ticket-%s' % ticket_id
216        retval = call(['svn', 'switch', svnurl, self.get_workdir()],
217            stdout=logfile, stderr=logfile)
218        self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval))
219
220    def add_new_file(self, filename=None):
221        workdir = self.get_workdir()
222        if filename is None:
223            newfile = os.path.join(workdir, self.__class__.__name__)
224        else:
225            newfile = os.path.join(workdir, filename)
226        open(newfile, 'w').write(random_page())
227        retval = call(['svn', 'add', newfile],
228            cwd=workdir,
229            stdout=logfile, stderr=logfile)
230        self.assertEqual(retval, 0, "svn add failed with error %s" % (retval))
231
232    def commit(self, message, files=None):
233        if files is None:
234            files = ['.']
[54]235        commit_message = self.__class__.__name__ + ": " + message
236        retval = call(['svn', 'commit', '-m', commit_message] + list(files),
[51]237            cwd=self.get_workdir(),
238            stdout=logfile, stderr=logfile)
239        self.assertEqual(retval, 0, "svn commit failed with error %s" % (retval))
240
[57]241    def mv(self, oldname, newname):
242        retval = call(['svn', 'mv', oldname, newname],
243            cwd=self.get_workdir(),
244            stdout=logfile, stderr=logfile)
245        self.assertEqual(retval, 0, "svn mv failed with error %s" % (retval))
[51]246
[57]247
[17]248class MergeBotTestEnabled(FunctionalTwillTestCaseSetup):
[16]249    def runTest(self):
250        self._tester.logout()
251        tc.go(self._tester.url)
252        self._tester.login('admin')
253        tc.follow('MergeBot')
254        mergeboturl = self._tester.url + '/mergebot'
255        tc.url(mergeboturl)
256        tc.notfind('No handler matched request to /mergebot')
257
258
[17]259class MergeBotTestNoVersion(FunctionalTwillTestCaseSetup):
[16]260    """Verify that if a ticket does not have the version field set, it will not
261    appear in the MergeBot list.
262    """
263    def runTest(self):
264        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
265            info={'component':'stuff', 'version':''})
266        tc.follow('MergeBot')
267        tc.notfind(self.__class__.__name__)
268
269
[17]270class MergeBotTestBranch(FunctionalTwillTestCaseSetup):
[16]271    def runTest(self):
272        """Verify that the 'branch' button works"""
273        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
274            info={'component':'stuff', 'version':'trunk'})
275        self._tester.branch(ticket_id, 'stuff')
276
277
[17]278class MergeBotTestRebranch(FunctionalTwillTestCaseSetup):
[16]279    def runTest(self):
280        """Verify that the 'rebranch' button works"""
281        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
282            info={'component':'stuff', 'version':'trunk'})
283        self._tester.branch(ticket_id, 'stuff')
284        self._tester.rebranch(ticket_id, 'stuff')
285
286
[17]287class MergeBotTestMerge(FunctionalTwillTestCaseSetup):
[16]288    def runTest(self):
289        """Verify that the 'merge' button works"""
290        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
291            info={'component':'stuff', 'version':'trunk'})
292        self._tester.branch(ticket_id, 'stuff')
293        self._tester.merge(ticket_id, 'stuff')
294
295
[17]296class MergeBotTestCheckMerge(FunctionalTwillTestCaseSetup):
[16]297    def runTest(self):
298        """Verify that the 'checkmerge' button works"""
299        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
300            info={'component':'stuff', 'version':'trunk'})
301        self._tester.branch(ticket_id, 'stuff')
302        self._tester.checkmerge(ticket_id, 'stuff')
303
304
[51]305class MergeBotTestRebranchWithChange(FunctionalSvnTestCaseSetup):
[46]306    def runTest(self):
307        """Verify that the 'rebranch' button works with changes on the branch"""
308        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
309            info={'component':'stuff', 'version':'trunk'})
310        self._tester.branch(ticket_id, 'stuff')
311
312        # checkout a working copy & make a change
[51]313        self.checkout(ticket_id)
[46]314        # Create & add a new file
[51]315        self.add_new_file()
316        self.commit('Add a new file')
[46]317
318        self._tester.rebranch(ticket_id, 'stuff')
319
320
[51]321class MergeBotTestRebranchWithChangeAndTrunkChange(FunctionalSvnTestCaseSetup):
322    def runTest(self):
[52]323        """Verify that the 'rebranch' button works with changes on the branch and trunk"""
[51]324        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
325            info={'component':'stuff', 'version':'trunk'})
326        self._tester.branch(ticket_id, 'stuff')
327
328        # checkout a working copy & make a change
329        self.checkout(ticket_id)
330        # Create & add a new file
331        basename = self.__class__.__name__
332        self.add_new_file(basename + '-ticket')
333        self.commit('Add a new file on ticket')
334        self.switch()
335        self.add_new_file(basename + '-trunk')
336        self.commit('Add a new file on trunk')
337
338        self._tester.rebranch(ticket_id, 'stuff')
339
340
[54]341class MergeBotTestRebranchWithConflict(FunctionalSvnTestCaseSetup):
342    def runTest(self):
343        """Verify that the 'rebranch' button works with conflicts on the branch and trunk"""
344        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
345            info={'component':'stuff', 'version':'trunk'})
346        basename = self.__class__.__name__
347
348        # create a file in which to have conflicts
349        self.checkout()
350        self.add_new_file(basename)
351        self.commit('Add a new file on trunk')
352
353        # create the branch
354        self._tester.branch(ticket_id, 'stuff')
355
356        # modify the file on trunk
357        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
358        self.commit('Modify the file on trunk')
359
360        # modify the file on the branch
361        self.switch(ticket_id)
362        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
363        self.commit('Modify the file on branch')
364
[57]365        # rebranch, make sure it shows a conflict
366        self._tester.rebranch_conflict(ticket_id, 'stuff')
[54]367
368
[57]369class MergeBotTestRebranchWithBranchRenameConflict(FunctionalSvnTestCaseSetup):
370    def runTest(self):
371        """Verify that the 'rebranch' button works when a file renamed on the branch was modified on trunk"""
372        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
373            info={'component':'stuff', 'version':'trunk'})
374        basename = self.__class__.__name__
375
376        # create a file in which to have conflicts
377        self.checkout()
378        self.add_new_file(basename)
379        self.commit('Add a new file on trunk')
380
381        # create the branch
382        self._tester.branch(ticket_id, 'stuff')
383
384        # modify the file on trunk
385        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
386        self.commit('Modify the file on trunk')
387
388        # rename the file on the branch
389        self.switch(ticket_id)
390        self.mv(basename, basename + '-renamed')
391        self.commit('Rename the file on branch')
392
393        self._tester.rebranch_conflict(ticket_id, 'stuff')
394
395
[17]396class MergeBotTestSingleUseCase(FunctionalTwillTestCaseSetup):
[16]397    def runTest(self):
398        """Create a branch, make a change, checkmerge, and merge it."""
399        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
400            info={'component':'stuff', 'version':'trunk'})
401        self._tester.branch(ticket_id, 'stuff')
402        # checkout a working copy & make a change
403        svnurl = self._testenv.repo_url()
404        workdir = os.path.join(self._testenv.dirname, self.__class__.__name__)
405        retval = call(['svn', 'checkout', svnurl + '/stuff/branches/ticket-%s' % ticket_id, workdir],
406            stdout=logfile, stderr=logfile)
407        self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval))
408        # Create & add a new file
409        newfile = os.path.join(workdir, self.__class__.__name__)
410        open(newfile, 'w').write(random_page())
411        retval = call(['svn', 'add', self.__class__.__name__],
412            cwd=workdir,
413            stdout=logfile, stderr=logfile)
414        self.assertEqual(retval, 0, "svn add failed with error %s" % (retval))
415        retval = call(['svn', 'commit', '-m', 'Add a new file', self.__class__.__name__],
416            cwd=workdir,
417            stdout=logfile, stderr=logfile)
418        self.assertEqual(retval, 0, "svn commit failed with error %s" % (retval))
419
420        self._tester.checkmerge(ticket_id, 'stuff')
421        self._tester.merge(ticket_id, 'stuff')
422
423        shutil.rmtree(workdir) # cleanup working copy
424
425
426def suite():
427    suite = MergeBotTestSuite()
428    suite.addTest(MergeBotTestEnabled())
429    suite.addTest(MergeBotTestNoVersion())
430    suite.addTest(MergeBotTestBranch())
[45]431    suite.addTest(MergeBotTestRebranch())
[16]432    suite.addTest(MergeBotTestCheckMerge())
[45]433    suite.addTest(MergeBotTestMerge())
[46]434    suite.addTest(MergeBotTestRebranchWithChange())
[51]435    suite.addTest(MergeBotTestRebranchWithChangeAndTrunkChange())
[54]436    suite.addTest(MergeBotTestRebranchWithConflict())
[57]437    suite.addTest(MergeBotTestRebranchWithBranchRenameConflict())
[16]438    suite.addTest(MergeBotTestSingleUseCase())
439    return suite
440
441if __name__ == '__main__':
442    unittest.main(defaultTest='suite')
Note: See TracBrowser for help on using the repository browser.