blob: a00c2cde026f3e5a3cc74a17a6c502aa9b6f2761 [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.plugins.checks.acceptance.api;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth8.assertThat;
import static com.google.gerrit.truth.CacheStatsSubject.assertThat;
import static com.google.gerrit.truth.CacheStatsSubject.cloneStats;
import com.google.common.cache.CacheStats;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.PluginDefinedInfo;
import com.google.gerrit.plugins.checks.CheckKey;
import com.google.gerrit.plugins.checks.CheckerUuid;
import com.google.gerrit.plugins.checks.CombinedCheckStateCache;
import com.google.gerrit.plugins.checks.acceptance.AbstractCheckersTest;
import com.google.gerrit.plugins.checks.api.ChangeCheckInfo;
import com.google.gerrit.plugins.checks.api.CheckInput;
import com.google.gerrit.plugins.checks.api.CheckState;
import com.google.gerrit.plugins.checks.api.CombinedCheckState;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import java.util.List;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
public class ChangeCheckInfoIT extends AbstractCheckersTest {
private CombinedCheckStateCache cache;
private Change.Id changeId;
private PatchSet.Id psId;
@Before
public void setUp() throws Exception {
cache = plugin.getSysInjector().getInstance(CombinedCheckStateCache.class);
psId = createChange().getPatchSetId();
changeId = psId.changeId();
}
@Test
public void infoRequiresOptionViaGet() throws Exception {
assertThat(
getChangeCheckInfo(gApi.changes().id(changeId.get()).get(ImmutableListMultimap.of())))
.isEmpty();
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
}
@Test
public void infoRequiresOptionViaQuery() throws Exception {
List<ChangeInfo> changeInfos = gApi.changes().query("change:" + changeId).get();
assertThat(changeInfos).hasSize(1);
assertThat(getChangeCheckInfo(changeInfos.get(0))).isEmpty();
assertThat(queryChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
}
@Test
public void noCheckers() throws Exception {
assertThat(checkerOperations.checkersOf(project)).isEmpty();
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
assertThat(queryChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
}
@Test
public void backfillRelevantCheckerViaGet() throws Exception {
checkerOperations.newChecker().repository(project).query("topic:foo").create();
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
gApi.changes().id(changeId.get()).topic("foo");
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.IN_PROGRESS));
}
@Test
public void combinedCheckStateViaGet() throws Exception {
CheckerUuid optionalCheckerUuid = checkerOperations.newChecker().repository(project).create();
CheckerUuid requiredCheckerUuid =
checkerOperations.newChecker().repository(project).required().create();
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.IN_PROGRESS));
checkOperations
.newCheck(CheckKey.create(project, psId, optionalCheckerUuid))
.state(CheckState.SUCCESSFUL)
.upsert();
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.IN_PROGRESS));
checkOperations
.newCheck(CheckKey.create(project, psId, requiredCheckerUuid))
.state(CheckState.FAILED)
.upsert();
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.FAILED));
}
@Test
public void combinedCheckStateViaQuery() throws Exception {
CacheStats start = cloneStats(cache.getStats());
long startReloadsFalse = cache.getReloadCount(false);
long startReloadsTrue = cache.getReloadCount(true);
assertThat(queryChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
// Cache hasn't yet populated during update.
// TODO(xchangcheng): still initialize the cache early without doing in the submit rule.
assertThat(cache.getStats()).since(start).hasHitCount(0);
assertThat(cache.getStats()).since(start).hasMissCount(1);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(0);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(0);
assertThat(queryChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
assertThat(cache.getStats()).since(start).hasHitCount(1);
assertThat(cache.getStats()).since(start).hasMissCount(1);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(0);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(0);
}
@Test
public void loadingCombinedCheckStateViaGetUpdatesCache() throws Exception {
cache.putForTest(project, psId, CombinedCheckState.FAILED);
CacheStats start = cloneStats(cache.getStats());
long startReloadsFalse = cache.getReloadCount(false);
long startReloadsTrue = cache.getReloadCount(true);
assertThat(queryChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.FAILED));
assertThat(cache.getStats()).since(start).hasHitCount(1);
assertThat(cache.getStats()).since(start).hasMissCount(0);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(0);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(0);
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
// Incurs reloads in attribute factory paths.
assertThat(cache.getStats()).since(start).hasHitCount(2);
assertThat(cache.getStats()).since(start).hasMissCount(0);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(0);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(1);
assertThat(queryChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
assertThat(cache.getStats()).since(start).hasHitCount(3);
assertThat(cache.getStats()).since(start).hasMissCount(0);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(0);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(1);
}
@Test
public void updatingCheckStateUpdatesCache() throws Exception {
CheckerUuid checkerUuid = checkerOperations.newChecker().repository(project).create();
cache.putForTest(project, psId, CombinedCheckState.IN_PROGRESS);
CacheStats start = cloneStats(cache.getStats());
long startReloadsFalse = cache.getReloadCount(false);
long startReloadsTrue = cache.getReloadCount(true);
assertThat(queryChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.IN_PROGRESS));
assertThat(cache.getStats()).since(start).hasHitCount(1);
assertThat(cache.getStats()).since(start).hasMissCount(0);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(0);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(0);
// Set non-required checker to FAILED, updating combined check state to WARNING.
CheckInput checkInput = new CheckInput();
checkInput.state = CheckState.FAILED;
checksApiFactory.revision(psId).id(checkerUuid).update(checkInput);
// Incurs reload after updating check state.
assertThat(cache.getStats()).since(start).hasHitCount(2);
assertThat(cache.getStats()).since(start).hasMissCount(0);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(0);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(1);
assertThat(queryChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.WARNING));
assertThat(cache.getStats()).since(start).hasHitCount(3);
assertThat(cache.getStats()).since(start).hasMissCount(0);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(0);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(1);
}
@Test
public void repeatedlyLoadingCombinedCheckStateViaGetResultsInOnlyNoOpReloads() throws Exception {
CacheStats start = cloneStats(cache.getStats());
long startReloadsFalse = cache.getReloadCount(false);
long startReloadsTrue = cache.getReloadCount(true);
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
// Incurs reloads in attribute factory paths.
assertThat(cache.getStats()).since(start).hasHitCount(0);
assertThat(cache.getStats()).since(start).hasMissCount(1);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(0);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(1);
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
assertThat(cache.getStats()).since(start).hasHitCount(1);
assertThat(cache.getStats()).since(start).hasMissCount(1);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(1);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(1);
assertThat(getChangeCheckInfo(changeId))
.hasValue(new ChangeCheckInfo("checks", CombinedCheckState.NOT_RELEVANT));
assertThat(cache.getStats()).since(start).hasHitCount(2);
assertThat(cache.getStats()).since(start).hasMissCount(1);
assertThat(cache.getReloadCount(false) - startReloadsFalse).isEqualTo(2);
assertThat(cache.getReloadCount(true) - startReloadsTrue).isEqualTo(1);
}
private Optional<ChangeCheckInfo> getChangeCheckInfo(Change.Id id) throws Exception {
return getChangeCheckInfo(
gApi.changes().id(id.get()).get(ImmutableListMultimap.of("checks--combined", "true")));
}
private Optional<ChangeCheckInfo> queryChangeCheckInfo(Change.Id id) throws Exception {
List<ChangeInfo> changeInfos =
gApi.changes().query("change:" + id).withPluginOption("checks--combined", "true").get();
assertThat(changeInfos).hasSize(1);
return getChangeCheckInfo(changeInfos.get(0));
}
private static Optional<ChangeCheckInfo> getChangeCheckInfo(ChangeInfo changeInfo) {
if (changeInfo.plugins == null) {
return Optional.empty();
}
ImmutableList<PluginDefinedInfo> infos =
changeInfo.plugins.stream().filter(i -> i.name.equals("checks")).collect(toImmutableList());
if (infos.isEmpty()) {
return Optional.empty();
}
assertThat(infos).hasSize(1);
assertThat(infos.get(0)).isInstanceOf(ChangeCheckInfo.class);
return Optional.of((ChangeCheckInfo) infos.get(0));
}
}