OLD | NEW |
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-present eyeo GmbH | 2 # Copyright (C) 2006-present 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 """Test Mercurial Trac integration hook.""" |
| 17 |
17 import mock | 18 import mock |
18 import unittest | 19 import pytest |
19 | 20 |
20 import sitescripts.hg.bin.update_issues as update_issues | 21 import sitescripts.hg.bin.update_issues as update_issues |
21 | 22 |
22 | 23 COMMIT_HASH = '000011112222' |
23 def _issue(component, milestone='', can_resolve=False): | |
24 issue = { | |
25 'attrs': {'_ts': 1, 'milestone': milestone, 'component': component}, | |
26 'actions': [['leave', '', '', []]], | |
27 } | |
28 if can_resolve: | |
29 issue['actions'].append(['resolve', '', '', []]) | |
30 return issue | |
31 | 24 |
32 | 25 |
33 ISSUES = { | 26 class MercurialRepositoryMock(list): |
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 | 27 |
41 MILESTONES = { | 28 def __init__(self, message): |
42 'completed': {'completed': True, 'name': 'completed'}, | 29 change_mock = mock.MagicMock() |
43 'current': {'completed': None, 'name': 'current'}, | 30 change_mock.rev.return_value = 0 |
44 'other': {'completed': None, 'name': 'other'}, | 31 change_mock.hex.return_value = COMMIT_HASH |
45 } | 32 change_mock.description.return_value = message |
| 33 list.__init__(self, [change_mock]) |
| 34 |
| 35 def url(self): |
| 36 return 'repo/mock' |
46 | 37 |
47 | 38 |
48 class _TestBase(unittest.TestCase): | 39 @pytest.fixture |
49 """Base class for hook tests that prepares the environment.""" | 40 def server_proxy_mock(mocker): |
50 | 41 return mocker.patch('xmlrpclib.ServerProxy') |
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: sorted(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 | |
83 def setUp(self): | |
84 self.ui = mock.Mock() | |
85 self._mock_trac() | |
86 self._mock_config() | |
87 | 42 |
88 | 43 |
89 class _MockRepo(list): | 44 @pytest.fixture |
90 def __init__(self, commit_messages): | 45 def ui_mock(): |
91 list.__init__(self) | 46 return mock.MagicMock() |
92 for i, message in enumerate(commit_messages): | |
93 mock_commit = mock.MagicMock() | |
94 mock_commit.rev.return_value = i | |
95 mock_commit.hex.return_value = '{:010x}'.format(i) + '0' * 30 | |
96 mock_commit.description.return_value = message | |
97 self.append(mock_commit) | |
98 self.changelog = mock.Mock() | |
99 self.changelog.findmissingrevs = self._findmissingrevs | |
100 | |
101 def _findmissingrevs(self, olds, news): | |
102 return range(olds[0] + 1, news[0] + 1) | |
103 | |
104 def __getitem__(self, commit_id): | |
105 if isinstance(commit_id, str): | |
106 return [commit for commit in self if commit.hex() == commit_id][0] | |
107 return list.__getitem__(self, commit_id) | |
108 | |
109 def url(self): | |
110 return 'mock/repo' | |
111 | 47 |
112 | 48 |
113 class TestChangegroupHook(_TestBase): | 49 @pytest.mark.parametrize('message', [ |
114 | 50 '', 'Issue #1337', 'Tissue 1337', 'Issue 13b', |
115 def _run_hook(self, commit_messages, warning_count=0, update_count=0): | 51 ]) |
116 repo = _MockRepo(commit_messages) | 52 def test_invalid_message_format(message, ui_mock, server_proxy_mock): |
117 update_issues.changegroup_hook(self.ui, repo, 0) | 53 """Check that commits with invalid messages are ignored with a warning.""" |
118 warnings = self.ui.warn.call_args_list | 54 repo_mock = MercurialRepositoryMock(message) |
119 updates = self.trac_proxy_mock.ticket.update.call_args_list | 55 update_issues.hook(ui_mock, repo_mock, 0) |
120 self.assertEqual(len(warnings), warning_count) | 56 ui_mock.warn.assert_called_once() |
121 self.assertEqual(len(updates), update_count) | 57 assert not server_proxy_mock.called |
122 return updates | |
123 | |
124 def test_commits_with_invalid_message_format_ignored(self): | |
125 self._run_hook([ | |
126 '', | |
127 'Issue #1337 - Extraneous characters in issue number', | |
128 'Issue 1337', # No dash, no message. | |
129 'Issue 1337: Colon instead of dash', | |
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_multiline_commit_message(self): | |
143 updates = self._run_hook(['Issue 1337 - Foo\nBar', | |
144 'Issue 1337 - Bar.\nBaz', | |
145 'Fixes 2448 - Foo\n\nBar', | |
146 'Fixes 2448 - Bar\n \nBaz'], | |
147 update_count=2) | |
148 comment_1337 = updates[0][0][1] | |
149 self.assertIn('Issue 1337 - Foo...]', comment_1337) | |
150 self.assertIn('Issue 1337 - Bar...]', comment_1337) | |
151 comment_2448 = updates[1][0][1] | |
152 self.assertIn('Fixes 2448 - Foo]', comment_2448) | |
153 self.assertIn('Fixes 2448 - Bar]', comment_2448) | |
154 | |
155 def test_multiline_commit_message_crlf(self): | |
156 updates = self._run_hook(['Issue 1337 - Foo\r\nBar', | |
157 'Issue 1337 - Bar.\r\nBaz', | |
158 'Fixes 2448 - Foo\r\n\r\nBar', | |
159 'Fixes 2448 - Bar\r\n \r\nBaz'], | |
160 update_count=2) | |
161 comment_1337 = updates[0][0][1] | |
162 self.assertIn('Issue 1337 - Foo...]', comment_1337) | |
163 self.assertIn('Issue 1337 - Bar...]', comment_1337) | |
164 comment_2448 = updates[1][0][1] | |
165 self.assertIn('Fixes 2448 - Foo]', comment_2448) | |
166 self.assertIn('Fixes 2448 - Bar]', comment_2448) | |
167 | |
168 def test_missing_issue_referenced(self): | |
169 self._run_hook(['Issue 42 - Bar'], warning_count=1) | |
170 | |
171 def test_multiple_issues_referenced(self): | |
172 updates = self._run_hook(['Issue 1337, fixes 2448 - Foo'], | |
173 update_count=2) | |
174 self.assertEqual(updates[0][0][0], 1337) | |
175 self.assertEqual(updates[1][0][0], 2448) | |
176 | |
177 def test_multiple_commits_for_issue(self): | |
178 updates = self._run_hook(['Issue 1337 - Foo', 'Fixes 1337 - Bar'], | |
179 update_count=1) | |
180 comment = updates[0][0][1] | |
181 self.assertIn('000000000000', comment) | |
182 self.assertIn('000000000100', comment) | |
183 | 58 |
184 | 59 |
185 class TestPushkeyHook(_TestBase): | 60 @pytest.mark.parametrize('message', ['Noissue - foobar', 'noissue']) |
186 | 61 def test_noissue(message, ui_mock, server_proxy_mock): |
187 def _run_hook(self, commit_messages, bookmark='master', | 62 """Check that noissue commits are ignored without warning.""" |
188 warning_count=0, update_count=0): | 63 repo_mock = MercurialRepositoryMock(message) |
189 repo = _MockRepo(['Base', 'Old'] + commit_messages) | 64 update_issues.hook(ui_mock, repo_mock, 0) |
190 update_issues.pushkey_hook(self.ui, repo, | 65 assert not ui_mock.warn.called |
191 namespace='bookmarks', key=bookmark, | 66 assert not server_proxy_mock.called |
192 old=1, new=1 + len(commit_messages)) | |
193 warnings = self.ui.warn.call_args_list | |
194 updates = self.trac_proxy_mock.ticket.update.call_args_list | |
195 self.assertEqual(len(warnings), warning_count) | |
196 self.assertEqual(len(updates), update_count) | |
197 return updates | |
198 | |
199 def _check_update(self, update, issue_id, action='resolve', | |
200 milestone='current'): | |
201 self.assertEqual(update[0][0], issue_id) | |
202 changes = update[0][2] | |
203 self.assertEqual(changes['action'], action) | |
204 if milestone is None: | |
205 self.assertNotIn('milestone', changes) | |
206 else: | |
207 self.assertEqual(changes['milestone'], milestone) | |
208 | |
209 def test_move_other_bookmark(self): | |
210 self._run_hook(['Fixes 1337 - Foo'], bookmark='other') # No updates. | |
211 | |
212 def test_one_issue_fixed(self): | |
213 updates = self._run_hook(['Fixes 1337 - Foo'], update_count=1) | |
214 self._check_update(updates[0], 1337) | |
215 | |
216 def test_fix_closed_issue(self): | |
217 updates = self._run_hook(['fixes 2448 - Foo'], update_count=1) | |
218 self._check_update(updates[0], 2448, action='leave', milestone='other') | |
219 | |
220 def test_fix_issue_noregexp(self): | |
221 updates = self._run_hook(['Fixes 4670 - Foo'], update_count=1) | |
222 self._check_update(updates[0], 4670, milestone=None) | |
223 | |
224 def test_fix_issue_no_matching_milestones(self): | |
225 updates = self._run_hook(['Fixes 5781 - Foo'], update_count=1) | |
226 self._check_update(updates[0], 5781, milestone=None) | |
227 | |
228 def test_fix_many(self): | |
229 updates = self._run_hook(['Fixes 1337 - Foo', 'Fixes 2448 - Bar'], | |
230 update_count=2) | |
231 self._check_update(updates[0], 1337) | |
232 self._check_update(updates[1], 2448, action='leave', milestone='other') | |
233 | |
234 def test_fix_nonexistent(self): | |
235 self._run_hook(['Fixes 7331 - Foo'], warning_count=1) | |
236 | |
237 def test_fix_closed_with_assigned_milestone(self): | |
238 self._run_hook(['fixes 3559 - Foo']) # No updates. | |
239 | 67 |
240 | 68 |
241 if __name__ == '__main__': | 69 @pytest.mark.parametrize('message', ['Issue 1337 - foo', 'issue 1337 - foo']) |
242 unittest.main() | 70 def test_single_issue(message, ui_mock, server_proxy_mock): |
| 71 """Check that a commit referencing a single issue gets linked.""" |
| 72 server_proxy = server_proxy_mock.return_value |
| 73 repo_mock = MercurialRepositoryMock(message) |
| 74 update_issues.hook(ui_mock, repo_mock, 0) |
| 75 assert not ui_mock.warn.called |
| 76 server_proxy.ticket.update.assert_called_once() |
| 77 call = server_proxy.ticket.update.call_args |
| 78 assert call[0][0] == 1337 |
| 79 comment = call[0][1] |
| 80 assert comment.startswith('A commit referencing this issue has landed') |
| 81 assert COMMIT_HASH in comment |
| 82 assert comment.endswith('- foo]') |
| 83 |
| 84 |
| 85 def test_multiple_issues(ui_mock, server_proxy_mock): |
| 86 """Check that a commit referencing two issues gets linked twice.""" |
| 87 server_proxy = server_proxy_mock.return_value |
| 88 repo_mock = MercurialRepositoryMock('Issue 1337, issue 2448') |
| 89 update_issues.hook(ui_mock, repo_mock, 0) |
| 90 assert not ui_mock.warn.called |
| 91 calls = server_proxy.ticket.update.call_args_list |
| 92 assert len(calls) == 2 |
| 93 assert calls[0][0][0] == 1337 |
| 94 assert calls[1][0][0] == 2448 |
OLD | NEW |