| Index: sitescripts/hg/test/update_issues.py | 
| =================================================================== | 
| --- a/sitescripts/hg/test/update_issues.py | 
| +++ b/sitescripts/hg/test/update_issues.py | 
| @@ -8,74 +8,210 @@ | 
| # Adblock Plus is distributed in the hope that it will be useful, | 
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| # GNU General Public License for more details. | 
| # | 
| # You should have received a copy of the GNU General Public License | 
| # along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
|  | 
| +import ConfigParser | 
| import mock | 
| import unittest | 
|  | 
| import sitescripts.hg.bin.update_issues as update_issues | 
|  | 
|  | 
| -def _create_mock_repo(message): | 
| -    mock_repo = mock.MagicMock() | 
| -    mock_repo.__len__.return_value = 1 | 
| -    mock_change = mock.MagicMock() | 
| -    mock_change.rev.return_value = 0 | 
| -    mock_change.description.return_value = message | 
| -    mock_repo.__getitem__.return_value = mock_change | 
| -    return mock_repo | 
| +def _issue(component, milestone='', can_resolve=False): | 
| +    issue = { | 
| +        'attrs': {'_ts': 1, 'milestone': milestone, 'component': component}, | 
| +        'actions': [['leave', '', '', []]] | 
| +    } | 
| +    if can_resolve: | 
| +        issue['actions'].append(['resolve', '', '', []]) | 
| +    return issue | 
|  | 
|  | 
| -class TestUpdateIssues(unittest.TestCase): | 
| +ISSUES = { | 
| +    1337: _issue(component='one', can_resolve=True), | 
| +    2448: _issue(component='two'), | 
| +    3559: _issue(component='one', milestone='other'), | 
| +    4670: _issue(component='three', can_resolve=True), | 
| +    5781: _issue(component='four', can_resolve=True) | 
| +} | 
| + | 
| +MILESTONES = { | 
| +    'completed': {'completed': True, 'name': 'completed'}, | 
| +    'current': {'completed': None, 'name': 'current'}, | 
| +    'other': {'completed': None, 'name': 'other'} | 
| +} | 
| + | 
| + | 
| +class _TestBase(unittest.TestCase): | 
| +    """Base class for hook tests that prepares the environment.""" | 
| + | 
| +    def _patchWith(self, target, return_value): | 
| +        patcher = mock.patch(target, return_value=return_value) | 
| +        patcher.start() | 
| +        self.addCleanup(patcher.stop) | 
| + | 
| +    def _create_mock_milestone_multicall(self): | 
| +        ret = [] | 
| +        multicall = mock.Mock(return_value=ret) | 
| +        multicall.ticket.milestone.get = lambda id: ret.append(MILESTONES[id]) | 
| +        return multicall | 
| + | 
| +    def _mock_trac(self): | 
| +        trac = mock.Mock() | 
| +        trac.ticket.get = lambda id: [id, mock.ANY, mock.ANY, | 
| +                                      ISSUES[id]['attrs']] | 
| +        trac.ticket.getActions = lambda id: ISSUES[id]['actions'] | 
| +        trac.ticket.milestone.getAll = lambda: MILESTONES.keys() | 
| +        self.trac_proxy_mock = trac | 
| +        self._patchWith('xmlrpclib.ServerProxy', trac) | 
| +        self._patchWith('xmlrpclib.MultiCall', | 
| +                        self._create_mock_milestone_multicall()) | 
| + | 
| +    def _mock_config(self): | 
| +        config = ConfigParser.ConfigParser() | 
| +        config.add_section('hg') | 
| +        config.set('hg', 'trac_xmlrpc_url', 'foo') | 
| +        config.set('hg', 'issue_url_template', '#{id}') | 
| +        config.add_section('hg_module_milestones') | 
| +        config.set('hg_module_milestones', 'one', '.*') | 
| +        config.set('hg_module_milestones', 'two', 'other') | 
| +        config.set('hg_module_milestones', 'four', 'completed') | 
| +        self._patchWith('sitescripts.hg.bin.update_issues.get_config', config) | 
| + | 
| def setUp(self): | 
| self.ui = mock.Mock() | 
| +        self._mock_trac() | 
| +        self._mock_config() | 
|  | 
| -    @mock.patch("xmlrpclib.ServerProxy") | 
| -    def test_commits_with_invalid_message_format_ignored(self, | 
| -                                                         mock_server_proxy): | 
| -        messages = ["", "Issue #1337", "Tissue 1337", "Issue 13b"] | 
| -        for message in messages: | 
| -            mock_repo = _create_mock_repo(message) | 
| -            update_issues.hook(self.ui, mock_repo, 0) | 
| -            self.ui.warn.assert_called_once() | 
| -            self.assertFalse(mock_server_proxy.called) | 
| -            self.ui.warn.reset_mock() | 
|  | 
| -    @mock.patch("xmlrpclib.ServerProxy") | 
| -    def test_noissue_commits_ignored(self, mock_server_proxy): | 
| -        messages = ["Noissue", "noissue"] | 
| -        for message in messages: | 
| -            mock_repo = _create_mock_repo(message) | 
| -            update_issues.hook(self.ui, mock_repo, 0) | 
| -            self.assertFalse(self.ui.warn.called) | 
| -            self.assertFalse(mock_server_proxy.called) | 
| +class _MockRepo(list): | 
| +    def __init__(self, commit_messages): | 
| +        list.__init__(self) | 
| +        for i, message in enumerate(commit_messages): | 
| +            mock_commit = mock.MagicMock() | 
| +            mock_commit.rev.return_value = i | 
| +            mock_commit.hex.return_value = '{:010x}'.format(i) + '0' * 30 | 
| +            mock_commit.description.return_value = message | 
| +            self.append(mock_commit) | 
| +        self.changelog = mock.Mock() | 
| +        self.changelog.findmissingrevs = self._findmissingrevs | 
|  | 
| -    @mock.patch("xmlrpclib.ServerProxy") | 
| -    def test_single_issue_referenced(self, mock_server_proxy): | 
| -        server_proxy_instance = mock_server_proxy.return_value | 
| -        messages = ["Issue 1337", "issue 1337"] | 
| -        for message in messages: | 
| -            mock_repo = _create_mock_repo(message) | 
| -            update_issues.hook(self.ui, mock_repo, 0) | 
| -            self.assertFalse(self.ui.warn.called) | 
| -            server_proxy_instance.ticket.update.assert_called_once() | 
| -            self.assertEqual(server_proxy_instance.ticket.update.call_args[0][0], | 
| -                             1337) | 
| -            server_proxy_instance.reset_mock() | 
| +    def _findmissingrevs(self, olds, news): | 
| +        return range(olds[0] + 1, news[0] + 1) | 
|  | 
| -    @mock.patch("xmlrpclib.ServerProxy") | 
| -    def test_multiple_issues_referenced(self, mock_server_proxy): | 
| -        server_proxy_instance = mock_server_proxy.return_value | 
| -        mock_repo = _create_mock_repo("Issue 1337, issue 2448") | 
| -        update_issues.hook(self.ui, mock_repo, 0) | 
| -        self.assertFalse(self.ui.warn.called) | 
| -        calls = server_proxy_instance.ticket.update.call_args_list | 
| -        self.assertEqual(len(calls), 2) | 
| -        self.assertEqual(calls[0][0][0], 1337) | 
| -        self.assertEqual(calls[1][0][0], 2448) | 
| +    def __getitem__(self, id): | 
| +        if isinstance(id, str): | 
| +            return [commit for commit in self if commit.hex() == id][0] | 
| +        return list.__getitem__(self, id) | 
| + | 
| +    def url(self): | 
| +        return 'mock/repo' | 
| + | 
| + | 
| +class TestChangegroupHook(_TestBase): | 
| + | 
| +    def _run_hook(self, commit_messages, warning_count=0, update_count=0): | 
| +        repo = _MockRepo(commit_messages) | 
| +        update_issues.changegroup_hook(self.ui, repo, 0) | 
| +        warnings = self.ui.warn.call_args_list | 
| +        updates = self.trac_proxy_mock.ticket.update.call_args_list | 
| +        self.assertEqual(len(warnings), warning_count) | 
| +        self.assertEqual(len(updates), update_count) | 
| +        return updates | 
| + | 
| +    def test_commits_with_invalid_message_format_ignored(self): | 
| +        self._run_hook([ | 
| +            '', | 
| +            'Issue #1337 - Extraneous characters in issue number', | 
| +            'Issue 1337',  # No dash, no message. | 
| +            'Issue 1337: Colon instead of dash', | 
| +            'Noissue no dash', | 
| +            'Issue 1337-No space around dash', | 
| +            'Fixes 1337 no dash' | 
| +        ], warning_count=7) | 
| + | 
| +    def test_noissue_commits_ignored(self): | 
| +        self._run_hook(['Noissue - Foo', 'noissue - Bar'])  # No updates. | 
| + | 
| +    def test_single_issue_referenced(self): | 
| +        updates = self._run_hook(['Issue 1337 - Foo'], update_count=1) | 
| +        self.assertEqual(updates[0][0][0], 1337) | 
| + | 
| +    def test_missing_issue_referenced(self): | 
| +        self._run_hook(['Issue 42 - Bar'], warning_count=1) | 
| + | 
| +    def test_multiple_issues_referenced(self): | 
| +        updates = self._run_hook(['Issue 1337, fixes 2448 - Foo'], | 
| +                                 update_count=2) | 
| +        self.assertEqual(updates[0][0][0], 1337) | 
| +        self.assertEqual(updates[1][0][0], 2448) | 
| + | 
| +    def test_multiple_commits_for_issue(self): | 
| +        updates = self._run_hook(['Issue 1337 - Foo', 'Fixes 1337 - Bar'], | 
| +                                 update_count=1) | 
| +        comment = updates[0][0][1] | 
| +        self.assertIn('000000000000', comment) | 
| +        self.assertIn('000000000100', comment) | 
| + | 
| + | 
| +class TestPushkeyHook(_TestBase): | 
| + | 
| +    def _run_hook(self, commit_messages, bookmark='master', | 
| +                  warning_count=0, update_count=0): | 
| +        repo = _MockRepo(['Base', 'Old'] + commit_messages) | 
| +        update_issues.pushkey_hook(self.ui, repo, | 
| +                                   namespace='bookmarks', key=bookmark, | 
| +                                   old=1, new=1 + len(commit_messages)) | 
| +        warnings = self.ui.warn.call_args_list | 
| +        updates = self.trac_proxy_mock.ticket.update.call_args_list | 
| +        self.assertEqual(len(warnings), warning_count) | 
| +        self.assertEqual(len(updates), update_count) | 
| +        return updates | 
| + | 
| +    def _check_update(self, update, issue_id, action='resolve', | 
| +                      milestone='current'): | 
| +        self.assertEqual(update[0][0], issue_id) | 
| +        changes = update[0][2] | 
| +        self.assertEqual(changes['action'], action) | 
| +        if milestone is None: | 
| +            self.assertNotIn('milestone', changes) | 
| +        else: | 
| +            self.assertEqual(changes['milestone'], milestone) | 
| + | 
| +    def test_move_other_bookmark(self): | 
| +        self._run_hook(['Fixes 1337 - Foo'], bookmark='other')  # No updates. | 
| + | 
| +    def test_one_issue_fixed(self): | 
| +        updates = self._run_hook(['Fixes 1337 - Foo'], update_count=1) | 
| +        self._check_update(updates[0], 1337) | 
| + | 
| +    def test_fix_closed_issue(self): | 
| +        updates = self._run_hook(['fixes 2448 - Foo'], update_count=1) | 
| +        self._check_update(updates[0], 2448, action='leave', milestone='other') | 
| + | 
| +    def test_fix_issue_noregexp(self): | 
| +        updates = self._run_hook(['Fixes 4670 - Foo'], update_count=1) | 
| +        self._check_update(updates[0], 4670, milestone=None) | 
| + | 
| +    def test_fix_issue_no_matching_milestones(self): | 
| +        updates = self._run_hook(['Fixes 5781 - Foo'], update_count=1) | 
| +        self._check_update(updates[0], 5781, milestone=None) | 
| + | 
| +    def test_fix_many(self): | 
| +        updates = self._run_hook(['Fixes 1337 - Foo', 'Fixes 2448 - Bar'], | 
| +                                 update_count=2) | 
| +        self._check_update(updates[0], 1337) | 
| +        self._check_update(updates[1], 2448, action='leave', milestone='other') | 
| + | 
| +    def test_fix_nonexistent(self): | 
| +        self._run_hook(['Fixes 7331 - Foo'], warning_count=1) | 
| + | 
| +    def test_fix_closed_with_assigned_milestone(self): | 
| +        self._run_hook(['fixes 3559 - Foo'])  # No updates. | 
| + | 
|  | 
| if __name__ == "__main__": | 
| unittest.main() | 
|  |