| // Copyright (C) 2017 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package com.google.gerrit.acceptance; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import com.google.common.collect.ImmutableSet; |
| import com.google.gerrit.common.Nullable; |
| import com.google.gerrit.reviewdb.client.Account; |
| import com.google.gerrit.reviewdb.client.AccountGroup; |
| import com.google.gerrit.reviewdb.client.Project; |
| import com.google.gerrit.reviewdb.client.RefNames; |
| import com.google.gerrit.server.account.AccountCache; |
| import com.google.gerrit.server.account.GroupCache; |
| import com.google.gerrit.server.account.GroupIncludeCache; |
| import com.google.gerrit.server.config.AllUsersName; |
| import com.google.gerrit.server.config.AllUsersNameProvider; |
| import com.google.gerrit.server.index.account.AccountIndexer; |
| import com.google.gerrit.server.index.group.GroupIndexer; |
| import com.google.gerrit.server.project.ProjectCache; |
| import com.google.gerrit.server.util.time.TimeUtil; |
| import com.google.gerrit.testing.GerritBaseTests; |
| import com.google.gerrit.testing.InMemoryRepositoryManager; |
| import com.google.gerrit.testing.TestTimeUtil; |
| import java.io.IOException; |
| import java.util.concurrent.TimeUnit; |
| import org.easymock.EasyMock; |
| import org.eclipse.jgit.lib.CommitBuilder; |
| import org.eclipse.jgit.lib.Constants; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.lib.ObjectInserter; |
| import org.eclipse.jgit.lib.PersonIdent; |
| import org.eclipse.jgit.lib.Ref; |
| import org.eclipse.jgit.lib.RefUpdate; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| public class ProjectResetterTest extends GerritBaseTests { |
| private InMemoryRepositoryManager repoManager; |
| private Project.NameKey project; |
| private Repository repo; |
| |
| @Before |
| public void setUp() throws Exception { |
| repoManager = new InMemoryRepositoryManager(); |
| project = new Project.NameKey("foo"); |
| repo = repoManager.createRepository(project); |
| } |
| |
| @Before |
| public void setTimeForTesting() { |
| TestTimeUtil.resetWithClockStep(1, TimeUnit.SECONDS); |
| } |
| |
| @After |
| public void resetTime() { |
| TestTimeUtil.useSystemTime(); |
| } |
| |
| @Test |
| public void resetAllRefs() throws Exception { |
| Ref matchingRef = createRef("refs/any/test"); |
| |
| try (ProjectResetter resetProject = |
| builder().build(new ProjectResetter.Config().reset(project))) { |
| updateRef(matchingRef); |
| } |
| |
| // The matching refs are reset to the old state. |
| assertRef(matchingRef); |
| } |
| |
| @Test |
| public void onlyResetMatchingRefs() throws Exception { |
| Ref matchingRef = createRef("refs/match/test"); |
| Ref anotherMatchingRef = createRef("refs/another-match/test"); |
| Ref nonMatchingRef = createRef("refs/no-match/test"); |
| |
| Ref updatedNonMatchingRef; |
| try (ProjectResetter resetProject = |
| builder() |
| .build( |
| new ProjectResetter.Config() |
| .reset(project, "refs/match/*", "refs/another-match/*"))) { |
| updateRef(matchingRef); |
| updateRef(anotherMatchingRef); |
| updatedNonMatchingRef = updateRef(nonMatchingRef); |
| } |
| |
| // The matching refs are reset to the old state. |
| assertRef(matchingRef); |
| assertRef(anotherMatchingRef); |
| |
| // The non-matching ref is not reset, hence it still has the updated state. |
| assertRef(updatedNonMatchingRef); |
| } |
| |
| @Test |
| public void onlyDeleteNewlyCreatedMatchingRefs() throws Exception { |
| Ref matchingRef; |
| Ref anotherMatchingRef; |
| Ref nonMatchingRef; |
| try (ProjectResetter resetProject = |
| builder() |
| .build( |
| new ProjectResetter.Config() |
| .reset(project, "refs/match/*", "refs/another-match/*"))) { |
| matchingRef = createRef("refs/match/test"); |
| anotherMatchingRef = createRef("refs/another-match/test"); |
| nonMatchingRef = createRef("refs/no-match/test"); |
| } |
| |
| // The matching refs are deleted since they didn't exist before. |
| assertDeletedRef(matchingRef); |
| assertDeletedRef(anotherMatchingRef); |
| |
| // The non-matching ref is not deleted. |
| assertRef(nonMatchingRef); |
| } |
| |
| @Test |
| public void onlyResetMatchingRefsMultipleProjects() throws Exception { |
| Project.NameKey project2 = new Project.NameKey("bar"); |
| Repository repo2 = repoManager.createRepository(project2); |
| |
| Ref matchingRefProject1 = createRef("refs/foo/test"); |
| Ref nonMatchingRefProject1 = createRef("refs/bar/test"); |
| |
| Ref matchingRefProject2 = createRef(repo2, "refs/bar/test"); |
| Ref nonMatchingRefProject2 = createRef(repo2, "refs/foo/test"); |
| |
| Ref updatedNonMatchingRefProject1; |
| Ref updatedNonMatchingRefProject2; |
| try (ProjectResetter resetProject = |
| builder() |
| .build( |
| new ProjectResetter.Config() |
| .reset(project, "refs/foo/*") |
| .reset(project2, "refs/bar/*"))) { |
| updateRef(matchingRefProject1); |
| updatedNonMatchingRefProject1 = updateRef(nonMatchingRefProject1); |
| |
| updateRef(repo2, matchingRefProject2); |
| updatedNonMatchingRefProject2 = updateRef(repo2, nonMatchingRefProject2); |
| } |
| |
| // The matching refs are reset to the old state. |
| assertRef(matchingRefProject1); |
| assertRef(repo2, matchingRefProject2); |
| |
| // The non-matching refs are not reset, hence they still has the updated states. |
| assertRef(updatedNonMatchingRefProject1); |
| assertRef(repo2, updatedNonMatchingRefProject2); |
| } |
| |
| @Test |
| public void onlyDeleteNewlyCreatedMatchingRefsMultipleProjects() throws Exception { |
| Project.NameKey project2 = new Project.NameKey("bar"); |
| Repository repo2 = repoManager.createRepository(project2); |
| |
| Ref matchingRefProject1; |
| Ref nonMatchingRefProject1; |
| Ref matchingRefProject2; |
| Ref nonMatchingRefProject2; |
| try (ProjectResetter resetProject = |
| builder() |
| .build( |
| new ProjectResetter.Config() |
| .reset(project, "refs/foo/*") |
| .reset(project2, "refs/bar/*"))) { |
| matchingRefProject1 = createRef("refs/foo/test"); |
| nonMatchingRefProject1 = createRef("refs/bar/test"); |
| |
| matchingRefProject2 = createRef(repo2, "refs/bar/test"); |
| nonMatchingRefProject2 = createRef(repo2, "refs/foo/test"); |
| } |
| |
| // The matching refs are deleted since they didn't exist before. |
| assertDeletedRef(matchingRefProject1); |
| assertDeletedRef(repo2, matchingRefProject2); |
| |
| // The non-matching ref is not deleted. |
| assertRef(nonMatchingRefProject1); |
| assertRef(repo2, nonMatchingRefProject2); |
| } |
| |
| @Test |
| public void onlyDeleteNewlyCreatedWithOverlappingRefPatterns() throws Exception { |
| Ref matchingRef; |
| try (ProjectResetter resetProject = |
| builder() |
| .build( |
| new ProjectResetter.Config().reset(project, "refs/match/*", "refs/match/test"))) { |
| // This ref matches 2 ref pattern, ProjectResetter should try to delete it only once. |
| matchingRef = createRef("refs/match/test"); |
| } |
| |
| // The matching ref is deleted since it didn't exist before. |
| assertDeletedRef(matchingRef); |
| } |
| |
| @Test |
| public void projectEvictionIfRefsMetaConfigIsReset() throws Exception { |
| Project.NameKey project2 = new Project.NameKey("bar"); |
| Repository repo2 = repoManager.createRepository(project2); |
| Ref metaConfig = createRef(repo2, RefNames.REFS_CONFIG); |
| |
| ProjectCache projectCache = EasyMock.createNiceMock(ProjectCache.class); |
| projectCache.evict(project2); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(projectCache); |
| |
| Ref nonMetaConfig = createRef("refs/heads/master"); |
| |
| try (ProjectResetter resetProject = |
| builder(null, null, null, null, null, null, projectCache) |
| .build(new ProjectResetter.Config().reset(project).reset(project2))) { |
| updateRef(nonMetaConfig); |
| updateRef(repo2, metaConfig); |
| } |
| |
| EasyMock.verify(projectCache); |
| } |
| |
| @Test |
| public void projectEvictionIfRefsMetaConfigIsDeleted() throws Exception { |
| Project.NameKey project2 = new Project.NameKey("bar"); |
| Repository repo2 = repoManager.createRepository(project2); |
| |
| ProjectCache projectCache = EasyMock.createNiceMock(ProjectCache.class); |
| projectCache.evict(project2); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(projectCache); |
| |
| try (ProjectResetter resetProject = |
| builder(null, null, null, null, null, null, projectCache) |
| .build(new ProjectResetter.Config().reset(project).reset(project2))) { |
| createRef("refs/heads/master"); |
| createRef(repo2, RefNames.REFS_CONFIG); |
| } |
| |
| EasyMock.verify(projectCache); |
| } |
| |
| @Test |
| public void accountEvictionIfUserBranchIsReset() throws Exception { |
| Account.Id accountId = new Account.Id(1); |
| Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT); |
| Repository allUsersRepo = repoManager.createRepository(allUsers); |
| Ref userBranch = createRef(allUsersRepo, RefNames.refsUsers(accountId)); |
| |
| AccountCache accountCache = EasyMock.createNiceMock(AccountCache.class); |
| accountCache.evict(accountId); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(accountCache); |
| |
| AccountIndexer accountIndexer = EasyMock.createNiceMock(AccountIndexer.class); |
| accountIndexer.index(accountId); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(accountIndexer); |
| |
| // Non-user branch because it's not in All-Users. |
| Ref nonUserBranch = createRef(RefNames.refsUsers(new Account.Id(2))); |
| |
| try (ProjectResetter resetProject = |
| builder(null, accountCache, accountIndexer, null, null, null, null) |
| .build(new ProjectResetter.Config().reset(project).reset(allUsers))) { |
| updateRef(nonUserBranch); |
| updateRef(allUsersRepo, userBranch); |
| } |
| |
| EasyMock.verify(accountCache, accountIndexer); |
| } |
| |
| @Test |
| public void accountEvictionIfUserBranchIsDeleted() throws Exception { |
| Account.Id accountId = new Account.Id(1); |
| Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT); |
| Repository allUsersRepo = repoManager.createRepository(allUsers); |
| |
| AccountCache accountCache = EasyMock.createNiceMock(AccountCache.class); |
| accountCache.evict(accountId); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(accountCache); |
| |
| AccountIndexer accountIndexer = EasyMock.createNiceMock(AccountIndexer.class); |
| accountIndexer.index(accountId); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(accountIndexer); |
| |
| try (ProjectResetter resetProject = |
| builder(null, accountCache, accountIndexer, null, null, null, null) |
| .build(new ProjectResetter.Config().reset(project).reset(allUsers))) { |
| // Non-user branch because it's not in All-Users. |
| createRef(RefNames.refsUsers(new Account.Id(2))); |
| |
| createRef(allUsersRepo, RefNames.refsUsers(accountId)); |
| } |
| |
| EasyMock.verify(accountCache, accountIndexer); |
| } |
| |
| @Test |
| public void accountEvictionIfExternalIdsBranchIsReset() throws Exception { |
| Account.Id accountId = new Account.Id(1); |
| Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT); |
| Repository allUsersRepo = repoManager.createRepository(allUsers); |
| Ref externalIds = createRef(allUsersRepo, RefNames.REFS_EXTERNAL_IDS); |
| createRef(allUsersRepo, RefNames.refsUsers(accountId)); |
| |
| Account.Id accountId2 = new Account.Id(2); |
| |
| AccountCache accountCache = EasyMock.createNiceMock(AccountCache.class); |
| accountCache.evict(accountId); |
| EasyMock.expectLastCall(); |
| accountCache.evict(accountId2); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(accountCache); |
| |
| AccountIndexer accountIndexer = EasyMock.createNiceMock(AccountIndexer.class); |
| accountIndexer.index(accountId); |
| EasyMock.expectLastCall(); |
| accountIndexer.index(accountId2); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(accountIndexer); |
| |
| // Non-user branch because it's not in All-Users. |
| Ref nonUserBranch = createRef(RefNames.refsUsers(new Account.Id(3))); |
| |
| try (ProjectResetter resetProject = |
| builder(null, accountCache, accountIndexer, null, null, null, null) |
| .build(new ProjectResetter.Config().reset(project).reset(allUsers))) { |
| updateRef(nonUserBranch); |
| updateRef(allUsersRepo, externalIds); |
| createRef(allUsersRepo, RefNames.refsUsers(accountId2)); |
| } |
| |
| EasyMock.verify(accountCache, accountIndexer); |
| } |
| |
| @Test |
| public void accountEvictionIfExternalIdsBranchIsDeleted() throws Exception { |
| Account.Id accountId = new Account.Id(1); |
| Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT); |
| Repository allUsersRepo = repoManager.createRepository(allUsers); |
| createRef(allUsersRepo, RefNames.refsUsers(accountId)); |
| |
| Account.Id accountId2 = new Account.Id(2); |
| |
| AccountCache accountCache = EasyMock.createNiceMock(AccountCache.class); |
| accountCache.evict(accountId); |
| EasyMock.expectLastCall(); |
| accountCache.evict(accountId2); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(accountCache); |
| |
| AccountIndexer accountIndexer = EasyMock.createNiceMock(AccountIndexer.class); |
| accountIndexer.index(accountId); |
| EasyMock.expectLastCall(); |
| accountIndexer.index(accountId2); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(accountIndexer); |
| |
| // Non-user branch because it's not in All-Users. |
| Ref nonUserBranch = createRef(RefNames.refsUsers(new Account.Id(3))); |
| |
| try (ProjectResetter resetProject = |
| builder(null, accountCache, accountIndexer, null, null, null, null) |
| .build(new ProjectResetter.Config().reset(project).reset(allUsers))) { |
| updateRef(nonUserBranch); |
| createRef(allUsersRepo, RefNames.REFS_EXTERNAL_IDS); |
| createRef(allUsersRepo, RefNames.refsUsers(accountId2)); |
| } |
| |
| EasyMock.verify(accountCache, accountIndexer); |
| } |
| |
| @Test |
| public void accountEvictionFromAccountCreatorIfUserBranchIsDeleted() throws Exception { |
| Account.Id accountId = new Account.Id(1); |
| Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT); |
| Repository allUsersRepo = repoManager.createRepository(allUsers); |
| |
| AccountCreator accountCreator = EasyMock.createNiceMock(AccountCreator.class); |
| accountCreator.evict(ImmutableSet.of(accountId)); |
| EasyMock.expectLastCall(); |
| EasyMock.replay(accountCreator); |
| |
| try (ProjectResetter resetProject = |
| builder(accountCreator, null, null, null, null, null, null) |
| .build(new ProjectResetter.Config().reset(project).reset(allUsers))) { |
| createRef(allUsersRepo, RefNames.refsUsers(accountId)); |
| } |
| |
| EasyMock.verify(accountCreator); |
| } |
| |
| @Test |
| public void groupEviction() throws Exception { |
| AccountGroup.UUID uuid1 = new AccountGroup.UUID("abcd1"); |
| AccountGroup.UUID uuid2 = new AccountGroup.UUID("abcd2"); |
| AccountGroup.UUID uuid3 = new AccountGroup.UUID("abcd3"); |
| Project.NameKey allUsers = new Project.NameKey(AllUsersNameProvider.DEFAULT); |
| Repository allUsersRepo = repoManager.createRepository(allUsers); |
| |
| GroupCache cache = EasyMock.createNiceMock(GroupCache.class); |
| GroupIndexer indexer = EasyMock.createNiceMock(GroupIndexer.class); |
| GroupIncludeCache includeCache = EasyMock.createNiceMock(GroupIncludeCache.class); |
| cache.evict(uuid2); |
| indexer.index(uuid2); |
| includeCache.evictParentGroupsOf(uuid2); |
| cache.evict(uuid3); |
| indexer.index(uuid3); |
| includeCache.evictParentGroupsOf(uuid3); |
| EasyMock.expectLastCall(); |
| |
| EasyMock.replay(cache, indexer); |
| |
| createRef(allUsersRepo, RefNames.refsGroups(uuid1)); |
| Ref ref2 = createRef(allUsersRepo, RefNames.refsGroups(uuid2)); |
| try (ProjectResetter resetProject = |
| builder(null, null, null, cache, includeCache, indexer, null) |
| .build(new ProjectResetter.Config().reset(project).reset(allUsers))) { |
| updateRef(allUsersRepo, ref2); |
| createRef(allUsersRepo, RefNames.refsGroups(uuid3)); |
| } |
| |
| EasyMock.verify(cache, indexer); |
| } |
| |
| private Ref createRef(String ref) throws IOException { |
| return createRef(repo, ref); |
| } |
| |
| private Ref createRef(Repository repo, String ref) throws IOException { |
| try (ObjectInserter oi = repo.newObjectInserter(); |
| RevWalk rw = new RevWalk(repo)) { |
| ObjectId emptyCommit = createCommit(repo); |
| RefUpdate updateRef = repo.updateRef(ref); |
| updateRef.setExpectedOldObjectId(ObjectId.zeroId()); |
| updateRef.setNewObjectId(emptyCommit); |
| assertThat(updateRef.update(rw)).isEqualTo(RefUpdate.Result.NEW); |
| return repo.exactRef(ref); |
| } |
| } |
| |
| private Ref updateRef(Ref ref) throws IOException { |
| return updateRef(repo, ref); |
| } |
| |
| private Ref updateRef(Repository repo, Ref ref) throws IOException { |
| try (ObjectInserter oi = repo.newObjectInserter(); |
| RevWalk rw = new RevWalk(repo)) { |
| ObjectId emptyCommit = createCommit(repo); |
| RefUpdate updateRef = repo.updateRef(ref.getName()); |
| updateRef.setExpectedOldObjectId(ref.getObjectId()); |
| updateRef.setNewObjectId(emptyCommit); |
| updateRef.setForceUpdate(true); |
| assertThat(updateRef.update(rw)).isEqualTo(RefUpdate.Result.FORCED); |
| Ref updatedRef = repo.exactRef(ref.getName()); |
| assertThat(updatedRef.getObjectId()).isNotEqualTo(ref.getObjectId()); |
| return updatedRef; |
| } |
| } |
| |
| private void assertRef(Ref ref) throws IOException { |
| assertRef(repo, ref); |
| } |
| |
| private void assertRef(Repository repo, Ref ref) throws IOException { |
| assertThat(repo.exactRef(ref.getName()).getObjectId()).isEqualTo(ref.getObjectId()); |
| } |
| |
| private void assertDeletedRef(Ref ref) throws IOException { |
| assertDeletedRef(repo, ref); |
| } |
| |
| private void assertDeletedRef(Repository repo, Ref ref) throws IOException { |
| assertThat(repo.exactRef(ref.getName())).isNull(); |
| } |
| |
| private ObjectId createCommit(Repository repo) throws IOException { |
| try (ObjectInserter oi = repo.newObjectInserter()) { |
| PersonIdent ident = |
| new PersonIdent(new PersonIdent("Foo Bar", "foo.bar@baz.com"), TimeUtil.nowTs()); |
| CommitBuilder cb = new CommitBuilder(); |
| cb.setTreeId(oi.insert(Constants.OBJ_TREE, new byte[] {})); |
| cb.setCommitter(ident); |
| cb.setAuthor(ident); |
| cb.setMessage("Test commit"); |
| |
| ObjectId commit = oi.insert(cb); |
| oi.flush(); |
| return commit; |
| } |
| } |
| |
| private ProjectResetter.Builder builder() { |
| return builder(null, null, null, null, null, null, null); |
| } |
| |
| private ProjectResetter.Builder builder( |
| @Nullable AccountCreator accountCreator, |
| @Nullable AccountCache accountCache, |
| @Nullable AccountIndexer accountIndexer, |
| @Nullable GroupCache groupCache, |
| @Nullable GroupIncludeCache groupIncludeCache, |
| @Nullable GroupIndexer groupIndexer, |
| @Nullable ProjectCache projectCache) { |
| return new ProjectResetter.Builder( |
| repoManager, |
| new AllUsersName(AllUsersNameProvider.DEFAULT), |
| accountCreator, |
| accountCache, |
| accountIndexer, |
| groupCache, |
| groupIncludeCache, |
| groupIndexer, |
| projectCache); |
| } |
| } |