blob: 3939c67084a838b2929a0b8912ead91478dc630a [file] [log] [blame]
// Copyright (C) 2019 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.git;
import static com.google.common.truth.Truth.assertThat;
import static java.util.stream.Collectors.toMap;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.AcceptanceTestRequestScope;
import com.google.gerrit.acceptance.NoHttpd;
import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.ChangeRefCache;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackend.RefFilterOptions;
import com.google.gerrit.server.query.change.ChangeData;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import javax.inject.Inject;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.junit.Before;
import org.junit.Test;
/**
* Tests the ChangeRefCache by running ls-remote calls and conditionally disabling the index and
* NoteDb. The cache is enabled by default.
*
* <p>Why are we not just testing ChangeRefCache directly? Our ref filtering code is rather complex
* and it is easy to get something wrong there. We want our assumptions about the performance of the
* cache to be validated against the entire component rather than just the cache.
*/
@NoHttpd
public class ChangeRefCacheIT extends AbstractDaemonTest {
@Inject private ChangeRefCache changeRefCache;
@Inject private PermissionBackend permissionBackend;
@Inject private RequestScopeOperations requestScopeOperations;
@Before
public void setUp() throws Exception {
// We want full ref evaluation so that we hit the cache every time.
baseConfig.setBoolean("auth", null, "skipFullRefEvaluationIfAllRefsAreVisible", false);
}
/**
* Ensure we use only the change index for getting initial data populated and don't touch NoteDb.
*/
@Test
public void useIndexForBootstrapping() throws Exception {
ChangeData change = createChange().getChange();
// TODO(hiesel) Rework as AutoClosable. Here and below.
changeRefCache.resetBootstrappedProjects();
AcceptanceTestRequestScope.Context ctx = disableDb();
try {
assertUploadPackRefs(
"HEAD",
"refs/heads/master",
RefNames.changeMetaRef(change.getId()),
change.currentPatchSet().getId().toRefName());
} finally {
enableDb(ctx);
}
}
/**
* Ensure we use only the change index for getting initial data populated and don't require any
* storage backend after that as long the data didn't change.
*/
@Test
public void serveResultsFromCacheAfterInitialBootstrap() throws Exception {
ChangeData change = createChange().getChange();
changeRefCache.resetBootstrappedProjects();
AcceptanceTestRequestScope.Context ctx = disableDb();
try {
assertUploadPackRefs(
"HEAD",
"refs/heads/master",
RefNames.changeMetaRef(change.getId()),
change.currentPatchSet().getId().toRefName());
} finally {
enableDb(ctx);
}
// No change since our first call, so this time we don't bootstrap or touch NoteDb
AcceptanceTestRequestScope.Context ctx2 = disableDb();
try {
try (AutoCloseable ignored = disableChangeIndex()) {
assertUploadPackRefs(
"HEAD",
"refs/heads/master",
RefNames.changeMetaRef(change.getId()),
change.currentPatchSet().getId().toRefName());
}
} finally {
enableDb(ctx2);
}
}
/**
* Ensure we use only the change index for getting initial data populated and NoteDb for reloading
* data that changed since.
*/
@Test
public void useIndexForBootstrappingAndDbForDeltaReload() throws Exception {
ChangeData change1 = createChange().getChange();
AcceptanceTestRequestScope.Context ctx = disableDb();
// Bootstrap: No NoteDb access as we expect it to use the index.
changeRefCache.resetBootstrappedProjects();
try {
assertUploadPackRefs(
"HEAD",
"refs/heads/master",
RefNames.changeMetaRef(change1.getId()),
change1.currentPatchSet().getId().toRefName());
} finally {
enableDb(ctx);
}
// Delta reload: No index access as we expect it to use NoteDb.
ChangeData change2 = createChange().getChange();
try (AutoCloseable ignored = disableChangeIndex()) {
assertUploadPackRefs(
"HEAD",
"refs/heads/master",
RefNames.changeMetaRef(change1.getId()),
change1.currentPatchSet().getId().toRefName(),
RefNames.changeMetaRef(change2.getId()),
change2.currentPatchSet().getId().toRefName());
}
}
/**
* Ensure we use only the change index for getting initial data populated and NoteDb for reloading
* data that changed since.
*/
@Test
public void useDbForDeltaReloadOnNewPatchSet() throws Exception {
ChangeData change1 =
pushFactory
.create(admin.getIdent(), testRepo, "original subject", "a", "a1")
.to("refs/for/master")
.getChange();
AcceptanceTestRequestScope.Context ctx = disableDb();
// Bootstrap: No NoteDb access as we expect it to use the index.
changeRefCache.resetBootstrappedProjects();
try {
assertUploadPackRefs(
"HEAD",
"refs/heads/master",
RefNames.changeMetaRef(change1.getId()),
change1.currentPatchSet().getId().toRefName());
} finally {
enableDb(ctx);
}
// Delta reload: No index access as we expect it to use NoteDb.
ChangeData change2 =
pushFactory
.create(
admin.getIdent(), testRepo, "subject2", "a", "a2", change1.change().getKey().get())
.to("refs/for/master")
.getChange();
List<PatchSet> patchSets = ImmutableList.copyOf(change2.patchSets());
assertThat(patchSets).hasSize(2);
try (AutoCloseable ctx2 = disableChangeIndex()) {
assertUploadPackRefs(
"HEAD",
"refs/heads/master",
RefNames.changeMetaRef(change1.getId()),
patchSets.get(0).getId().toRefName(),
patchSets.get(1).getId().toRefName());
}
}
/**
* Ensure we use only the change index for getting initial data populated and NoteDb for reloading
* data that changed since.
*/
@Test
public void useDbForIterativeFetchingOnMetadataChange() throws Exception {
ChangeData change1 =
pushFactory
.create(admin.getIdent(), testRepo, "original subject", "a", "a1")
.to("refs/for/master")
.getChange();
// Bootstrap: No NoteDb access as we expect it to use the index.
AcceptanceTestRequestScope.Context ctx = disableDb();
try {
changeRefCache.resetBootstrappedProjects();
assertUploadPackRefs(
"HEAD",
"refs/heads/master",
RefNames.changeMetaRef(change1.getId()),
change1.currentPatchSet().getId().toRefName());
} finally {
enableDb(ctx);
}
try (AutoCloseable ignored = disableChangeIndex()) {
// user can see public change
requestScopeOperations.setApiUser(user.getId());
assertUploadPackRefs(
"HEAD",
"refs/heads/master",
RefNames.changeMetaRef(change1.getId()),
change1.currentPatchSet().getId().toRefName());
}
// Delta reload: No index access as we expect it to use NoteDb.
requestScopeOperations.setApiUser(admin.getId());
gApi.changes().id(change1.getId().id).setPrivate(true);
try (AutoCloseable ignored = disableChangeIndex()) {
// user can't see private change from admin
requestScopeOperations.setApiUser(user.getId());
assertUploadPackRefs("HEAD", "refs/heads/master");
}
// admin adds the user as reviewer
requestScopeOperations.setApiUser(admin.getId());
gApi.changes().id(change1.getId().id).addReviewer(user.email);
try (AutoCloseable ignored = disableChangeIndex()) {
// Use can see private change
requestScopeOperations.setApiUser(user.getId());
assertUploadPackRefs(
"HEAD",
"refs/heads/master",
RefNames.changeMetaRef(change1.getId()),
change1.currentPatchSet().getId().toRefName());
}
}
private void assertUploadPackRefs(String... expectedRefs) throws Exception {
try (Repository repo = repoManager.openRepository(project)) {
assertRefs(repo, permissionBackend.user(user(user)).project(project), expectedRefs);
}
}
private void assertRefs(
Repository repo, PermissionBackend.ForProject forProject, String... expectedRefs)
throws Exception {
Map<String, Ref> all = getAllRefs(repo);
assertThat(forProject.filter(all, repo, RefFilterOptions.defaults()).keySet())
.containsExactlyElementsIn(expectedRefs);
}
private static Map<String, Ref> getAllRefs(Repository repo) throws IOException {
return repo.getRefDatabase()
.getRefs()
.stream()
.collect(toMap(Ref::getName, Function.identity()));
}
}