Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: sitescripts/hg/test/update_issues.py

Issue 29339623: Issue 3681 - Add suport for "Fixes XXXX - ..." commit messages (Closed)
Patch Set: Fix Strunk+White violations Created May 18, 2016, 8:29 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « sitescripts/hg/template/issue_commit_comment.tmpl ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # This file is part of the Adblock Plus web scripts, 1 # This file is part of the Adblock Plus web scripts,
2 # Copyright (C) 2006-2016 Eyeo GmbH 2 # Copyright (C) 2006-2016 Eyeo GmbH
3 # 3 #
4 # Adblock Plus is free software: you can redistribute it and/or modify 4 # Adblock Plus is free software: you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License version 3 as 5 # it under the terms of the GNU General Public License version 3 as
6 # published by the Free Software Foundation. 6 # published by the Free Software Foundation.
7 # 7 #
8 # Adblock Plus is distributed in the hope that it will be useful, 8 # Adblock Plus is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of 9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details. 11 # GNU General Public License for more details.
12 # 12 #
13 # You should have received a copy of the GNU General Public License 13 # You should have received a copy of the GNU General Public License
14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. 14 # along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
15 15
16 import ConfigParser
16 import mock 17 import mock
17 import unittest 18 import unittest
18 19
19 import sitescripts.hg.bin.update_issues as update_issues 20 import sitescripts.hg.bin.update_issues as update_issues
20 21
21 22
22 def _create_mock_repo(message): 23 def _issue(component, milestone='', can_resolve=False):
23 mock_repo = mock.MagicMock() 24 issue = {
24 mock_repo.__len__.return_value = 1 25 'attrs': {'_ts': 1, 'milestone': milestone, 'component': component},
25 mock_change = mock.MagicMock() 26 'actions': [['leave', '', '', []]]
26 mock_change.rev.return_value = 0 27 }
27 mock_change.description.return_value = message 28 if can_resolve:
28 mock_repo.__getitem__.return_value = mock_change 29 issue['actions'].append(['resolve', '', '', []])
29 return mock_repo 30 return issue
30 31
31 32
32 class TestUpdateIssues(unittest.TestCase): 33 ISSUES = {
34 1337: _issue(component='one', can_resolve=True),
35 2448: _issue(component='two'),
36 3559: _issue(component='one', milestone='other'),
37 4670: _issue(component='three', can_resolve=True),
38 5781: _issue(component='four', can_resolve=True)
39 }
40
41 MILESTONES = {
42 'completed': {'completed': True, 'name': 'completed'},
43 'current': {'completed': None, 'name': 'current'},
44 'other': {'completed': None, 'name': 'other'}
45 }
46
47
48 class _TestBase(unittest.TestCase):
49 """Base class for hook tests that prepares the environment."""
50
51 def _patchWith(self, target, return_value):
52 patcher = mock.patch(target, return_value=return_value)
53 patcher.start()
54 self.addCleanup(patcher.stop)
55
56 def _create_mock_milestone_multicall(self):
57 ret = []
58 multicall = mock.Mock(return_value=ret)
59 multicall.ticket.milestone.get = lambda i: ret.append(MILESTONES[i])
60 return multicall
61
62 def _mock_trac(self):
63 trac = mock.Mock()
64 trac.ticket.get = lambda i: [i, mock.ANY, mock.ANY, ISSUES[i]['attrs']]
65 trac.ticket.getActions = lambda i: ISSUES[i]['actions']
66 trac.ticket.milestone.getAll = lambda: MILESTONES.keys()
67 self.trac_proxy_mock = trac
68 self._patchWith('xmlrpclib.ServerProxy', trac)
69 self._patchWith('xmlrpclib.MultiCall',
70 self._create_mock_milestone_multicall())
71
72 def _mock_config(self):
73 config = ConfigParser.ConfigParser()
74 config.add_section('hg')
75 config.set('hg', 'trac_xmlrpc_url', 'foo')
76 config.set('hg', 'issue_url_template', '#{id}')
77 config.add_section('hg_module_milestones')
78 config.set('hg_module_milestones', 'one', '.*')
79 config.set('hg_module_milestones', 'two', 'other')
80 config.set('hg_module_milestones', 'four', 'completed')
81 self._patchWith('sitescripts.hg.bin.update_issues.get_config', config)
82
33 def setUp(self): 83 def setUp(self):
34 self.ui = mock.Mock() 84 self.ui = mock.Mock()
35 85 self._mock_trac()
36 @mock.patch("xmlrpclib.ServerProxy") 86 self._mock_config()
37 def test_commits_with_invalid_message_format_ignored(self, 87
38 mock_server_proxy): 88
39 messages = ["", "Issue #1337", "Tissue 1337", "Issue 13b"] 89 class _MockRepo(list):
40 for message in messages: 90 def __init__(self, commit_messages):
41 mock_repo = _create_mock_repo(message) 91 list.__init__(self)
42 update_issues.hook(self.ui, mock_repo, 0) 92 for i, message in enumerate(commit_messages):
43 self.ui.warn.assert_called_once() 93 mock_commit = mock.MagicMock()
44 self.assertFalse(mock_server_proxy.called) 94 mock_commit.rev.return_value = i
45 self.ui.warn.reset_mock() 95 mock_commit.hex.return_value = '{:010x}'.format(i) + '0' * 30
46 96 mock_commit.description.return_value = message
47 @mock.patch("xmlrpclib.ServerProxy") 97 self.append(mock_commit)
48 def test_noissue_commits_ignored(self, mock_server_proxy): 98 self.changelog = mock.Mock()
49 messages = ["Noissue", "noissue"] 99 self.changelog.findmissingrevs = self._findmissingrevs
50 for message in messages: 100
51 mock_repo = _create_mock_repo(message) 101 def _findmissingrevs(self, olds, news):
52 update_issues.hook(self.ui, mock_repo, 0) 102 return range(olds[0] + 1, news[0] + 1)
53 self.assertFalse(self.ui.warn.called) 103
54 self.assertFalse(mock_server_proxy.called) 104 def __getitem__(self, commit_id):
55 105 if isinstance(commit_id, str):
56 @mock.patch("xmlrpclib.ServerProxy") 106 return [commit for commit in self if commit.hex() == commit_id][0]
57 def test_single_issue_referenced(self, mock_server_proxy): 107 return list.__getitem__(self, commit_id)
58 server_proxy_instance = mock_server_proxy.return_value 108
59 messages = ["Issue 1337", "issue 1337"] 109 def url(self):
60 for message in messages: 110 return 'mock/repo'
61 mock_repo = _create_mock_repo(message) 111
62 update_issues.hook(self.ui, mock_repo, 0) 112
63 self.assertFalse(self.ui.warn.called) 113 class TestChangegroupHook(_TestBase):
64 server_proxy_instance.ticket.update.assert_called_once() 114
65 self.assertEqual(server_proxy_instance.ticket.update.call_args[0][0] , 115 def _run_hook(self, commit_messages, warning_count=0, update_count=0):
66 1337) 116 repo = _MockRepo(commit_messages)
67 server_proxy_instance.reset_mock() 117 update_issues.changegroup_hook(self.ui, repo, 0)
68 118 warnings = self.ui.warn.call_args_list
69 @mock.patch("xmlrpclib.ServerProxy") 119 updates = self.trac_proxy_mock.ticket.update.call_args_list
70 def test_multiple_issues_referenced(self, mock_server_proxy): 120 self.assertEqual(len(warnings), warning_count)
71 server_proxy_instance = mock_server_proxy.return_value 121 self.assertEqual(len(updates), update_count)
72 mock_repo = _create_mock_repo("Issue 1337, issue 2448") 122 return updates
73 update_issues.hook(self.ui, mock_repo, 0) 123
74 self.assertFalse(self.ui.warn.called) 124 def test_commits_with_invalid_message_format_ignored(self):
75 calls = server_proxy_instance.ticket.update.call_args_list 125 self._run_hook([
76 self.assertEqual(len(calls), 2) 126 '',
77 self.assertEqual(calls[0][0][0], 1337) 127 'Issue #1337 - Extraneous characters in issue number',
78 self.assertEqual(calls[1][0][0], 2448) 128 'Issue 1337', # No dash, no message.
79 129 'Issue 1337: Colon instead of dash',
80 if __name__ == "__main__": 130 'Noissue no dash',
131 'Issue 1337-No space around dash',
132 'Fixes 1337 no dash'
133 ], warning_count=7)
134
135 def test_noissue_commits_ignored(self):
136 self._run_hook(['Noissue - Foo', 'noissue - Bar']) # No updates.
137
138 def test_single_issue_referenced(self):
139 updates = self._run_hook(['Issue 1337 - Foo'], update_count=1)
140 self.assertEqual(updates[0][0][0], 1337)
141
142 def test_missing_issue_referenced(self):
143 self._run_hook(['Issue 42 - Bar'], warning_count=1)
144
145 def test_multiple_issues_referenced(self):
146 updates = self._run_hook(['Issue 1337, fixes 2448 - Foo'],
147 update_count=2)
148 self.assertEqual(updates[0][0][0], 1337)
149 self.assertEqual(updates[1][0][0], 2448)
150
151 def test_multiple_commits_for_issue(self):
152 updates = self._run_hook(['Issue 1337 - Foo', 'Fixes 1337 - Bar'],
153 update_count=1)
154 comment = updates[0][0][1]
155 self.assertIn('000000000000', comment)
156 self.assertIn('000000000100', comment)
157
158
159 class TestPushkeyHook(_TestBase):
160
161 def _run_hook(self, commit_messages, bookmark='master',
162 warning_count=0, update_count=0):
163 repo = _MockRepo(['Base', 'Old'] + commit_messages)
164 update_issues.pushkey_hook(self.ui, repo,
165 namespace='bookmarks', key=bookmark,
166 old=1, new=1 + len(commit_messages))
167 warnings = self.ui.warn.call_args_list
168 updates = self.trac_proxy_mock.ticket.update.call_args_list
169 self.assertEqual(len(warnings), warning_count)
170 self.assertEqual(len(updates), update_count)
171 return updates
172
173 def _check_update(self, update, issue_id, action='resolve',
174 milestone='current'):
175 self.assertEqual(update[0][0], issue_id)
176 changes = update[0][2]
177 self.assertEqual(changes['action'], action)
178 if milestone is None:
179 self.assertNotIn('milestone', changes)
180 else:
181 self.assertEqual(changes['milestone'], milestone)
182
183 def test_move_other_bookmark(self):
184 self._run_hook(['Fixes 1337 - Foo'], bookmark='other') # No updates.
185
186 def test_one_issue_fixed(self):
187 updates = self._run_hook(['Fixes 1337 - Foo'], update_count=1)
188 self._check_update(updates[0], 1337)
189
190 def test_fix_closed_issue(self):
191 updates = self._run_hook(['fixes 2448 - Foo'], update_count=1)
192 self._check_update(updates[0], 2448, action='leave', milestone='other')
193
194 def test_fix_issue_noregexp(self):
195 updates = self._run_hook(['Fixes 4670 - Foo'], update_count=1)
196 self._check_update(updates[0], 4670, milestone=None)
197
198 def test_fix_issue_no_matching_milestones(self):
199 updates = self._run_hook(['Fixes 5781 - Foo'], update_count=1)
200 self._check_update(updates[0], 5781, milestone=None)
201
202 def test_fix_many(self):
203 updates = self._run_hook(['Fixes 1337 - Foo', 'Fixes 2448 - Bar'],
204 update_count=2)
205 self._check_update(updates[0], 1337)
206 self._check_update(updates[1], 2448, action='leave', milestone='other')
207
208 def test_fix_nonexistent(self):
209 self._run_hook(['Fixes 7331 - Foo'], warning_count=1)
210
211 def test_fix_closed_with_assigned_milestone(self):
212 self._run_hook(['fixes 3559 - Foo']) # No updates.
213
214
215 if __name__ == '__main__':
81 unittest.main() 216 unittest.main()
OLDNEW
« no previous file with comments | « sitescripts/hg/template/issue_commit_comment.tmpl ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld