# HG changeset patch # User Martin Bornhold # Date 2016-07-08 14:40:16 # Node ID 82af22512352bdb13439736c01ebda6c90051110 # Parent 6ab62426f28bffc461d20b8d05eb6e0518b9bef6 tests: Add tests for hg invalidation of vcsserver cache. diff --git a/rhodecode/tests/vcs/test_hg_vcsserver_cache_invalidation.py b/rhodecode/tests/vcs/test_hg_vcsserver_cache_invalidation.py new file mode 100644 --- /dev/null +++ b/rhodecode/tests/vcs/test_hg_vcsserver_cache_invalidation.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2016-2016 RhodeCode GmbH +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License, version 3 +# (only), as published by the Free Software Foundation. +# +# This program 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 Affero General Public License +# along with this program. If not, see . +# +# This program is dual-licensed. If you wish to learn more about the +# RhodeCode Enterprise Edition, including its added features, Support services, +# and proprietary license terms, please see https://rhodecode.com/licenses/ + +import pytest + +from mock import patch + +from rhodecode.lib.vcs.backends.base import Reference + + +class TestMercurialRemoteRepoInvalidation(object): + ref_stub = Reference('branch', 'default', None) + + @pytest.mark.parametrize('method_name', [ + 'bookmark', + 'commit', + 'pull', + 'pull_cmd', + 'push', + 'rebase', + 'strip', + 'strip', + ]) + def test_method_invokes_invalidate_on_remote_repo( + self, method_name, backend_hg): + """ + Check that the listed methods call invalidate_vcs_cache on their remote + repo instance. + """ + from rhodecode.lib.vcs import client_http + repo = backend_hg.repo.scm_instance() + remote = repo._remote + with patch.object(remote, 'invalidate_vcs_cache') as invalidate_cache: + with patch.object(client_http, '_remote_call'): + method = getattr(remote, method_name) + method() + assert invalidate_cache.called + + def _prepare_shadow_repo(self, pull_request): + """ + Helper that creates a shadow repo that can be used to reproduce the + CommitDoesNotExistError when pulling in from target and source + references. + """ + from rhodecode.model.pull_request import PullRequestModel + + target_vcs = pull_request.target_repo.scm_instance() + target_ref = pull_request.target_ref_parts + source_ref = pull_request.source_ref_parts + + # Create shadow repository. + pr = PullRequestModel() + workspace_id = pr._workspace_id(pull_request) + shadow_repository_path = target_vcs._maybe_prepare_merge_workspace( + workspace_id, target_ref) + shadow_repo = target_vcs._get_shadow_instance(shadow_repository_path) + + # This will populate the cache of the mercurial repository object + # inside of the VCSServer. + shadow_repo.get_commit() + + return shadow_repo, source_ref, target_ref + + @pytest.mark.backends('hg') + def test_commit_does_not_exist_error_happens(self, pr_util): + from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError + + pull_request = pr_util.create_pull_request() + target_vcs = pull_request.target_repo.scm_instance() + source_vcs = pull_request.source_repo.scm_instance() + shadow_repo, source_ref, target_ref = self._prepare_shadow_repo( + pull_request) + + # Pull from target and source references but without invalidation of + # RemoteRepo objects and without VCSServer caching of mercurial + # repository objects. + with patch.object(shadow_repo._remote, 'invalidate_vcs_cache'): + # NOTE: Do not use patch.dict() to disable the cache because it + # restores the WHOLE dict and not only the patched keys. + shadow_repo._remote._wire['cache'] = False + shadow_repo._local_pull(target_vcs.path, target_ref) + shadow_repo._local_pull(source_vcs.path, source_ref) + shadow_repo._remote._wire.pop('cache') + + # Try to lookup the target_ref in shadow repo. This should work because + # the shadow repo is a clone of the target and always contains all off + # it's commits in the initial cache. + shadow_repo.get_commit(target_ref.commit_id) + + # If we try to lookup the source_ref it should fail because the shadow + # repo commit cache doesn't get invalidated. (Due to patched + # invalidation and caching above). + with pytest.raises(CommitDoesNotExistError): + shadow_repo.get_commit(source_ref.commit_id) + + @pytest.mark.backends('hg') + def test_commit_does_not_exist_error_does_not_happen(self, pr_util): + pull_request = pr_util.create_pull_request() + target_vcs = pull_request.target_repo.scm_instance() + source_vcs = pull_request.source_repo.scm_instance() + shadow_repo, source_ref, target_ref = self._prepare_shadow_repo( + pull_request) + + # Pull from target and source references without without VCSServer + # caching of mercurial repository objects but with active invalidation + # of RemoteRepo objects. + # NOTE: Do not use patch.dict() to disable the cache because it + # restores the WHOLE dict and not only the patched keys. + shadow_repo._remote._wire['cache'] = False + shadow_repo._local_pull(target_vcs.path, target_ref) + shadow_repo._local_pull(source_vcs.path, source_ref) + shadow_repo._remote._wire.pop('cache') + + # Try to lookup the target and source references in shadow repo. This + # should work because the RemoteRepo object gets invalidated during the + # above pull operations. + shadow_repo.get_commit(target_ref.commit_id) + shadow_repo.get_commit(source_ref.commit_id)