| // 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.entities.Change; |
| import com.google.gerrit.entities.PatchSet; |
| 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 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(4); |
| 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(5); |
| 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)); |
| } |
| } |