| // Copyright (C) 2016 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.api.change; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.Truth.assertWithMessage; |
| import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel; |
| import static com.google.gerrit.extensions.client.ChangeKind.MERGE_FIRST_PARENT_UPDATE; |
| import static com.google.gerrit.extensions.client.ChangeKind.NO_CHANGE; |
| import static com.google.gerrit.extensions.client.ChangeKind.NO_CODE_CHANGE; |
| import static com.google.gerrit.extensions.client.ChangeKind.REWORK; |
| import static com.google.gerrit.extensions.client.ChangeKind.TRIVIAL_REBASE; |
| import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_COMMIT; |
| import static com.google.gerrit.extensions.client.ListChangesOption.CURRENT_REVISION; |
| import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS; |
| import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; |
| import static com.google.gerrit.server.project.testing.TestLabels.labelBuilder; |
| import static com.google.gerrit.server.project.testing.TestLabels.value; |
| |
| import com.google.common.cache.Cache; |
| import com.google.common.collect.ImmutableList; |
| import com.google.gerrit.acceptance.AbstractDaemonTest; |
| import com.google.gerrit.acceptance.NoHttpd; |
| import com.google.gerrit.acceptance.PushOneCommit; |
| import com.google.gerrit.acceptance.TestAccount; |
| import com.google.gerrit.acceptance.testsuite.change.ChangeKindCreator; |
| import com.google.gerrit.acceptance.testsuite.change.ChangeOperations; |
| import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; |
| import com.google.gerrit.acceptance.testsuite.request.RequestScopeOperations; |
| import com.google.gerrit.entities.Change; |
| import com.google.gerrit.entities.LabelId; |
| import com.google.gerrit.entities.LabelType; |
| import com.google.gerrit.entities.PatchSet; |
| import com.google.gerrit.entities.RefNames; |
| import com.google.gerrit.extensions.api.changes.ReviewInput; |
| import com.google.gerrit.extensions.client.ChangeKind; |
| import com.google.gerrit.extensions.common.ApprovalInfo; |
| import com.google.gerrit.extensions.common.ChangeInfo; |
| import com.google.gerrit.server.change.ChangeKindCacheImpl; |
| import com.google.gerrit.server.project.testing.TestLabels; |
| import com.google.inject.Inject; |
| import com.google.inject.name.Named; |
| import java.util.EnumSet; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.junit.Before; |
| import org.junit.Test; |
| |
| @NoHttpd |
| public class StickyApprovalsIT extends AbstractDaemonTest { |
| @Inject private ProjectOperations projectOperations; |
| @Inject private RequestScopeOperations requestScopeOperations; |
| @Inject private ChangeOperations changeOperations; |
| @Inject private ChangeKindCreator changeKindCreator; |
| |
| @Inject |
| @Named("change_kind") |
| private Cache<ChangeKindCacheImpl.Key, ChangeKind> changeKindCache; |
| |
| @Before |
| public void setup() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| // Overwrite "Code-Review" label that is inherited from All-Projects. |
| // This way changes to the "Code Review" label don't affect other tests. |
| LabelType.Builder codeReview = |
| labelBuilder( |
| LabelId.CODE_REVIEW, |
| value(2, "Looks good to me, approved"), |
| value(1, "Looks good to me, but someone else must approve"), |
| value(0, "No score"), |
| value(-1, "I would prefer that you didn't submit this"), |
| value(-2, "Do not submit")); |
| codeReview.setCopyAllScoresIfNoChange(false); |
| u.getConfig().upsertLabelType(codeReview.build()); |
| |
| LabelType.Builder verified = |
| labelBuilder( |
| LabelId.VERIFIED, value(1, "Passes"), value(0, "No score"), value(-1, "Failed")); |
| verified.setCopyAllScoresIfNoChange(false); |
| u.getConfig().upsertLabelType(verified.build()); |
| |
| u.save(); |
| } |
| projectOperations |
| .project(project) |
| .forUpdate() |
| .add( |
| allowLabel(TestLabels.codeReview().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-2, 2)) |
| .add( |
| allowLabel(TestLabels.verified().getName()) |
| .ref(RefNames.REFS_HEADS + "*") |
| .group(REGISTERED_USERS) |
| .range(-1, 1)) |
| .update(); |
| } |
| |
| @Test |
| public void notSticky() throws Exception { |
| assertNotSticky( |
| EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)); |
| } |
| |
| @Test |
| public void stickyOnAnyScore() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyAnyScore(true)); |
| u.save(); |
| } |
| |
| for (ChangeKind changeKind : |
| EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) { |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| |
| String changeId = changeKindCreator.createChange(changeKind, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, 1, -1); |
| |
| changeKindCreator.updateChange(changeId, changeKind, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, changeKind); |
| assertVotes(c, user, 1, 0, changeKind); |
| } |
| } |
| |
| @Test |
| public void stickyWhenCopyConditionIsTrue() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyCondition("is:ANY")); |
| u.save(); |
| } |
| |
| for (ChangeKind changeKind : |
| EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) { |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| |
| String changeId = changeKindCreator.createChange(changeKind, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, 1, -1); |
| |
| changeKindCreator.updateChange(changeId, changeKind, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, changeKind); |
| assertVotes(c, user, 1, 0, changeKind); |
| } |
| } |
| |
| @Test |
| public void stickyEvenWhenUserCantSeeUploaderInGroup() throws Exception { |
| // user can't see admin group |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| String administratorsUUID = gApi.groups().query("name:Administrators").get().get(0).id; |
| u.getConfig() |
| .updateLabelType( |
| LabelId.CODE_REVIEW, b -> b.setCopyCondition("approverin:" + administratorsUUID)); |
| u.save(); |
| } |
| |
| String changeId = createChange().getChangeId(); |
| approve(changeId); |
| amendChange(changeId); |
| vote(user, changeId, 1, -1); // Invalidate cache |
| requestScopeOperations.setApiUser(user.id()); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0); |
| assertVotes(c, user, 1, -1); |
| } |
| |
| @Test |
| public void stickyOnMinScore() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyMinScore(true)); |
| u.save(); |
| } |
| |
| for (ChangeKind changeKind : |
| EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) { |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| |
| String changeId = changeKindCreator.createChange(changeKind, testRepo, admin); |
| vote(admin, changeId, -1, 1); |
| vote(user, changeId, -2, -1); |
| |
| changeKindCreator.updateChange(changeId, changeKind, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 0, 0, changeKind); |
| assertVotes(c, user, -2, 0, changeKind); |
| } |
| } |
| |
| @Test |
| public void stickyWhenEitherBooleanConfigsOrCopyConditionAreTrue() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig() |
| .updateLabelType( |
| LabelId.CODE_REVIEW, b -> b.setCopyCondition("is:MAX").setCopyMinScore(true)); |
| u.save(); |
| } |
| |
| for (ChangeKind changeKind : |
| EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) { |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| |
| String changeId = changeKindCreator.createChange(changeKind, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, -2, -1); |
| |
| changeKindCreator.updateChange(changeId, changeKind, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, changeKind); |
| assertVotes(c, user, -2, 0, changeKind); |
| } |
| } |
| |
| @Test |
| public void stickyOnMaxScore() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyMaxScore(true)); |
| u.save(); |
| } |
| |
| for (ChangeKind changeKind : |
| EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) { |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| |
| String changeId = changeKindCreator.createChange(changeKind, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, 1, -1); |
| |
| changeKindCreator.updateChange(changeId, changeKind, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, changeKind); |
| assertVotes(c, user, 0, 0, changeKind); |
| } |
| } |
| |
| @Test |
| public void stickyOnCopyValues() throws Exception { |
| TestAccount user2 = accountCreator.user2(); |
| |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig() |
| .updateLabelType( |
| LabelId.CODE_REVIEW, b -> b.setCopyValues(ImmutableList.of((short) -1, (short) 1))); |
| u.save(); |
| } |
| |
| for (ChangeKind changeKind : |
| EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) { |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| |
| String changeId = changeKindCreator.createChange(changeKind, testRepo, admin); |
| vote(admin, changeId, -1, 1); |
| vote(user, changeId, -2, -1); |
| vote(user2, changeId, 1, -1); |
| |
| changeKindCreator.updateChange(changeId, changeKind, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, -1, 0, changeKind); |
| assertVotes(c, user, 0, 0, changeKind); |
| assertVotes(c, user2, 1, 0, changeKind); |
| } |
| } |
| |
| @Test |
| public void stickyOnTrivialRebase() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig() |
| .updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyAllScoresOnTrivialRebase(true)); |
| u.save(); |
| } |
| |
| String changeId = changeKindCreator.createChange(TRIVIAL_REBASE, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, -2, -1); |
| |
| changeKindCreator.updateChange(changeId, NO_CHANGE, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, NO_CHANGE); |
| assertVotes(c, user, -2, 0, NO_CHANGE); |
| |
| changeKindCreator.updateChange(changeId, TRIVIAL_REBASE, testRepo, admin, project); |
| c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, TRIVIAL_REBASE); |
| assertVotes(c, user, -2, 0, TRIVIAL_REBASE); |
| |
| assertNotSticky(EnumSet.of(REWORK, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE)); |
| |
| // check that votes are sticky when trivial rebase is done by cherry-pick |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| changeId = createChange().getChangeId(); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, -2, -1); |
| |
| String cherryPickChangeId = |
| changeKindCreator.cherryPick(changeId, TRIVIAL_REBASE, testRepo, admin, project); |
| c = detailedChange(cherryPickChangeId); |
| assertVotes(c, admin, 2, 0); |
| assertVotes(c, user, -2, 0); |
| |
| // check that votes are not sticky when rework is done by cherry-pick |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| changeId = createChange().getChangeId(); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, -2, -1); |
| |
| cherryPickChangeId = changeKindCreator.cherryPick(changeId, REWORK, testRepo, admin, project); |
| c = detailedChange(cherryPickChangeId); |
| assertVotes(c, admin, 0, 0); |
| assertVotes(c, user, 0, 0); |
| } |
| |
| @Test |
| public void stickyOnNoCodeChange() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(LabelId.VERIFIED, b -> b.setCopyAllScoresIfNoCodeChange(true)); |
| u.save(); |
| } |
| |
| String changeId = changeKindCreator.createChange(NO_CODE_CHANGE, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, -2, -1); |
| |
| changeKindCreator.updateChange(changeId, NO_CHANGE, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 0, 1, NO_CHANGE); |
| assertVotes(c, user, 0, -1, NO_CHANGE); |
| |
| changeKindCreator.updateChange(changeId, NO_CODE_CHANGE, testRepo, admin, project); |
| c = detailedChange(changeId); |
| assertVotes(c, admin, 0, 1, NO_CODE_CHANGE); |
| assertVotes(c, user, 0, -1, NO_CODE_CHANGE); |
| |
| assertNotSticky(EnumSet.of(REWORK, TRIVIAL_REBASE, MERGE_FIRST_PARENT_UPDATE)); |
| } |
| |
| @Test |
| public void stickyOnMergeFirstParentUpdate() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig() |
| .updateLabelType( |
| LabelId.CODE_REVIEW, b -> b.setCopyAllScoresOnMergeFirstParentUpdate(true)); |
| u.save(); |
| } |
| |
| String changeId = changeKindCreator.createChange(MERGE_FIRST_PARENT_UPDATE, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, -2, -1); |
| |
| changeKindCreator.updateChange(changeId, NO_CHANGE, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, NO_CHANGE); |
| assertVotes(c, user, -2, 0, NO_CHANGE); |
| |
| changeKindCreator.updateChange(changeId, MERGE_FIRST_PARENT_UPDATE, testRepo, admin, project); |
| c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, MERGE_FIRST_PARENT_UPDATE); |
| assertVotes(c, user, -2, 0, MERGE_FIRST_PARENT_UPDATE); |
| |
| assertNotSticky(EnumSet.of(REWORK, NO_CODE_CHANGE, TRIVIAL_REBASE)); |
| } |
| |
| @Test |
| public void notStickyWithCopyOnNoChangeWhenSecondParentIsUpdated() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyAllScoresIfNoChange(true)); |
| u.save(); |
| } |
| |
| String changeId = changeKindCreator.createChangeForMergeCommit(testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, -2, -1); |
| |
| changeKindCreator.updateSecondParent(changeId, testRepo, admin); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 0, 0, null); |
| assertVotes(c, user, 0, 0, null); |
| } |
| |
| @Test |
| public void notStickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsAdded() |
| throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig() |
| .updateLabelType( |
| LabelId.CODE_REVIEW, b -> b.setCopyAllScoresIfListOfFilesDidNotChange(true)); |
| u.save(); |
| } |
| Change.Id changeId = |
| changeOperations.newChange().project(project).file("file").content("content").create(); |
| vote(admin, changeId.toString(), 2, 1); |
| vote(user, changeId.toString(), -2, -1); |
| |
| changeOperations |
| .change(changeId) |
| .newPatchset() |
| .file("new file") |
| .content("new content") |
| .create(); |
| ChangeInfo c = detailedChange(changeId.toString()); |
| |
| // no votes are copied since the list of files changed. |
| assertVotes(c, admin, 0, 0); |
| assertVotes(c, user, 0, 0); |
| } |
| |
| @Test |
| public void notStickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsDeleted() |
| throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig() |
| .updateLabelType( |
| LabelId.CODE_REVIEW, b -> b.setCopyAllScoresIfListOfFilesDidNotChange(true)); |
| u.save(); |
| } |
| Change.Id changeId = |
| changeOperations.newChange().project(project).file("file").content("content").create(); |
| vote(admin, changeId.toString(), 2, 1); |
| vote(user, changeId.toString(), -2, -1); |
| |
| changeOperations.change(changeId).newPatchset().file("file").delete().create(); |
| ChangeInfo c = detailedChange(changeId.toString()); |
| |
| // no votes are copied since the list of files changed. |
| assertVotes(c, admin, 0, 0); |
| assertVotes(c, user, 0, 0); |
| } |
| |
| @Test |
| public void stickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsModified() |
| throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig() |
| .updateLabelType( |
| LabelId.CODE_REVIEW, b -> b.setCopyAllScoresIfListOfFilesDidNotChange(true)); |
| u.save(); |
| } |
| Change.Id changeId = |
| changeOperations.newChange().project(project).file("file").content("content").create(); |
| vote(admin, changeId.toString(), 2, 1); |
| vote(user, changeId.toString(), -2, -1); |
| |
| changeOperations.change(changeId).newPatchset().file("file").content("new content").create(); |
| ChangeInfo c = detailedChange(changeId.toString()); |
| |
| // only code review votes are copied since copyAllScoresIfListOfFilesDidNotChange is |
| // configured for that label, and list of files didn't change. |
| assertVotes(c, admin, 2, 0); |
| assertVotes(c, user, -2, 0); |
| } |
| |
| @Test |
| public void notStickyWithCopyAllScoresIfListOfFilesDidNotChangeWhenFileIsRenamed() |
| throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig() |
| .updateLabelType( |
| LabelId.CODE_REVIEW, b -> b.setCopyAllScoresIfListOfFilesDidNotChange(true)); |
| u.save(); |
| } |
| Change.Id changeId = |
| changeOperations.newChange().project(project).file("file").content("content").create(); |
| vote(admin, changeId.toString(), 2, 1); |
| vote(user, changeId.toString(), -2, -1); |
| |
| changeOperations.change(changeId).newPatchset().file("file").renameTo("new_file").create(); |
| ChangeInfo c = detailedChange(changeId.toString()); |
| |
| // no votes are copied since the list of files changed (rename). |
| assertVotes(c, admin, 0, 0); |
| assertVotes(c, user, 0, 0); |
| } |
| |
| @Test |
| public void removedVotesNotSticky() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig() |
| .updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyAllScoresOnTrivialRebase(true)); |
| u.getConfig().updateLabelType(LabelId.VERIFIED, b -> b.setCopyAllScoresIfNoCodeChange(true)); |
| u.save(); |
| } |
| |
| for (ChangeKind changeKind : |
| EnumSet.of(REWORK, TRIVIAL_REBASE, NO_CODE_CHANGE, MERGE_FIRST_PARENT_UPDATE, NO_CHANGE)) { |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| |
| String changeId = changeKindCreator.createChange(changeKind, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| vote(user, changeId, -2, -1); |
| |
| // Remove votes by re-voting with 0 |
| vote(admin, changeId, 0, 0); |
| vote(user, changeId, 0, 0); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 0, 0, null); |
| assertVotes(c, user, 0, 0, null); |
| |
| changeKindCreator.updateChange(changeId, changeKind, testRepo, admin, project); |
| c = detailedChange(changeId); |
| assertVotes(c, admin, 0, 0, changeKind); |
| assertVotes(c, user, 0, 0, changeKind); |
| } |
| } |
| |
| @Test |
| public void stickyAcrossMultiplePatchSets() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyMaxScore(true)); |
| u.getConfig().updateLabelType(LabelId.VERIFIED, b -> b.setCopyAllScoresIfNoCodeChange(true)); |
| u.save(); |
| } |
| |
| String changeId = changeKindCreator.createChange(REWORK, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| |
| for (int i = 0; i < 5; i++) { |
| changeKindCreator.updateChange(changeId, NO_CODE_CHANGE, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 1, NO_CODE_CHANGE); |
| } |
| |
| changeKindCreator.updateChange(changeId, REWORK, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, REWORK); |
| } |
| |
| @Test |
| public void stickyAcrossMultiplePatchSetsDoNotRegressPerformance() throws Exception { |
| // The purpose of this test is to make sure that we compute change kind only against the parent |
| // patch set. Change kind is a heavy operation. In prior version of Gerrit, we computed the |
| // change kind against all prior patch sets. This is a regression that made Gerrit do expensive |
| // work in O(num-patch-sets). This test ensures that we aren't regressing. |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyMaxScore(true)); |
| u.getConfig().updateLabelType(LabelId.VERIFIED, b -> b.setCopyAllScoresIfNoCodeChange(true)); |
| u.save(); |
| } |
| |
| String changeId = changeKindCreator.createChange(REWORK, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| changeKindCreator.updateChange(changeId, NO_CODE_CHANGE, testRepo, admin, project); |
| changeKindCreator.updateChange(changeId, NO_CODE_CHANGE, testRepo, admin, project); |
| changeKindCreator.updateChange(changeId, NO_CODE_CHANGE, testRepo, admin, project); |
| |
| Map<Integer, ObjectId> revisions = new HashMap<>(); |
| gApi.changes() |
| .id(changeId) |
| .get() |
| .revisions |
| .forEach( |
| (revId, revisionInfo) -> |
| revisions.put(revisionInfo._number, ObjectId.fromString(revId))); |
| assertThat(revisions.size()).isEqualTo(4); |
| assertChangeKindCacheContains(revisions.get(3), revisions.get(4)); |
| assertChangeKindCacheContains(revisions.get(2), revisions.get(3)); |
| assertChangeKindCacheContains(revisions.get(1), revisions.get(2)); |
| |
| assertChangeKindCacheDoesNotContain(revisions.get(1), revisions.get(4)); |
| assertChangeKindCacheDoesNotContain(revisions.get(2), revisions.get(4)); |
| assertChangeKindCacheDoesNotContain(revisions.get(1), revisions.get(3)); |
| } |
| |
| @Test |
| public void copyMinMaxAcrossMultiplePatchSets() throws Exception { |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyMaxScore(true)); |
| u.getConfig().updateLabelType(LabelId.CODE_REVIEW, b -> b.setCopyMinScore(true)); |
| u.save(); |
| } |
| |
| // Vote max score on PS1 |
| String changeId = changeKindCreator.createChange(REWORK, testRepo, admin); |
| vote(admin, changeId, 2, 1); |
| |
| // Have someone else vote min score on PS2 |
| changeKindCreator.updateChange(changeId, REWORK, testRepo, admin, project); |
| vote(user, changeId, -2, 0); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, REWORK); |
| assertVotes(c, user, -2, 0, REWORK); |
| |
| // No vote changes on PS3 |
| changeKindCreator.updateChange(changeId, REWORK, testRepo, admin, project); |
| c = detailedChange(changeId); |
| assertVotes(c, admin, 2, 0, REWORK); |
| assertVotes(c, user, -2, 0, REWORK); |
| |
| // Both users revote on PS4 |
| changeKindCreator.updateChange(changeId, REWORK, testRepo, admin, project); |
| vote(admin, changeId, 1, 1); |
| vote(user, changeId, 1, 1); |
| c = detailedChange(changeId); |
| assertVotes(c, admin, 1, 1, REWORK); |
| assertVotes(c, user, 1, 1, REWORK); |
| |
| // New approvals shouldn't carry through to PS5 |
| changeKindCreator.updateChange(changeId, REWORK, testRepo, admin, project); |
| c = detailedChange(changeId); |
| assertVotes(c, admin, 0, 0, REWORK); |
| assertVotes(c, user, 0, 0, REWORK); |
| } |
| |
| @Test |
| public void deleteStickyVote() throws Exception { |
| String label = LabelId.CODE_REVIEW; |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(label, b -> b.setCopyMaxScore(true)); |
| u.save(); |
| } |
| |
| // Vote max score on PS1 |
| String changeId = changeKindCreator.createChange(REWORK, testRepo, admin); |
| vote(admin, changeId, label, 2); |
| assertVotes(detailedChange(changeId), admin, label, 2, null); |
| changeKindCreator.updateChange(changeId, REWORK, testRepo, admin, project); |
| assertVotes(detailedChange(changeId), admin, label, 2, REWORK); |
| |
| // Delete vote that was copied via sticky approval |
| deleteVote(admin, changeId, label); |
| assertVotes(detailedChange(changeId), admin, label, 0, REWORK); |
| } |
| |
| @Test |
| public void canVoteMultipleTimesOnNewPatchsets() throws Exception { |
| // Code-Review will be sticky. |
| String label = LabelId.CODE_REVIEW; |
| try (ProjectConfigUpdate u = updateProject(project)) { |
| u.getConfig().updateLabelType(label, b -> b.setCopyAnyScore(true)); |
| u.save(); |
| } |
| |
| PushOneCommit.Result r = createChange(); |
| |
| // Add a new vote. |
| ReviewInput input = new ReviewInput().label(LabelId.CODE_REVIEW, 2); |
| gApi.changes().id(r.getChangeId()).current().review(input); |
| |
| // Make a new patchset, keeping the Code-Review +2 vote. |
| amendChange(r.getChangeId()); |
| |
| // Post without changing the vote. |
| input = new ReviewInput().label(LabelId.CODE_REVIEW, 2); |
| gApi.changes().id(r.getChangeId()).current().review(input); |
| |
| // There is a vote both on patchset 1 and on patchset 2, although both votes are Code-Review +2. |
| assertThat(r.getChange().approvals().get(PatchSet.id(r.getChange().getId(), 1))).hasSize(1); |
| assertThat(r.getChange().approvals().get(PatchSet.id(r.getChange().getId(), 2))).hasSize(1); |
| } |
| |
| private void assertChangeKindCacheContains(ObjectId prior, ObjectId next) { |
| ChangeKind kind = |
| changeKindCache.getIfPresent(ChangeKindCacheImpl.Key.create(prior, next, "recursive")); |
| assertThat(kind).isNotNull(); |
| } |
| |
| private void assertChangeKindCacheDoesNotContain(ObjectId prior, ObjectId next) { |
| ChangeKind kind = |
| changeKindCache.getIfPresent(ChangeKindCacheImpl.Key.create(prior, next, "recursive")); |
| assertThat(kind).isNull(); |
| } |
| |
| private ChangeInfo detailedChange(String changeId) throws Exception { |
| return gApi.changes().id(changeId).get(DETAILED_LABELS, CURRENT_REVISION, CURRENT_COMMIT); |
| } |
| |
| private void assertNotSticky(Set<ChangeKind> changeKinds) throws Exception { |
| for (ChangeKind changeKind : changeKinds) { |
| testRepo.reset(projectOperations.project(project).getHead("master")); |
| |
| String changeId = changeKindCreator.createChange(changeKind, testRepo, admin); |
| vote(admin, changeId, +2, 1); |
| vote(user, changeId, -2, -1); |
| |
| changeKindCreator.updateChange(changeId, changeKind, testRepo, admin, project); |
| ChangeInfo c = detailedChange(changeId); |
| assertVotes(c, admin, 0, 0, changeKind); |
| assertVotes(c, user, 0, 0, changeKind); |
| } |
| } |
| |
| private void vote(TestAccount user, String changeId, String label, int vote) throws Exception { |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes().id(changeId).current().review(new ReviewInput().label(label, vote)); |
| } |
| |
| private void vote(TestAccount user, String changeId, int codeReviewVote, int verifiedVote) |
| throws Exception { |
| requestScopeOperations.setApiUser(user.id()); |
| ReviewInput in = |
| new ReviewInput() |
| .label(LabelId.CODE_REVIEW, codeReviewVote) |
| .label(LabelId.VERIFIED, verifiedVote); |
| gApi.changes().id(changeId).current().review(in); |
| } |
| |
| private void deleteVote(TestAccount user, String changeId, String label) throws Exception { |
| requestScopeOperations.setApiUser(user.id()); |
| gApi.changes().id(changeId).reviewer(user.id().toString()).deleteVote(label); |
| } |
| |
| private void assertVotes(ChangeInfo c, TestAccount user, int codeReviewVote, int verifiedVote) { |
| assertVotes(c, user, codeReviewVote, verifiedVote, null); |
| } |
| |
| private void assertVotes( |
| ChangeInfo c, TestAccount user, int codeReviewVote, int verifiedVote, ChangeKind changeKind) { |
| assertVotes(c, user, LabelId.CODE_REVIEW, codeReviewVote, changeKind); |
| assertVotes(c, user, LabelId.VERIFIED, verifiedVote, changeKind); |
| } |
| |
| private void assertVotes( |
| ChangeInfo c, TestAccount user, String label, int expectedVote, ChangeKind changeKind) { |
| Integer vote = 0; |
| if (c.labels.get(label) != null && c.labels.get(label).all != null) { |
| for (ApprovalInfo approval : c.labels.get(label).all) { |
| if (approval._accountId == user.id().get()) { |
| vote = approval.value; |
| break; |
| } |
| } |
| } |
| |
| String name = "label = " + label; |
| if (changeKind != null) { |
| name += "; changeKind = " + changeKind.name(); |
| } |
| assertWithMessage(name).that(vote).isEqualTo(expectedVote); |
| } |
| } |