blob: 9d37497b10d4d6569c21755ea7cb666d713a91cd [file] [log] [blame]
// Copyright (C) 2022 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.pgm;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.api.projects.SubmitRequirementApi;
import com.google.gerrit.extensions.common.LabelDefinitionInfo;
import com.google.gerrit.extensions.common.LabelDefinitionInput;
import com.google.gerrit.extensions.common.SubmitRequirementInfo;
import com.google.gerrit.extensions.common.SubmitRequirementInput;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.server.schema.MigrateLabelFunctionsToSubmitRequirement;
import com.google.gerrit.server.schema.MigrateLabelFunctionsToSubmitRequirement.Status;
import com.google.gerrit.server.schema.UpdateUI;
import com.google.inject.Inject;
import java.util.Map;
import java.util.Set;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Test;
/** Test for {@link com.google.gerrit.server.schema.MigrateLabelFunctionsToSubmitRequirement}. */
@Sandboxed
public class MigrateLabelFunctionsToSubmitRequirementIT extends AbstractDaemonTest {
@Inject private ProjectOperations projectOperations;
@Test
public void migrateBlockingLabel_maxWithBlock() throws Exception {
createLabel("Foo", "MaxWithBlock", /* ignoreSelfApproval= */ false);
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ null,
/* submittabilityExpression= */ "label:Foo=MAX AND -label:Foo=MIN",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateBlockingLabel_maxNoBlock() throws Exception {
createLabel("Foo", "MaxNoBlock", /* ignoreSelfApproval= */ false);
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ null,
/* submittabilityExpression= */ "label:Foo=MAX",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateBlockingLabel_anyWithBlock() throws Exception {
createLabel("Foo", "AnyWithBlock", /* ignoreSelfApproval= */ false);
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ null,
/* submittabilityExpression= */ "-label:Foo=MIN",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateBlockingLabel_maxWithBlock_withIgnoreSelfApproval() throws Exception {
createLabel("Foo", "MaxWithBlock", /* ignoreSelfApproval= */ true);
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ null,
/* submittabilityExpression= */ "label:Foo=MAX,user=non_uploader AND -label:Foo=MIN",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateBlockingLabel_maxNoBlock_withIgnoreSelfApproval() throws Exception {
createLabel("Foo", "MaxNoBlock", /* ignoreSelfApproval= */ true);
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ null,
/* submittabilityExpression= */ "label:Foo=MAX,user=non_uploader",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateNonBlockingLabel_noBlock() throws Exception {
// NoBlock labels are left as is, i.e. we don't create a "submit requirement" for them. Those
// labels will then be treated as trigger votes in the change page.
createLabel("Foo", "NoBlock", /* ignoreSelfApproval= */ false);
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.NO_CHANGE);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(0);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
// No SR was created for the label. Label will be treated as a trigger vote.
assertNonExistentSr("Foo");
// Label function has not changed.
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateNonBlockingLabel_noOp() throws Exception {
// NoOp labels are left as is, i.e. we don't create a "submit requirement" for them. Those
// labels will then be treated as trigger votes in the change page.
createLabel("Foo", "NoOp", /* ignoreSelfApproval= */ false);
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(0);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
// No SR was created for the label. Label will be treated as a trigger vote.
assertNonExistentSr("Foo");
// The NoOp function is converted to NoBlock. Both are same.
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateNoBlockLabel_withSingleZeroValue() throws Exception {
// Labels that have a single "zero" value are skipped in the project. The migrator creates
// non-applicable SR for these labels.
createLabel("Foo", "NoBlock", /* ignoreSelfApproval= */ false, ImmutableMap.of("0", "No vote"));
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
// a non-applicable SR was created for the skipped label.
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ "is:false",
/* submittabilityExpression= */ "is:true",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateMaxWithBlockLabel_withSingleZeroValue() throws Exception {
// Labels that have a single "zero" value are skipped in the project. The migrator creates
// non-applicable SRs for these labels.
createLabel(
"Foo", "MaxWithBlock", /* ignoreSelfApproval= */ false, ImmutableMap.of("0", "No vote"));
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
// a non-applicable SR was created for the skipped label.
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ "is:false",
/* submittabilityExpression= */ "is:true",
/* canOverride= */ true);
// The MaxWithBlock function is converted to NoBlock. This has no effect anyway because the
// label was originally skipped.
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void cannotCreateLabelsWithNoValues() {
// This test just asserts the server's behaviour for visibility; admins cannot create a label
// without any defined values.
Exception thrown =
assertThrows(
BadRequestException.class,
() ->
createLabel("Foo", "NoBlock", /* ignoreSelfApproval= */ false, ImmutableMap.of()));
assertThat(thrown).hasMessageThat().isEqualTo("values are required");
}
@Test
public void migrateNonBlockingLabel_patchSetLock_doesNothing() throws Exception {
createLabel("Foo", "PatchSetLock", /* ignoreSelfApproval= */ false);
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.NO_CHANGE);
// No submit requirement created for the patchset lock label function
assertThat(updateUI.newlyCreatedSrs).isEqualTo(0);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertNonExistentSr(/* srName = */ "Foo");
assertLabelFunction("Foo", "PatchSetLock");
}
@Test
public void migrationIsCommittedWithServerIdent() throws Exception {
RevCommit oldMetaCommit = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
createLabel("Foo", "MaxWithBlock", /* ignoreSelfApproval= */ false);
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ null,
/* submittabilityExpression= */ "label:Foo=MAX AND -label:Foo=MIN",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
RevCommit newMetaCommit = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
assertThat(newMetaCommit).isNotEqualTo(oldMetaCommit);
assertThat(newMetaCommit.getCommitterIdent().getEmailAddress())
.isEqualTo(serverIdent.get().getEmailAddress());
}
@Test
public void migrateBlockingLabel_withBranchAttribute() throws Exception {
createLabelWithBranch(
"Foo",
"MaxWithBlock",
/* ignoreSelfApproval= */ false,
ImmutableList.of("refs/heads/master"));
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ "branch:\\\"refs/heads/master\\\"",
/* submittabilityExpression= */ "label:Foo=MAX AND -label:Foo=MIN",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateBlockingLabel_withMultipleBranchAttributes() throws Exception {
createLabelWithBranch(
"Foo",
"MaxWithBlock",
/* ignoreSelfApproval= */ false,
ImmutableList.of("refs/heads/master", "refs/heads/develop"));
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ "branch:\\\"refs/heads/master\\\" "
+ "OR branch:\\\"refs/heads/develop\\\"",
/* submittabilityExpression= */ "label:Foo=MAX AND -label:Foo=MIN",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateBlockingLabel_withRegexBranchAttribute() throws Exception {
createLabelWithBranch(
"Foo",
"MaxWithBlock",
/* ignoreSelfApproval= */ false,
ImmutableList.of("^refs/heads/main-.*"));
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ "branch:\\\"^refs/heads/main-.*\\\"",
/* submittabilityExpression= */ "label:Foo=MAX AND -label:Foo=MIN",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrateBlockingLabel_withRegexAndNonRegexBranchAttributes() throws Exception {
createLabelWithBranch(
"Foo",
"MaxWithBlock",
/* ignoreSelfApproval= */ false,
ImmutableList.of("refs/heads/master", "^refs/heads/main-.*"));
assertNonExistentSr(/* srName = */ "Foo");
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ "branch:\\\"refs/heads/master\\\" "
+ "OR branch:\\\"^refs/heads/main-.*\\\"",
/* submittabilityExpression= */ "label:Foo=MAX AND -label:Foo=MIN",
/* canOverride= */ true);
assertLabelFunction("Foo", "NoBlock");
}
@Test
public void migrationIsIdempotent() throws Exception {
String oldRefsConfigId;
try (Repository repo = repoManager.openRepository(project)) {
oldRefsConfigId = repo.exactRef(RefNames.REFS_CONFIG).getObjectId().toString();
}
createLabel("Foo", "MaxWithBlock", /* ignoreSelfApproval= */ false);
assertNonExistentSr(/* srName = */ "Foo");
// Running the migration causes REFS_CONFIG to change.
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(1);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
try (Repository repo = repoManager.openRepository(project)) {
assertThat(oldRefsConfigId)
.isNotEqualTo(repo.exactRef(RefNames.REFS_CONFIG).getObjectId().toString());
oldRefsConfigId = repo.exactRef(RefNames.REFS_CONFIG).getObjectId().toString();
}
// No new SRs will be created. No conflicting submit requirements either since the migration
// detects that a previous run was made and skips the migration.
updateUI = runMigration(/* expectedResult= */ Status.PREVIOUSLY_MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(0);
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
// Running the migration a second time won't update REFS_CONFIG.
try (Repository repo = repoManager.openRepository(project)) {
assertThat(oldRefsConfigId)
.isEqualTo(repo.exactRef(RefNames.REFS_CONFIG).getObjectId().toString());
}
}
@Test
public void migrationDoesNotCreateANewSubmitRequirement_ifSRAlreadyExists_matchingWithMigration()
throws Exception {
createLabel("Foo", "MaxWithBlock", /* ignoreSelfApproval= */ false);
createSubmitRequirement("Foo", "label:Foo=MAX AND -label:Foo=MIN", /* canOverride= */ true);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ null,
/* submittabilityExpression= */ "label:Foo=MAX AND -label:Foo=MIN",
/* canOverride= */ true);
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
// No new submit requirements are created.
assertThat(updateUI.newlyCreatedSrs).isEqualTo(0);
// No conflicting submit requirements from migration vs. what was previously configured.
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(0);
// The existing SR was left as is.
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ null,
/* submittabilityExpression= */ "label:Foo=MAX AND -label:Foo=MIN",
/* canOverride= */ true);
}
@Test
public void
migrationDoesNotCreateANewSubmitRequirement_ifSRAlreadyExists_mismatchingWithMigration()
throws Exception {
createLabel("Foo", "MaxWithBlock", /* ignoreSelfApproval= */ false);
createSubmitRequirement("Foo", "project:" + project, /* canOverride= */ true);
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ null,
/* submittabilityExpression= */ "project:" + project,
/* canOverride= */ true);
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
// One conflicting submit requirement between migration vs. what was previously configured.
assertThat(updateUI.existingSrsMismatchingWithMigration).isEqualTo(1);
// The existing SR was left as is.
assertExistentSr(
/* srName */ "Foo",
/* applicabilityExpression= */ null,
/* submittabilityExpression= */ "project:" + project,
/* canOverride= */ true);
}
@Test
public void migrationResetsBlockingLabel_ifSRAlreadyExists() throws Exception {
createLabel("Foo", "MaxWithBlock", /* ignoreSelfApproval= */ false);
createSubmitRequirement("Foo", "owner:" + admin.email(), /* canOverride= */ true);
TestUpdateUI updateUI = runMigration(/* expectedResult= */ Status.MIGRATED);
assertThat(updateUI.newlyCreatedSrs).isEqualTo(0);
// The label function was reset
assertLabelFunction("Foo", "NoBlock");
}
private TestUpdateUI runMigration(Status expectedResult) throws Exception {
TestUpdateUI updateUi = new TestUpdateUI();
MigrateLabelFunctionsToSubmitRequirement executor =
new MigrateLabelFunctionsToSubmitRequirement(repoManager, serverIdent.get());
Status status = executor.executeMigration(project, updateUi);
assertThat(status).isEqualTo(expectedResult);
projectCache.evictAndReindex(project);
return updateUi;
}
private void createLabel(String labelName, String function, boolean ignoreSelfApproval)
throws Exception {
createLabel(
labelName,
function,
ignoreSelfApproval,
ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad"));
}
private void createLabel(
String labelName, String function, boolean ignoreSelfApproval, Map<String, String> values)
throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.name = labelName;
input.function = function;
input.ignoreSelfApproval = ignoreSelfApproval;
input.values = values;
gApi.projects().name(project.get()).label(labelName).create(input);
}
private void createLabelWithBranch(
String labelName,
String function,
boolean ignoreSelfApproval,
ImmutableList<String> refPatterns)
throws Exception {
LabelDefinitionInput input = new LabelDefinitionInput();
input.name = labelName;
input.function = function;
input.ignoreSelfApproval = ignoreSelfApproval;
input.values = ImmutableMap.of("+1", "Looks Good", " 0", "Don't Know", "-1", "Looks Bad");
input.branches = refPatterns;
gApi.projects().name(project.get()).label(labelName).create(input);
}
@CanIgnoreReturnValue
private SubmitRequirementApi createSubmitRequirement(
String name, String submitExpression, boolean canOverride) throws Exception {
SubmitRequirementInput input = new SubmitRequirementInput();
input.name = name;
input.submittabilityExpression = submitExpression;
input.allowOverrideInChildProjects = canOverride;
return gApi.projects().name(project.get()).submitRequirement(name).create(input);
}
private void assertLabelFunction(String labelName, String function) throws Exception {
LabelDefinitionInfo info = gApi.projects().name(project.get()).label(labelName).get();
assertThat(info.function).isEqualTo(function);
}
private void assertNonExistentSr(String srName) {
ResourceNotFoundException foo =
assertThrows(
ResourceNotFoundException.class,
() -> gApi.projects().name(project.get()).submitRequirement("Foo").get());
assertThat(foo.getMessage()).isEqualTo("Submit requirement '" + srName + "' does not exist");
}
private void assertExistentSr(
String srName,
String applicabilityExpression,
String submittabilityExpression,
boolean canOverride)
throws Exception {
SubmitRequirementInfo sr = gApi.projects().name(project.get()).submitRequirement(srName).get();
assertThat(sr.applicabilityExpression).isEqualTo(applicabilityExpression);
assertThat(sr.submittabilityExpression).isEqualTo(submittabilityExpression);
assertThat(sr.allowOverrideInChildProjects).isEqualTo(canOverride);
}
private static class TestUpdateUI implements UpdateUI {
int existingSrsMismatchingWithMigration = 0;
int newlyCreatedSrs = 0;
@Override
public void message(String message) {
if (message.startsWith("Warning")) {
existingSrsMismatchingWithMigration += 1;
} else if (message.startsWith("Project")) {
newlyCreatedSrs += 1;
}
}
@Override
public boolean yesno(boolean defaultValue, String message) {
return false;
}
@Override
public void waitForUser() {}
@Override
public String readString(String defaultValue, Set<String> allowedValues, String message) {
return null;
}
@Override
public boolean isBatch() {
return false;
}
}
}