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

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

Mergebot: refactoring tests and adding more testcases

File size: 15.2 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
8import os
9import unittest
10import time
11import shutil
12
13from subprocess import call, Popen #, PIPE, STDOUT
14from twill.errors import TwillAssertionError
15
16
17from trac.tests.functional import FunctionalTestSuite, FunctionalTester, FunctionalTwillTestCaseSetup, tc, b, logfile
18from trac.tests.functional.svntestenv import SvnFunctionalTestEnvironment
19from trac.tests.contentgen import random_page #, random_sentence, random_word
20
21
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
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.
54    def __init__(self, trac_url, repo_url):
55        FunctionalTester.__init__(self, trac_url)
56        self.repo_url = repo_url
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:
63                #tc.reload() # This appears to re-POST
64                tc.go(b.get_url())
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:
75                #tc.reload() # This appears to re-POST
76                tc.go(b.get_url())
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')
93        self.wait_until_find('Nothing in the queue', timeout)
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
104    def rebranch(self, ticket_id, component, timeout=15):
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')
109        self.wait_until_find('Nothing in the queue', timeout)
110        tc.find('Rebranch')
111        tc.find('Merge')
112        tc.find('CheckMerge')
113        self.go_to_ticket(ticket_id)
114        tc.find('Rebranched from .* for .*')
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
120    def merge(self, ticket_id, component, timeout=5):
121        """timeout is in seconds."""
122        self.go_to_mergebot()
123        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
124        tc.submit('Merge')
125        self.wait_until_find('Nothing in the queue', timeout)
126        tc.find('Branch')
127        self.go_to_ticket(ticket_id)
128        tc.find('Merged .* to .* for')
129        # TODO: We may want to change this to remove the "dead" branch
130        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
131                    stdout=logfile, stderr=logfile)
132        if retval:
133            raise Exception('svn ls failed with exit code %s' % retval)
134
135    def checkmerge(self, ticket_id, component, timeout=5):
136        """timeout is in seconds."""
137        self.go_to_mergebot()
138        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
139        tc.submit('CheckMerge')
140        self.wait_until_find('Nothing in the queue', timeout)
141        tc.find('Rebranch')
142        tc.find('Merge')
143        tc.find('CheckMerge')
144        self.go_to_ticket(ticket_id)
145        tc.find('while checking merge of')
146        # TODO: We may want to change this to remove the "dead" branch
147        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
148                    stdout=logfile, stderr=logfile)
149        if retval:
150            raise Exception('svn ls failed with exit code %s' % retval)
151
152
153class MergeBotTestSuite(FunctionalTestSuite):
154    def setUp(self):
155        port = 8889
156        baseurl = "http://localhost:%s" % port
157        self._testenv = SvnFunctionalTestEnvironment("testenv%s" % port, port, baseurl)
158
159        # Configure mergebot
160        env = self._testenv.get_trac_environment()
161        env.config.set('components', 'mergebot.web_ui.mergebotmodule', 'enabled')
162        env.config.save()
163        os.mkdir(os.path.join("testenv%s" % port, 'trac', 'mergebot'))
164        self._testenv._tracadmin('upgrade') # sets up the bulk of the mergebot config
165        env.config.parse_if_needed()
166        env.config.set('mergebot', 'repository_url', self._testenv.repo_url())
167        env.config.set('logging', 'log_type', 'file')
168        env.config.save()
169        env.config.parse_if_needed()
170
171        self._testenv.start()
172        self._tester = MergeBotFunctionalTester(baseurl, self._testenv.repo_url())
173        os.system('mergebotdaemon -f "%s" > %s/mergebotdaemon.log 2>&1 &' % (self._testenv.tracdir, self._testenv.tracdir))
174        self.fixture = (self._testenv, self._tester)
175
176        # Setup some common component stuff for MergeBot's use:
177        svnurl = self._testenv.repo_url()
178        for component in ['stuff', 'flagship', 'submarine']:
179            self._tester.create_component(component)
180            if call(['svn', '-m', 'Create tree for "%s".' % component, 'mkdir',
181                     svnurl + '/' + component,
182                     svnurl + '/' + component + '/trunk',
183                     svnurl + '/' + component + '/tags',
184                     svnurl + '/' + component + '/branches'],
185                    stdout=logfile, stderr=logfile):
186                raise Exception("svn mkdir failed")
187
188        self._tester.create_version('trunk')
189
190
191class FunctionalSvnTestCaseSetup(FunctionalTwillTestCaseSetup):
192    def get_workdir(self):
193        return os.path.join(self._testenv.dirname, self.__class__.__name__)
194
195    def checkout(self, ticket_id=None):
196        """checkout a working copy of the branch for the given ticket, or trunk if none given"""
197        if ticket_id is None:
198            svnurl = self._testenv.repo_url() + '/stuff/trunk'
199        else:
200            svnurl = self._testenv.repo_url() + '/stuff/branches/ticket-%s' % ticket_id
201        retval = call(['svn', 'checkout', svnurl, self.get_workdir()],
202            stdout=logfile, stderr=logfile)
203        self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval))
204
205    def switch(self, ticket_id=None):
206        if ticket_id is None:
207            svnurl = self._testenv.repo_url() + '/stuff/trunk'
208        else:
209            svnurl = self._testenv.repo_url() + '/stuff/branches/ticket-%s' % ticket_id
210        retval = call(['svn', 'switch', svnurl, self.get_workdir()],
211            stdout=logfile, stderr=logfile)
212        self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval))
213
214    def add_new_file(self, filename=None):
215        workdir = self.get_workdir()
216        if filename is None:
217            newfile = os.path.join(workdir, self.__class__.__name__)
218        else:
219            newfile = os.path.join(workdir, filename)
220        open(newfile, 'w').write(random_page())
221        retval = call(['svn', 'add', newfile],
222            cwd=workdir,
223            stdout=logfile, stderr=logfile)
224        self.assertEqual(retval, 0, "svn add failed with error %s" % (retval))
225
226    def commit(self, message, files=None):
227        if files is None:
228            files = ['.']
229        retval = call(['svn', 'commit', '-m', message] + list(files),
230            cwd=self.get_workdir(),
231            stdout=logfile, stderr=logfile)
232        self.assertEqual(retval, 0, "svn commit failed with error %s" % (retval))
233
234
235class MergeBotTestEnabled(FunctionalTwillTestCaseSetup):
236    def runTest(self):
237        self._tester.logout()
238        tc.go(self._tester.url)
239        self._tester.login('admin')
240        tc.follow('MergeBot')
241        mergeboturl = self._tester.url + '/mergebot'
242        tc.url(mergeboturl)
243        tc.notfind('No handler matched request to /mergebot')
244
245
246class MergeBotTestNoVersion(FunctionalTwillTestCaseSetup):
247    """Verify that if a ticket does not have the version field set, it will not
248    appear in the MergeBot list.
249    """
250    def runTest(self):
251        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
252            info={'component':'stuff', 'version':''})
253        tc.follow('MergeBot')
254        tc.notfind(self.__class__.__name__)
255
256
257class MergeBotTestBranch(FunctionalTwillTestCaseSetup):
258    def runTest(self):
259        """Verify that the 'branch' button works"""
260        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
261            info={'component':'stuff', 'version':'trunk'})
262        self._tester.branch(ticket_id, 'stuff')
263
264
265class MergeBotTestRebranch(FunctionalTwillTestCaseSetup):
266    def runTest(self):
267        """Verify that the 'rebranch' button works"""
268        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
269            info={'component':'stuff', 'version':'trunk'})
270        self._tester.branch(ticket_id, 'stuff')
271        self._tester.rebranch(ticket_id, 'stuff')
272
273
274class MergeBotTestMerge(FunctionalTwillTestCaseSetup):
275    def runTest(self):
276        """Verify that the 'merge' button works"""
277        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
278            info={'component':'stuff', 'version':'trunk'})
279        self._tester.branch(ticket_id, 'stuff')
280        self._tester.merge(ticket_id, 'stuff')
281
282
283class MergeBotTestCheckMerge(FunctionalTwillTestCaseSetup):
284    def runTest(self):
285        """Verify that the 'checkmerge' button works"""
286        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
287            info={'component':'stuff', 'version':'trunk'})
288        self._tester.branch(ticket_id, 'stuff')
289        self._tester.checkmerge(ticket_id, 'stuff')
290
291
292class MergeBotTestRebranchWithChange(FunctionalSvnTestCaseSetup):
293    def runTest(self):
294        """Verify that the 'rebranch' button works with changes on the branch"""
295        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
296            info={'component':'stuff', 'version':'trunk'})
297        self._tester.branch(ticket_id, 'stuff')
298
299        # checkout a working copy & make a change
300        self.checkout(ticket_id)
301        # Create & add a new file
302        self.add_new_file()
303        self.commit('Add a new file')
304
305        self._tester.rebranch(ticket_id, 'stuff')
306
307
308class MergeBotTestRebranchWithChangeAndTrunkChange(FunctionalSvnTestCaseSetup):
309    def runTest(self):
310        """Verify that the 'rebranch' button works with changes on the branch and trunk"""
311        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
312            info={'component':'stuff', 'version':'trunk'})
313        self._tester.branch(ticket_id, 'stuff')
314
315        # checkout a working copy & make a change
316        self.checkout(ticket_id)
317        # Create & add a new file
318        basename = self.__class__.__name__
319        self.add_new_file(basename + '-ticket')
320        self.commit('Add a new file on ticket')
321        self.switch()
322        self.add_new_file(basename + '-trunk')
323        self.commit('Add a new file on trunk')
324
325        self._tester.rebranch(ticket_id, 'stuff')
326
327
328class MergeBotTestSingleUseCase(FunctionalTwillTestCaseSetup):
329    def runTest(self):
330        """Create a branch, make a change, checkmerge, and merge it."""
331        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
332            info={'component':'stuff', 'version':'trunk'})
333        self._tester.branch(ticket_id, 'stuff')
334        # checkout a working copy & make a change
335        svnurl = self._testenv.repo_url()
336        workdir = os.path.join(self._testenv.dirname, self.__class__.__name__)
337        retval = call(['svn', 'checkout', svnurl + '/stuff/branches/ticket-%s' % ticket_id, workdir],
338            stdout=logfile, stderr=logfile)
339        self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval))
340        # Create & add a new file
341        newfile = os.path.join(workdir, self.__class__.__name__)
342        open(newfile, 'w').write(random_page())
343        retval = call(['svn', 'add', self.__class__.__name__],
344            cwd=workdir,
345            stdout=logfile, stderr=logfile)
346        self.assertEqual(retval, 0, "svn add failed with error %s" % (retval))
347        retval = call(['svn', 'commit', '-m', 'Add a new file', self.__class__.__name__],
348            cwd=workdir,
349            stdout=logfile, stderr=logfile)
350        self.assertEqual(retval, 0, "svn commit failed with error %s" % (retval))
351
352        self._tester.checkmerge(ticket_id, 'stuff')
353        self._tester.merge(ticket_id, 'stuff')
354
355        shutil.rmtree(workdir) # cleanup working copy
356
357
358def suite():
359    suite = MergeBotTestSuite()
360    suite.addTest(MergeBotTestEnabled())
361    suite.addTest(MergeBotTestNoVersion())
362    suite.addTest(MergeBotTestBranch())
363    suite.addTest(MergeBotTestRebranch())
364    suite.addTest(MergeBotTestCheckMerge())
365    suite.addTest(MergeBotTestMerge())
366    suite.addTest(MergeBotTestRebranchWithChange())
367    suite.addTest(MergeBotTestRebranchWithChangeAndTrunkChange())
368    suite.addTest(MergeBotTestSingleUseCase())
369    return suite
370
371if __name__ == '__main__':
372    unittest.main(defaultTest='suite')
Note: See TracBrowser for help on using the repository browser.