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

Last change on this file since 26 was 17, checked in by retracile, 16 years ago

Mergebot: redesigned implementation. Still has rough edges.

File size: 11.9 KB
Line 
1#!/usr/bin/python
2"""Automated tests for MergeBot
3"""
4
5import os
6import unittest
7import time
8import shutil
9
10from subprocess import call, Popen #, PIPE, STDOUT
11from twill.errors import TwillAssertionError
12
13
14from trac.tests.functional import FunctionalTestSuite, FunctionalTestEnvironment, FunctionalTester, FunctionalTwillTestCaseSetup, tc, b, logfile
15from trac.tests.contentgen import random_page #, random_sentence, random_word
16
17
18#class MergeBotTestEnvironment(FunctionalTestEnvironment):
19#    """Slight change to FunctionalTestEnvironment to keep the PYTHONPATH from
20#    our environment.
21#    """
22#    def start(self):
23#        """Starts the webserver"""
24#        server = Popen(["python", "./trac/web/standalone.py",
25#                        "--port=%s" % self.port, "-s",
26#                        "--basic-auth=trac,%s," % self.htpasswd,
27#                        self.tracdir],
28#                       #env={'PYTHONPATH':'.'},
29#                       stdout=logfile, stderr=logfile,
30#                      )
31#        self.pid = server.pid
32#        time.sleep(1) # Give the server time to come up
33#
34#    def _tracadmin(self, *args):
35#        """Internal utility method for calling trac-admin"""
36#        if call(["python", "./trac/admin/console.py", self.tracdir] +
37#                list(args),
38#                #env={'PYTHONPATH':'.'},
39#                stdout=logfile, stderr=logfile):
40#            raise Exception('Failed running trac-admin with %r' % (args, ))
41#
42#
43#FunctionalTestEnvironment = MergeBotTestEnvironment
44
45
46class MergeBotFunctionalTester(FunctionalTester):
47    """Adds some MergeBot functionality to the functional tester."""
48    # FIXME: the tc.find( <various actions> ) checks are bogus: any ticket can
49    # satisfy them, not just the one we're working on.
50    def __init__(self, *args, **kwargs):
51        FunctionalTester.__init__(self, *args, **kwargs)
52        self.mergeboturl = self.url + '/mergebot'
53
54    def wait_until_find(self, search, timeout=5):
55        start = time.time()
56        while time.time() - start < timeout:
57            try:
58                tc.reload()
59                tc.find(search)
60                return
61            except TwillAssertionError:
62                pass
63        raise TwillAssertionError("Unable to find %r within %s seconds" % (search, timeout))
64
65    def wait_until_notfind(self, search, timeout=5):
66        start = time.time()
67        while time.time() - start < timeout:
68            try:
69                tc.reload()
70                tc.notfind(search)
71                return
72            except TwillAssertionError:
73                pass
74        raise TwillAssertionError("Unable to notfind %r within %s seconds" % (search, timeout))
75
76    def go_to_mergebot(self):
77        tc.go(self.mergeboturl)
78        tc.url(self.mergeboturl)
79        tc.notfind('No handler matched request to /mergebot')
80
81    def branch(self, ticket_id, component, timeout=1):
82        """timeout is in seconds."""
83        self.go_to_mergebot()
84        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
85        tc.submit('Branch')
86        self.wait_until_notfind('Doing branch', timeout)
87        tc.find('Rebranch')
88        tc.find('Merge')
89        tc.find('CheckMerge')
90        self.go_to_ticket(ticket_id)
91        tc.find('Created branch from .* for .*')
92        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
93                    stdout=logfile, stderr=logfile)
94        if retval:
95            raise Exception('svn ls failed with exit code %s' % retval)
96
97    def rebranch(self, ticket_id, component, timeout=5):
98        """timeout is in seconds."""
99        self.go_to_mergebot()
100        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
101        tc.submit('Rebranch')
102        self.wait_until_notfind('Doing rebranch', timeout)
103        tc.find('Rebranch')
104        tc.find('Merge')
105        tc.find('CheckMerge')
106        self.go_to_ticket(ticket_id)
107        tc.find('Rebranched from .* for .*')
108        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
109                    stdout=logfile, stderr=logfile)
110        if retval:
111            raise Exception('svn ls failed with exit code %s' % retval)
112
113    def merge(self, ticket_id, component, timeout=5):
114        """timeout is in seconds."""
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('Merge')
118        self.wait_until_notfind('Doing merge', timeout)
119        tc.find('Branch')
120        self.go_to_ticket(ticket_id)
121        tc.find('Merged .* to .* for')
122        # TODO: We may want to change this to remove the "dead" branch
123        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
124                    stdout=logfile, stderr=logfile)
125        if retval:
126            raise Exception('svn ls failed with exit code %s' % retval)
127
128    def checkmerge(self, ticket_id, component, timeout=5):
129        """timeout is in seconds."""
130        self.go_to_mergebot()
131        tc.formvalue('ops-%s' % ticket_id, 'ticket', ticket_id) # Essentially a noop to select the right form
132        tc.submit('CheckMerge')
133        self.wait_until_notfind('Doing checkmerge', timeout)
134        tc.find('Rebranch')
135        tc.find('Merge')
136        tc.find('CheckMerge')
137        self.go_to_ticket(ticket_id)
138        tc.find('while checking merge of')
139        # TODO: We may want to change this to remove the "dead" branch
140        retval = call(['svn', 'ls', self.repo_url + '/' + component + '/branches/ticket-%s' % ticket_id],
141                    stdout=logfile, stderr=logfile)
142        if retval:
143            raise Exception('svn ls failed with exit code %s' % retval)
144
145
146class MergeBotTestSuite(FunctionalTestSuite):
147    def setUp(self):
148        port = 8889
149        baseurl = "http://localhost:%s" % port
150        self._testenv = FunctionalTestEnvironment("testenv%s" % port, port, baseurl)
151
152        # Configure mergebot
153        env = self._testenv.get_trac_environment()
154        env.config.set('components', 'mergebot.web_ui.mergebotmodule', 'enabled')
155        env.config.set('mergebot', 'repository_url', self._testenv.repo_url())
156        env.config.set('mergebot', 'work_dir', self._testenv.repodir + '/mergebot')
157
158        env.config.set('ticket-custom', 'mergebotstate', 'select')
159        env.config.set('ticket-custom', 'mergebotstate.editable', '0')
160        env.config.set('ticket-custom', 'mergebotstate.label', 'MergeBotState')
161        env.config.set('ticket-custom', 'mergebotstate.options', '| tomerge | merged | tobranch | branched | conflicts')
162        env.config.set('ticket-custom', 'mergebotstate.order', '2')
163        env.config.set('ticket-custom', 'mergebotstate.value', '')
164
165        env.config.set('logging', 'log_type', 'file')
166
167        env.config.save()
168        env.config.parse_if_needed()
169
170        self._testenv.start()
171        self._tester = MergeBotFunctionalTester(baseurl, self._testenv.repo_url())
172
173        # Setup some common component stuff for MergeBot's use:
174        svnurl = self._testenv.repo_url()
175        for component in ['stuff', 'flagship', 'submarine']:
176            self._tester.create_component(component)
177            if call(['svn', '-m', 'Create tree for "%s".' % component, 'mkdir',
178                     svnurl + '/' + component,
179                     svnurl + '/' + component + '/trunk',
180                     svnurl + '/' + component + '/tags',
181                     svnurl + '/' + component + '/branches'],
182                    stdout=logfile, stderr=logfile):
183                raise Exception("svn mkdir failed")
184
185        self._tester.create_version('trunk')
186
187
188class MergeBotTestEnabled(FunctionalTwillTestCaseSetup):
189    def runTest(self):
190        self._tester.logout()
191        tc.go(self._tester.url)
192        self._tester.login('admin')
193        tc.follow('MergeBot')
194        mergeboturl = self._tester.url + '/mergebot'
195        tc.url(mergeboturl)
196        tc.notfind('No handler matched request to /mergebot')
197
198
199class MergeBotTestQueueList(FunctionalTwillTestCaseSetup):
200    def runTest(self):
201        tc.follow('MergeBot')
202        for queue in 'branch', 'rebranch', 'checkmerge', 'merge':
203            tc.find('%s Queue' % queue)
204
205
206class MergeBotTestNoVersion(FunctionalTwillTestCaseSetup):
207    """Verify that if a ticket does not have the version field set, it will not
208    appear in the MergeBot list.
209    """
210    def runTest(self):
211        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
212            info={'component':'stuff', 'version':''})
213        tc.follow('MergeBot')
214        tc.notfind(self.__class__.__name__)
215
216
217class MergeBotTestBranch(FunctionalTwillTestCaseSetup):
218    def runTest(self):
219        """Verify that the 'branch' button works"""
220        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
221            info={'component':'stuff', 'version':'trunk'})
222        self._tester.branch(ticket_id, 'stuff')
223
224
225class MergeBotTestRebranch(FunctionalTwillTestCaseSetup):
226    def runTest(self):
227        """Verify that the 'rebranch' button works"""
228        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
229            info={'component':'stuff', 'version':'trunk'})
230        self._tester.branch(ticket_id, 'stuff')
231        self._tester.rebranch(ticket_id, 'stuff')
232
233
234class MergeBotTestMerge(FunctionalTwillTestCaseSetup):
235    def runTest(self):
236        """Verify that the 'merge' button works"""
237        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
238            info={'component':'stuff', 'version':'trunk'})
239        self._tester.branch(ticket_id, 'stuff')
240        self._tester.merge(ticket_id, 'stuff')
241
242
243class MergeBotTestCheckMerge(FunctionalTwillTestCaseSetup):
244    def runTest(self):
245        """Verify that the 'checkmerge' button works"""
246        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
247            info={'component':'stuff', 'version':'trunk'})
248        self._tester.branch(ticket_id, 'stuff')
249        self._tester.checkmerge(ticket_id, 'stuff')
250
251
252class MergeBotTestSingleUseCase(FunctionalTwillTestCaseSetup):
253    def runTest(self):
254        """Create a branch, make a change, checkmerge, and merge it."""
255        ticket_id = self._tester.create_ticket(summary=self.__class__.__name__,
256            info={'component':'stuff', 'version':'trunk'})
257        self._tester.branch(ticket_id, 'stuff')
258        # checkout a working copy & make a change
259        svnurl = self._testenv.repo_url()
260        workdir = os.path.join(self._testenv.dirname, self.__class__.__name__)
261        retval = call(['svn', 'checkout', svnurl + '/stuff/branches/ticket-%s' % ticket_id, workdir],
262            stdout=logfile, stderr=logfile)
263        self.assertEqual(retval, 0, "svn checkout failed with error %s" % (retval))
264        # Create & add a new file
265        newfile = os.path.join(workdir, self.__class__.__name__)
266        open(newfile, 'w').write(random_page())
267        retval = call(['svn', 'add', self.__class__.__name__],
268            cwd=workdir,
269            stdout=logfile, stderr=logfile)
270        self.assertEqual(retval, 0, "svn add failed with error %s" % (retval))
271        retval = call(['svn', 'commit', '-m', 'Add a new file', self.__class__.__name__],
272            cwd=workdir,
273            stdout=logfile, stderr=logfile)
274        self.assertEqual(retval, 0, "svn commit failed with error %s" % (retval))
275
276        self._tester.checkmerge(ticket_id, 'stuff')
277        self._tester.merge(ticket_id, 'stuff')
278
279        shutil.rmtree(workdir) # cleanup working copy
280
281
282def suite():
283    suite = MergeBotTestSuite()
284    suite.addTest(MergeBotTestEnabled())
285    suite.addTest(MergeBotTestQueueList())
286    suite.addTest(MergeBotTestNoVersion())
287    suite.addTest(MergeBotTestBranch())
288    suite.addTest(MergeBotTestRebranch())
289    suite.addTest(MergeBotTestMerge())
290    suite.addTest(MergeBotTestCheckMerge())
291    suite.addTest(MergeBotTestSingleUseCase())
292    return suite
293
294if __name__ == '__main__':
295    unittest.main(defaultTest='suite')
Note: See TracBrowser for help on using the repository browser.