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

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

Mergebot: lengthen timeouts to support slower machines

File size: 36.8 KB
Line 
1#!/usr/bin/python
2"""Automated tests for MergeBot
3
4Run from a Trac source tree with mergebot installed system-wide.  (This needs
5to be reworked to be less cumbersome.)
6"""
7
8# TODO: The testcases need to be extended to cover the following:
9# - using a ticket #number as a version
10# - using a release version number as a version
11# - verify inter-ticket dependency checking
12# - verify failure cascades through inter-ticket dependencies
13# - change the version of a ticket, rebranch, and merge
14
15import os
16import unittest
17import time
18import shutil
19
20from subprocess import call, Popen #, PIPE, STDOUT
21from twill.errors import TwillAssertionError
22
23
24from trac.tests.functional import FunctionalTestSuite, FunctionalTester, FunctionalTwillTestCaseSetup, tc, b, logfile
25from trac.tests.functional.svntestenv import SvnFunctionalTestEnvironment
26from trac.tests.contentgen import random_page, random_sentence #, random_word
27
28
29#class MergeBotTestEnvironment(FunctionalTestEnvironment):
30#    """Slight change to FunctionalTestEnvironment to keep the PYTHONPATH from
31#    our environment.
32#    """
33#    def start(self):
34#        """Starts the webserver"""
35#        server = Popen(["python", "./trac/web/standalone.py",
36#                        "--port=%s" % self.port, "-s",
37#                        "--basic-auth=trac,%s," % self.htpasswd,
38#                        self.tracdir],
39#                       #env={'PYTHONPATH':'.'},
40#                       stdout=logfile, stderr=logfile,
41#                      )
42#        self.pid = server.pid
43#        time.sleep(1) # Give the server time to come up
44#
45#    def _tracadmin(self, *args):
46#        """Internal utility method for calling trac-admin"""
47#        if call(["python", "./trac/admin/console.py", self.tracdir] +
48#                list(args),
49#                #env={'PYTHONPATH':'.'},
50#                stdout=logfile, stderr=logfile):
51#            raise Exception('Failed running trac-admin with %r' % (args, ))
52#
53#
54#FunctionalTestEnvironment = MergeBotTestEnvironment
55
56
57class MergeBotFunctionalTester(FunctionalTester):
58    """Adds some MergeBot functionality to the functional tester."""
59    # FIXME: the tc.find( <various actions> ) checks are bogus: any ticket can
60    # satisfy them, not just the one we're working on.
61    def __init__(self, trac_url, repo_url):
62        FunctionalTester.__init__(self, trac_url)
63        self.repo_url = repo_url
64        self.mergeboturl = self.url + '/mergebot'
65
66    def wait_until_find(self, search, timeout=5):
67        start = time.time()
68        while time.time() - start < timeout:
69            try:
70                #tc.reload() # This appears to re-POST
71                tc.go(b.get_url())
72                tc.find(search)
73                return
74            except TwillAssertionError:
75                pass
76        raise TwillAssertionError("Unable to find %r within %s seconds" % (search, timeout))
77
78    def wait_until_notfind(self, search, timeout=5):
79        start = time.time()
80        while time.time() - start < timeout:
81            try:
82                #tc.reload() # This appears to re-POST
83                tc.go(b.get_url())
84                tc.notfind(search)
85                return
86            except TwillAssertionError:
87                pass
88        raise TwillAssertionError("Unable to notfind %r within %s seconds" % (search, timeout))
89
90    def go_to_mergebot(self):
91        tc.go(self.mergeboturl)
92        tc.url(self.mergeboturl)
93        tc.notfind('No handler matched request to /mergebot')
94
95    def queue_branch(self, ticket_id):
96        self.go_to_mergebot()
97        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
98        tc.submit('Branch')
99
100    def branch(self, ticket_id, component, timeout=1):
101        """timeout is in seconds."""
102        self.queue_branch(ticket_id)
103        self.wait_until_find('Nothing in the queue', timeout)
104        tc.find('Rebranch')
105        tc.find('Merge')
106        tc.find('CheckMerge')
107        self.go_to_ticket(ticket_id)
108        tc.find('Created branch from .* for .*')
109        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
110                    stdout=logfile, stderr=logfile)
111        if retval:
112            raise Exception('svn ls failed with exit code %s' % retval)
113
114    def queue_rebranch(self, ticket_id):
115        self.go_to_mergebot()
116        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
117        tc.submit('Rebranch')
118
119    def _rebranch(self, ticket_id, component, search, timeout=15):
120        """timeout is in seconds."""
121        self.queue_rebranch(ticket_id)
122        self.wait_until_find('Nothing in the queue', timeout)
123        tc.find('Rebranch')
124        tc.find('Merge')
125        tc.find('CheckMerge')
126        self.go_to_ticket(ticket_id)
127        tc.find(search)
128        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
129                    stdout=logfile, stderr=logfile)
130        if retval:
131            raise Exception('svn ls failed with exit code %s' % retval)
132
133    def rebranch(self, ticket_id, component, timeout=15):
134        self._rebranch(ticket_id, component, 'Rebranched from .* for .*', timeout)
135
136    def rebranch_conflict(self, ticket_id, component, timeout=15):
137        self._rebranch(ticket_id, component, 'There were conflicts on rebranching', timeout)
138
139    def queue_merge(self, ticket_id):
140        self.go_to_mergebot()
141        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
142        tc.submit('Merge')
143
144    def merge(self, ticket_id, component, timeout=5):
145        self._merge(ticket_id, component, 'Merged .* to .* for', timeout)
146
147    def merge_conflict(self, ticket_id, component, timeout=5):
148        self._merge(ticket_id, component, 'Found [0-9]+ conflicts? in attempt to merge ', timeout)
149
150    def _merge(self, ticket_id, component, search, timeout=5):
151        """timeout is in seconds."""
152        self.queue_merge(ticket_id)
153        self.wait_until_find('Nothing in the queue', timeout)
154        tc.find('Branch')
155        self.go_to_ticket(ticket_id)
156        tc.find(search)
157        # TODO: We may want to change this to remove the "dead" branch
158        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
159                    stdout=logfile, stderr=logfile)
160        if retval:
161            raise Exception('svn ls failed with exit code %s' % retval)
162
163    def queue_checkmerge(self, ticket_id):
164        self.go_to_mergebot()
165        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
166        tc.submit('CheckMerge')
167
168    def checkmerge(self, ticket_id, component, timeout=5):
169        """timeout is in seconds."""
170        self.queue_checkmerge(ticket_id)
171        self.wait_until_find('Nothing in the queue', timeout)
172        tc.find('Rebranch')
173        tc.find('Merge')
174        tc.find('CheckMerge')
175        self.go_to_ticket(ticket_id)
176        tc.find('while checking merge of')
177        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
178                    stdout=logfile, stderr=logfile)
179        if retval:
180            raise Exception('svn ls failed with exit code %s' % retval)
181
182    def wait_for_empty_queue(self, timeout=10):
183        self.go_to_mergebot()
184        self.wait_until_find('Nothing in the queue', timeout)
185
186
187class MergeBotTestSuite(FunctionalTestSuite):
188    def setUp(self):
189        port = 8889
190        baseurl = "http://localhost:%s" % port
191        self._testenv = SvnFunctionalTestEnvironment("testenv%s" % port, port, baseurl)
192
193        # Configure mergebot
194        env = self._testenv.get_trac_environment()
195        env.config.set('components', 'mergebot.web_ui.mergebotmodule', 'enabled')
196        env.config.save()
197        os.mkdir(os.path.join("testenv%s" % port, 'trac', 'mergebot'))
198        self._testenv._tracadmin('upgrade') # sets up the bulk of the mergebot config
199        env.config.parse_if_needed()
200        env.config.set('mergebot', 'repository_url', self._testenv.repo_url())
201        env.config.set('logging', 'log_type', 'file')
202        env.config.save()
203        env.config.parse_if_needed()
204
205        self._testenv.start()
206        self._tester = MergeBotFunctionalTester(baseurl, self._testenv.repo_url())
207        os.system('mergebotdaemon -f "%s" > %s/mergebotdaemon.log 2>&1 &' % (self._testenv.tracdir, self._testenv.tracdir))
208        self.fixture = (self._testenv, self._tester)
209
210        # Setup some common component stuff for MergeBot's use:
211        svnurl = self._testenv.repo_url()
212        for component in ['stuff', 'flagship', 'submarine']:
213            self._tester.create_component(component)
214            if call(['svn', '-m', 'Create tree for "%s".' % component, 'mkdir',
215                     svnurl + '/' + component,
216                     svnurl + '/' + component + '/trunk',
217                     svnurl + '/' + component + '/tags',
218                     svnurl + '/' + component + '/branches'],
219                    stdout=logfile, stderr=logfile):
220                raise Exception("svn mkdir failed")
221
222        self._tester.create_version('trunk')
223
224
225class FunctionalSvnTestCaseSetup(FunctionalTwillTestCaseSetup):
226    def get_workdir(self):
227        return os.path.join(self._testenv.dirname, self.__class__.__name__)
228
229    def checkout(self, ticket_id=None):
230        """checkout a working copy of the branch for the given ticket, or trunk if none given"""
231        if ticket_id is None:
232            svnurl = self._testenv.repo_url() + '/stuff/trunk'
233        else:
234            svnurl = self._testenv.repo_url() + '/stuff/branches/ticket-%s' % ticket_id
235        retval = call(['svn', 'checkout', svnurl, self.get_workdir()],
236            stdout=logfile, stderr=logfile)
237        self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval))
238
239    def cleanup(self):
240        shutil.rmtree(self.get_workdir())
241
242    def switch(self, ticket_id=None):
243        if ticket_id is None:
244            svnurl = self._testenv.repo_url() + '/stuff/trunk'
245        else:
246            svnurl = self._testenv.repo_url() + '/stuff/branches/ticket-%s' % ticket_id
247        retval = call(['svn', 'switch', svnurl, self.get_workdir()],
248            stdout=logfile, stderr=logfile)
249        self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval))
250
251    def add_new_file(self, filename=None, file_size=None):
252        workdir = self.get_workdir()
253        if filename is None:
254            newfile = os.path.join(workdir, self.__class__.__name__)
255        else:
256            newfile = os.path.join(workdir, filename)
257        if file_size is None:
258            data = random_page()
259        else:
260            data = ''
261            while len(data) < file_size:
262                data += random_page()
263            data = data[:file_size]
264        open(newfile, 'w').write(data)
265        retval = call(['svn', 'add', newfile],
266            cwd=workdir,
267            stdout=logfile, stderr=logfile)
268        self.assertEqual(retval, 0, "svn add failed with error %s" % (retval))
269
270    def commit(self, message, files=None):
271        if files is None:
272            files = ['.']
273        commit_message = self.__class__.__name__ + ": " + message
274        retval = call(['svn', 'commit', '-m', commit_message] + list(files),
275            cwd=self.get_workdir(),
276            stdout=logfile, stderr=logfile)
277        self.assertEqual(retval, 0, "svn commit failed with error %s" % (retval))
278
279    def mv(self, oldname, newname):
280        retval = call(['svn', 'mv', oldname, newname],
281            cwd=self.get_workdir(),
282            stdout=logfile, stderr=logfile)
283        self.assertEqual(retval, 0, "svn mv failed with error %s" % (retval))
284
285    def propset(self, propname, propvalue, filename):
286        retval = call(['svn', 'propset', propname, propvalue, filename],
287            cwd=self.get_workdir(),
288            stdout=logfile, stderr=logfile)
289        self.assertEqual(retval, 0, "svn prposet failed with error %s" % (retval))
290       
291    def propdel(self, propname, filename):
292        retval = call(['svn', 'propdel', propname, filename],
293            cwd=self.get_workdir(),
294            stdout=logfile, stderr=logfile)
295        self.assertEqual(retval, 0, "svn prposet failed with error %s" % (retval))
296
297    def rm(self, filename):
298        retval = call(['svn', 'rm', filename],
299            cwd=self.get_workdir(),
300            stdout=logfile, stderr=logfile)
301        self.assertEqual(retval, 0, "svn rm failed with error %s" % (retval))
302
303
304class MergeBotTestEnabled(FunctionalTwillTestCaseSetup):
305    def runTest(self):
306        self._tester.logout()
307        tc.go(self._tester.url)
308        self._tester.login('admin')
309        tc.follow('MergeBot')
310        mergeboturl = self._tester.url + '/mergebot'
311        tc.url(mergeboturl)
312        tc.notfind('No handler matched request to /mergebot')
313
314
315class MergeBotTestNoVersion(FunctionalTwillTestCaseSetup):
316    """Verify that if a ticket does not have the version field set, it will not
317    appear in the MergeBot list.
318    """
319    def runTest(self):
320        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
321            info={'component':'stuff', 'version':''})
322        tc.follow('MergeBot')
323        tc.notfind(self.__class__.__name__)
324
325
326class MergeBotTestBranch(FunctionalTwillTestCaseSetup):
327    def runTest(self):
328        """Verify that the 'branch' button works"""
329        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
330            info={'component':'stuff', 'version':'trunk'})
331        self._tester.branch(ticket_id, 'stuff')
332
333
334class MergeBotTestRebranch(FunctionalTwillTestCaseSetup):
335    def runTest(self):
336        """Verify that the 'rebranch' button works"""
337        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
338            info={'component':'stuff', 'version':'trunk'})
339        self._tester.branch(ticket_id, 'stuff')
340        self._tester.rebranch(ticket_id, 'stuff')
341
342
343class MergeBotTestMerge(FunctionalTwillTestCaseSetup):
344    def runTest(self):
345        """Verify that the 'merge' button works"""
346        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
347            info={'component':'stuff', 'version':'trunk'})
348        self._tester.branch(ticket_id, 'stuff')
349        self._tester.merge(ticket_id, 'stuff')
350
351
352class MergeBotTestMergeWithChange(FunctionalSvnTestCaseSetup):
353    def runTest(self):
354        """Verify that the 'merge' button works with changes on the branch"""
355        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
356            info={'component':'stuff', 'version':'trunk'})
357        self._tester.branch(ticket_id, 'stuff')
358
359        # checkout a working copy & make a change
360        self.checkout(ticket_id)
361        # Create & add a new file
362        self.add_new_file()
363        self.commit('Add a new file')
364
365        self._tester.merge(ticket_id, 'stuff')
366
367        self.cleanup()
368
369
370class MergeBotTestMergeWithChangeAndTrunkChange(FunctionalSvnTestCaseSetup):
371    def runTest(self):
372        """Verify that the 'merge' button works with changes on the branch and trunk"""
373        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
374            info={'component':'stuff', 'version':'trunk'})
375        self._tester.branch(ticket_id, 'stuff')
376
377        # checkout a working copy & make a change
378        self.checkout(ticket_id)
379        # Create & add a new file
380        basename = self.__class__.__name__
381        self.add_new_file(basename + '-ticket')
382        self.commit('Add a new file on ticket')
383        self.switch()
384        self.add_new_file(basename + '-trunk')
385        self.commit('Add a new file on trunk')
386
387        self._tester.merge(ticket_id, 'stuff')
388        self.cleanup()
389
390
391class MergeBotTestMergeWithConflict(FunctionalSvnTestCaseSetup):
392    def runTest(self):
393        """Verify that the 'merge' button detects conflicts between the branch and trunk"""
394        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
395            info={'component':'stuff', 'version':'trunk'})
396        basename = self.__class__.__name__
397
398        # create a file in which to have conflicts
399        self.checkout()
400        self.add_new_file(basename)
401        self.commit('Add a new file on trunk')
402
403        # create the branch
404        self._tester.branch(ticket_id, 'stuff')
405
406        # modify the file on trunk
407        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
408        self.commit('Modify the file on trunk')
409
410        # modify the file on the branch
411        self.switch(ticket_id)
412        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
413        self.commit('Modify the file on branch')
414
415        # merge, make sure it shows a conflict
416        self._tester.merge_conflict(ticket_id, 'stuff')
417
418        self.cleanup()
419
420
421class MergeBotTestMergeWithPropertyConflict(FunctionalSvnTestCaseSetup):
422    def runTest(self):
423        """Verify that the 'merge' button detects property conflicts between the branch and trunk"""
424        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
425            info={'component':'stuff', 'version':'trunk'})
426        basename = self.__class__.__name__
427
428        self.checkout()
429        self.propset('svn:ignore', basename, '.')
430        self.commit('set property on trunk')
431
432        # create the branch
433        self._tester.branch(ticket_id, 'stuff')
434
435        # modify the property on trunk
436        self.propset('svn:ignore', basename + '\ntrunk', '.')
437        self.commit('Modify the property on trunk')
438
439        # modify the property on the branch
440        self.switch(ticket_id)
441        self.propset('svn:ignore', basename + '\nbranch', '.')
442        self.commit('Modify the property on branch')
443
444        # merge, make sure it shows a conflict
445        self._tester.merge_conflict(ticket_id, 'stuff')
446
447        self.cleanup()
448
449
450class MergeBotTestMergeWithPropertyBranchDeleteConflict(FunctionalSvnTestCaseSetup):
451    def runTest(self):
452        """Verify that the 'merge' button detects property deleted on branch and modified on trunk"""
453        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
454            info={'component':'stuff', 'version':'trunk'})
455        basename = self.__class__.__name__
456
457        self.checkout()
458        self.propset('svn:ignore', basename, '.')
459        self.commit('set property on trunk')
460
461        # create the branch
462        self._tester.branch(ticket_id, 'stuff')
463
464        # modify the property on trunk
465        self.propset('svn:ignore', basename + '\ntrunk', '.')
466        self.commit('Modify the property on trunk')
467
468        # delete the property on the branch
469        self.switch(ticket_id)
470        self.propdel('svn:ignore', '.')
471        self.commit('Delete the property on branch')
472
473        # merge, make sure it shows a conflict
474        self._tester.merge_conflict(ticket_id, 'stuff')
475
476        self.cleanup()
477
478
479class MergeBotTestMergeWithPropertyTrunkDeleteConflict(FunctionalSvnTestCaseSetup):
480    def runTest(self):
481        """Verify that the 'merge' button detects property deleted on trunk and modified on branch"""
482        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
483            info={'component':'stuff', 'version':'trunk'})
484        basename = self.__class__.__name__
485
486        self.checkout()
487        self.propset('svn:ignore', basename, '.')
488        self.commit('set property on trunk')
489
490        # create the branch
491        self._tester.branch(ticket_id, 'stuff')
492
493        # delete the property on trunk
494        self.propdel('svn:ignore', '.')
495        self.commit('Delete the property on trunk')
496
497        # delete the property on the branch
498        self.switch(ticket_id)
499        self.propset('svn:ignore', basename + '\nbranch', '.')
500        self.commit('Modify the property on branch')
501
502        # merge, make sure it shows a conflict
503        self._tester.merge_conflict(ticket_id, 'stuff')
504
505        self.cleanup()
506
507
508class MergeBotTestMergeWithBranchRenameConflict(FunctionalSvnTestCaseSetup):
509    def runTest(self):
510        """Verify that the 'merge' button detects a conflict when a file renamed on the branch was modified on trunk"""
511        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
512            info={'component':'stuff', 'version':'trunk'})
513        basename = self.__class__.__name__
514
515        # create a file in which to have conflicts
516        self.checkout()
517        self.add_new_file(basename)
518        self.commit('Add a new file on trunk')
519
520        # create the branch
521        self._tester.branch(ticket_id, 'stuff')
522
523        # modify the file on trunk
524        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
525        self.commit('Modify the file on trunk')
526
527        # rename the file on the branch
528        self.switch(ticket_id)
529        self.mv(basename, basename + '-renamed')
530        self.commit('Rename the file on the branch')
531
532        self._tester.merge_conflict(ticket_id, 'stuff')
533
534        self.cleanup()
535
536
537class MergeBotTestMergeWithTrunkRenameConflict(FunctionalSvnTestCaseSetup):
538    def runTest(self):
539        """Verify that the 'merge' button detects conflicts when a file renamed on trunk was modified on the branch"""
540        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
541            info={'component':'stuff', 'version':'trunk'})
542        basename = self.__class__.__name__
543
544        # create a file in which to have conflicts
545        self.checkout()
546        self.add_new_file(basename)
547        self.commit('Add a new file on trunk')
548
549        # create the branch
550        self._tester.branch(ticket_id, 'stuff')
551
552        # rename the file on trunk
553        self.mv(basename, basename + '-renamed')
554        self.commit('Rename the file on trunk')
555
556        # rename the file on the branch
557        self.switch(ticket_id)
558        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
559        self.commit('Modify the file on the branch')
560
561        # make sure it finds the conflict
562        self._tester.merge_conflict(ticket_id, 'stuff')
563
564        self.cleanup()
565
566
567class MergeBotTestCheckMerge(FunctionalTwillTestCaseSetup):
568    def runTest(self):
569        """Verify that the 'checkmerge' button works"""
570        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
571            info={'component':'stuff', 'version':'trunk'})
572        self._tester.branch(ticket_id, 'stuff')
573        self._tester.checkmerge(ticket_id, 'stuff')
574
575
576class MergeBotTestRebranchWithChange(FunctionalSvnTestCaseSetup):
577    def runTest(self):
578        """Verify that the 'rebranch' button works with changes on the branch"""
579        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
580            info={'component':'stuff', 'version':'trunk'})
581        self._tester.branch(ticket_id, 'stuff')
582
583        # checkout a working copy & make a change
584        self.checkout(ticket_id)
585        # Create & add a new file
586        self.add_new_file()
587        self.commit('Add a new file')
588
589        self._tester.rebranch(ticket_id, 'stuff')
590        self.cleanup()
591
592
593class MergeBotTestRebranchWithChangeAndTrunkChange(FunctionalSvnTestCaseSetup):
594    def runTest(self):
595        """Verify that the 'rebranch' button works with changes on the branch and trunk"""
596        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
597            info={'component':'stuff', 'version':'trunk'})
598        self._tester.branch(ticket_id, 'stuff')
599
600        # checkout a working copy & make a change
601        self.checkout(ticket_id)
602        # Create & add a new file
603        basename = self.__class__.__name__
604        self.add_new_file(basename + '-ticket')
605        self.commit('Add a new file on ticket')
606        self.switch()
607        self.add_new_file(basename + '-trunk')
608        self.commit('Add a new file on trunk')
609
610        self._tester.rebranch(ticket_id, 'stuff')
611        self.cleanup()
612
613
614class MergeBotTestRebranchWithConflict(FunctionalSvnTestCaseSetup):
615    def runTest(self):
616        """Verify that the 'rebranch' button detects conflicts between the branch and trunk"""
617        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
618            info={'component':'stuff', 'version':'trunk'})
619        basename = self.__class__.__name__
620
621        # create a file in which to have conflicts
622        self.checkout()
623        self.add_new_file(basename)
624        self.commit('Add a new file on trunk')
625
626        # create the branch
627        self._tester.branch(ticket_id, 'stuff')
628
629        # modify the file on trunk
630        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
631        self.commit('Modify the file on trunk')
632
633        # modify the file on the branch
634        self.switch(ticket_id)
635        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
636        self.commit('Modify the file on branch')
637
638        # rebranch, make sure it shows a conflict
639        self._tester.rebranch_conflict(ticket_id, 'stuff')
640
641        self.cleanup()
642
643
644class MergeBotTestRebranchWithPropertyConflict(FunctionalSvnTestCaseSetup):
645    def runTest(self):
646        """Verify that the 'rebranch' button detects property conflicts between the branch and trunk"""
647        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
648            info={'component':'stuff', 'version':'trunk'})
649        basename = self.__class__.__name__
650
651        self.checkout()
652        self.propset('svn:ignore', basename, '.')
653        self.commit('set property on trunk')
654
655        # create the branch
656        self._tester.branch(ticket_id, 'stuff')
657
658        # modify the property on trunk
659        self.propset('svn:ignore', basename + '\ntrunk', '.')
660        self.commit('Modify the property on trunk')
661
662        # modify the property on the branch
663        self.switch(ticket_id)
664        self.propset('svn:ignore', basename + '\nbranch', '.')
665        self.commit('Modify the property on branch')
666
667        # rebranch, make sure it shows a conflict
668        self._tester.rebranch_conflict(ticket_id, 'stuff')
669
670        self.cleanup()
671
672
673class MergeBotTestRebranchWithPropertyBranchDeleteConflict(FunctionalSvnTestCaseSetup):
674    def runTest(self):
675        """Verify that the 'rebranch' button detects property deleted on branch and modified on trunk"""
676        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
677            info={'component':'stuff', 'version':'trunk'})
678        basename = self.__class__.__name__
679
680        self.checkout()
681        self.propset('svn:ignore', basename, '.')
682        self.commit('set property on trunk')
683
684        # create the branch
685        self._tester.branch(ticket_id, 'stuff')
686
687        # modify the property on trunk
688        self.propset('svn:ignore', basename + '\ntrunk', '.')
689        self.commit('Modify the property on trunk')
690
691        # delete the property on the branch
692        self.switch(ticket_id)
693        self.propdel('svn:ignore', '.')
694        self.commit('Delete the property on branch')
695
696        # rebranch, make sure it shows a conflict
697        self._tester.rebranch_conflict(ticket_id, 'stuff')
698
699        self.cleanup()
700
701
702class MergeBotTestRebranchWithPropertyTrunkDeleteConflict(FunctionalSvnTestCaseSetup):
703    def runTest(self):
704        """Verify that the 'rebranch' button detects property deleted on trunk and modified on branch"""
705        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
706            info={'component':'stuff', 'version':'trunk'})
707        basename = self.__class__.__name__
708
709        self.checkout()
710        self.propset('svn:ignore', basename, '.')
711        self.commit('set property on trunk')
712
713        # create the branch
714        self._tester.branch(ticket_id, 'stuff')
715
716        # delete the property on trunk
717        self.propdel('svn:ignore', '.')
718        self.commit('Delete the property on trunk')
719
720        # delete the property on the branch
721        self.switch(ticket_id)
722        self.propset('svn:ignore', basename + '\nbranch', '.')
723        self.commit('Modify the property on branch')
724
725        # rebranch, make sure it shows a conflict
726        self._tester.rebranch_conflict(ticket_id, 'stuff')
727
728        self.cleanup()
729
730
731class MergeBotTestRebranchWithBranchRenameConflict(FunctionalSvnTestCaseSetup):
732    def runTest(self):
733        """Verify that the 'rebranch' button detects a conflict when a file renamed on the branch was modified on trunk"""
734        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
735            info={'component':'stuff', 'version':'trunk'})
736        basename = self.__class__.__name__
737
738        # create a file in which to have conflicts
739        self.checkout()
740        self.add_new_file(basename)
741        self.commit('Add a new file on trunk')
742
743        # create the branch
744        self._tester.branch(ticket_id, 'stuff')
745
746        # modify the file on trunk
747        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
748        self.commit('Modify the file on trunk')
749
750        # rename the file on the branch
751        self.switch(ticket_id)
752        self.mv(basename, basename + '-renamed')
753        self.commit('Rename the file on the branch')
754
755        self._tester.rebranch_conflict(ticket_id, 'stuff')
756
757        self.cleanup()
758
759
760class MergeBotTestRebranchWithTrunkRenameConflict(FunctionalSvnTestCaseSetup):
761    def runTest(self):
762        """Verify that the 'rebranch' button detects conflicts when a file renamed on trunk was modified on the branch"""
763        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
764            info={'component':'stuff', 'version':'trunk'})
765        basename = self.__class__.__name__
766
767        # create a file in which to have conflicts
768        self.checkout()
769        self.add_new_file(basename)
770        self.commit('Add a new file on trunk')
771
772        # create the branch
773        self._tester.branch(ticket_id, 'stuff')
774
775        # rename the file on trunk
776        self.mv(basename, basename + '-renamed')
777        self.commit('Rename the file on trunk')
778
779        # rename the file on the branch
780        self.switch(ticket_id)
781        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
782        self.commit('Modify the file on the branch')
783
784        # make sure it finds the conflict
785        self._tester.rebranch_conflict(ticket_id, 'stuff')
786
787        self.cleanup()
788
789
790class MergeBotTestSingleUseCase(FunctionalSvnTestCaseSetup):
791    def runTest(self):
792        """Create a branch, make a change, checkmerge, and merge it."""
793        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
794            info={'component':'stuff', 'version':'trunk'})
795        self._tester.branch(ticket_id, 'stuff')
796        # checkout a working copy & make a change
797        self.checkout(ticket_id)
798        # Create & add a new file
799        self.add_new_file()
800        self.commit('Add a new file')
801        self._tester.checkmerge(ticket_id, 'stuff')
802        self._tester.merge(ticket_id, 'stuff')
803        self.cleanup()
804
805
806class MergeBotTestBranchReuse(FunctionalSvnTestCaseSetup):
807    def runTest(self):
808        """Merge a branch, branch it again, and merge it a second time."""
809        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
810            info={'component':'stuff', 'version':'trunk'})
811        basename = self.__class__.__name__
812
813        self.checkout()
814        self.add_new_file(basename)
815        self.commit('Add a new file')
816
817        self._tester.branch(ticket_id, 'stuff')
818        self.switch(ticket_id)
819        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
820        self.commit('Make a modification')
821        self._tester.merge(ticket_id, 'stuff')
822
823        self._tester.branch(ticket_id, 'stuff')
824        self.switch(ticket_id)
825        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
826        self.commit('Make a second modification')
827        self._tester.merge(ticket_id, 'stuff')
828        self.cleanup()
829
830
831class MergeBotTestMergeDependency(FunctionalSvnTestCaseSetup):
832    def runTest(self):
833        """Merge two branches to trunk; make sure the second one shows as waiting"""
834        # This test is fundamentally racy.  The size of the files has been
835        # chosen to slow down the test long enough to get a view of the
836        # mergebot page with one of them waiting for the other to complete.
837        ticket_one = self._tester.create_ticket(summary=self.__class__.__name__ + " one",
838            info={'component':'stuff', 'version':'trunk'})
839        ticket_two = self._tester.create_ticket(summary=self.__class__.__name__ + " two",
840            info={'component':'stuff', 'version':'trunk'})
841        basename = self.__class__.__name__
842
843        self._tester.branch(ticket_one, 'stuff')
844        self._tester.branch(ticket_two, 'stuff')
845
846        self.checkout(ticket_one)
847        self.add_new_file(basename + '-one', 30*1024**2)
848        self.commit('Add a new file')
849
850        self.switch(ticket_two)
851        self.add_new_file(basename + '-two', 30*1024**2)
852        self.commit('Add a new file')
853
854        self._tester.queue_merge(ticket_two)
855        self._tester.queue_merge(ticket_one)
856
857        #self._tester.go_to_mergebot()
858        tc.find('Waiting')
859        self._tester.wait_for_empty_queue(180) # Has to be large for slow machines
860
861        self.switch()
862        self.rm(basename + '-one')
863        self.rm(basename + '-two')
864        self.commit('Drop large files so later tests don\'t have to deal with them.')
865        self.cleanup()
866
867
868class MergeBotTestMergeDependencyCascade(FunctionalSvnTestCaseSetup):
869    def runTest(self):
870        """Merge two branches to trunk; make sure the second is cancelled when
871        the first hits merge conflicts.
872        """
873        ticket_one = self._tester.create_ticket(summary=self.__class__.__name__ + " one",
874            info={'component':'stuff', 'version':'trunk'})
875        ticket_two = self._tester.create_ticket(summary=self.__class__.__name__ + " two",
876            info={'component':'stuff', 'version':'trunk'})
877        basename = self.__class__.__name__
878
879        self.checkout()
880        # create a file on trunk to work with
881        # make it large enough to give us time to queue the second ticket
882        # before this one completes its merge
883        self.add_new_file(basename, 30*1024**2)
884        self.commit('Add a new file')
885        # branch the first ticket that we will have fail to merge due to
886        # conflicts.
887        self._tester.branch(ticket_one, 'stuff')
888        # modify the file on trunk
889        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
890        self.commit('Modify the file on trunk')
891        # and modify the file on the branch
892        self.switch(ticket_one)
893        open(os.path.join(self.get_workdir(), basename), 'a').write(random_sentence())
894        self.commit('Modify the file on branch in conflicting manner')
895        # create a branch for the second ticket with a non-conflicting change
896        self._tester.branch(ticket_two, 'stuff')
897        self.switch(ticket_two)
898        self.add_new_file(basename + '-two')
899        self.commit('Add a new file')
900
901        # the stage is set.  Now we request the merge that will conflict,
902        # followed by the merge that won't, and wait for the merges to complete.
903        self._tester.queue_merge(ticket_one)
904        self._tester.queue_merge(ticket_two)
905        self._tester.wait_for_empty_queue(180) # Has to be large for slow machines
906        # Then we verify that one had conflicts, and one was cancelled.       
907        self._tester.go_to_ticket(ticket_one)
908        tc.find('Found [0-9]+ conflicts? in attempt to merge ')
909        self._tester.go_to_ticket(ticket_two)
910        tc.notfind('Merged .* to .* for')
911        # and clean up the mess
912        self.cleanup()
913
914
915def suite():
916    suite = MergeBotTestSuite()
917    suite.addTest(MergeBotTestEnabled())
918    suite.addTest(MergeBotTestNoVersion())
919    suite.addTest(MergeBotTestBranch())
920    suite.addTest(MergeBotTestRebranch())
921    suite.addTest(MergeBotTestCheckMerge())
922    suite.addTest(MergeBotTestMerge())
923    suite.addTest(MergeBotTestRebranchWithChange())
924    suite.addTest(MergeBotTestRebranchWithChangeAndTrunkChange())
925    suite.addTest(MergeBotTestRebranchWithConflict())
926    suite.addTest(MergeBotTestRebranchWithPropertyConflict())
927    suite.addTest(MergeBotTestRebranchWithBranchRenameConflict())
928    suite.addTest(MergeBotTestRebranchWithTrunkRenameConflict())
929    suite.addTest(MergeBotTestRebranchWithPropertyBranchDeleteConflict())
930    suite.addTest(MergeBotTestRebranchWithPropertyTrunkDeleteConflict())
931    suite.addTest(MergeBotTestMergeWithChange())
932    suite.addTest(MergeBotTestMergeWithChangeAndTrunkChange())
933    suite.addTest(MergeBotTestMergeWithConflict())
934    suite.addTest(MergeBotTestMergeWithPropertyConflict())
935    suite.addTest(MergeBotTestMergeWithBranchRenameConflict())
936    suite.addTest(MergeBotTestMergeWithTrunkRenameConflict())
937    suite.addTest(MergeBotTestMergeWithPropertyBranchDeleteConflict())
938    suite.addTest(MergeBotTestMergeWithPropertyTrunkDeleteConflict())
939    suite.addTest(MergeBotTestSingleUseCase())
940    suite.addTest(MergeBotTestBranchReuse())
941    suite.addTest(MergeBotTestMergeDependency())
942    suite.addTest(MergeBotTestMergeDependencyCascade())
943    return suite
944
945if __name__ == '__main__':
946    unittest.main(defaultTest='suite')
Note: See TracBrowser for help on using the repository browser.