blob: e85a03286403c422604601d92a974dfcfbcc56f1 [file] [log] [blame]
// 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 static com.google.gerrit.testing.TestActionRefUpdateContext.openTestRefUpdateContext;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.only;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.AccountGroup;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.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.update.context.RefUpdateContext;
import com.google.gerrit.server.util.time.TimeUtil;
import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.gerrit.testing.TestTimeUtil;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
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 {
private InMemoryRepositoryManager repoManager;
private Project.NameKey project;
private Repository repo;
@Before
public void setUp() throws Exception {
repoManager = new InMemoryRepositoryManager();
project = 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.Builder().reset(project).build())) {
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.Builder()
.reset(project, "refs/match/*", "refs/another-match/*")
.build())) {
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.Builder()
.reset(project, "refs/match/*", "refs/another-match/*")
.build())) {
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 = 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.Builder()
.reset(project, "refs/foo/*")
.reset(project2, "refs/bar/*")
.build())) {
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 = Project.nameKey("bar");
Repository repo2 = repoManager.createRepository(project2);
Ref matchingRefProject1;
Ref nonMatchingRefProject1;
Ref matchingRefProject2;
Ref nonMatchingRefProject2;
try (ProjectResetter resetProject =
builder()
.build(
new ProjectResetter.Config.Builder()
.reset(project, "refs/foo/*")
.reset(project2, "refs/bar/*")
.build())) {
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.Builder()
.reset(project, "refs/match/*", "refs/match/test")
.build())) {
// 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 = Project.nameKey("bar");
Repository repo2 = repoManager.createRepository(project2);
Ref metaConfig = createRef(repo2, RefNames.REFS_CONFIG);
ProjectCache projectCache = mock(ProjectCache.class);
Ref nonMetaConfig = createRef("refs/heads/master");
try (ProjectResetter resetProject =
builder(null, null, null, null, null, null, projectCache)
.build(new ProjectResetter.Config.Builder().reset(project).reset(project2).build())) {
updateRef(nonMetaConfig);
updateRef(repo2, metaConfig);
}
verify(projectCache, only()).evictAndReindex(project2);
}
@Test
public void projectEvictionIfRefsMetaConfigIsDeleted() throws Exception {
Project.NameKey project2 = Project.nameKey("bar");
Repository repo2 = repoManager.createRepository(project2);
ProjectCache projectCache = mock(ProjectCache.class);
try (ProjectResetter resetProject =
builder(null, null, null, null, null, null, projectCache)
.build(new ProjectResetter.Config.Builder().reset(project).reset(project2).build())) {
createRef("refs/heads/master");
createRef(repo2, RefNames.REFS_CONFIG);
}
verify(projectCache, only()).evictAndReindex(project2);
}
@Test
public void groupEviction() throws Exception {
AccountGroup.UUID uuid1 = AccountGroup.uuid("abcd1");
AccountGroup.UUID uuid2 = AccountGroup.uuid("abcd2");
AccountGroup.UUID uuid3 = AccountGroup.uuid("abcd3");
Project.NameKey allUsers = Project.nameKey(AllUsersNameProvider.DEFAULT);
Repository allUsersRepo = repoManager.createRepository(allUsers);
GroupCache cache = mock(GroupCache.class);
GroupIndexer indexer = mock(GroupIndexer.class);
GroupIncludeCache includeCache = mock(GroupIncludeCache.class);
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.Builder().reset(project).reset(allUsers).build())) {
updateRef(allUsersRepo, ref2);
createRef(allUsersRepo, RefNames.refsGroups(uuid3));
}
verify(cache).evict(uuid2);
verify(indexer).index(uuid2);
verify(includeCache).evictParentGroupsOf(uuid2);
verify(cache).evict(uuid3);
verify(indexer).index(uuid3);
verify(includeCache).evictParentGroupsOf(uuid3);
verifyNoMoreInteractions(cache, indexer, includeCache);
}
@CanIgnoreReturnValue
private Ref createRef(String ref) throws IOException {
return createRef(repo, ref);
}
@CanIgnoreReturnValue
private Ref createRef(Repository repo, String ref) throws IOException {
try (RefUpdateContext ctx = openTestRefUpdateContext()) {
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);
}
}
}
@CanIgnoreReturnValue
private Ref updateRef(Ref ref) throws IOException {
return updateRef(repo, ref);
}
@CanIgnoreReturnValue
private Ref updateRef(Repository repo, Ref ref) throws IOException {
try (RefUpdateContext ctx = openTestRefUpdateContext()) {
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.now());
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);
}
}