blob: 6de334bb85e7c5bd2bbf3e83f5d5225a25ca0863 [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.collect.ImmutableList.toImmutableList;
import static com.google.common.truth.Truth.assertThat;
import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allowLabel;
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 static com.google.gerrit.testing.GerritJUnit.assertThrows;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.gerrit.acceptance.AbstractDaemonTest;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.entities.LabelId;
import com.google.gerrit.entities.LabelType;
import com.google.gerrit.entities.Project;
import com.google.gerrit.entities.RefNames;
import com.google.gerrit.extensions.client.ChangeKind;
import com.google.gerrit.server.project.ProjectConfig;
import com.google.gerrit.server.project.testing.TestLabels;
import com.google.gerrit.server.schema.MigrateLabelConfigToCopyCondition;
import com.google.inject.Inject;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.junit.Before;
import org.junit.Test;
public class MigrateLabelConfigToCopyConditionIT extends AbstractDaemonTest {
private static final ImmutableSet<String> DEPRECATED_FIELDS =
ImmutableSet.<String>builder()
.add(ProjectConfig.KEY_COPY_ANY_SCORE)
.add(ProjectConfig.KEY_COPY_MIN_SCORE)
.add(ProjectConfig.KEY_COPY_MAX_SCORE)
.add(ProjectConfig.KEY_COPY_VALUE)
.add(ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE)
.add(ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE)
.add(ProjectConfig.KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE)
.add(ProjectConfig.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE)
.add(ProjectConfig.KEY_COPY_ALL_SCORES_IF_LIST_OF_FILES_DID_NOT_CHANGE)
.build();
@Inject private ProjectOperations projectOperations;
@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 this is not submitted as is"),
value(-2, "This shall not be submitted"));
u.getConfig().upsertLabelType(codeReview.build());
LabelType.Builder verified =
labelBuilder(
LabelId.VERIFIED, value(1, "Passes"), value(0, "No score"), value(-1, "Failed"));
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();
// overwrite the default value for copyAllScoresIfNoChange which is true for the migration
updateProjectConfig(
cfg -> {
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
/* value= */ false);
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.VERIFIED,
ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
/* value= */ false);
});
}
@Test
public void nothingToMigrate_noLabels() throws Exception {
Project.NameKey projectWithoutLabelDefinitions = projectOperations.newProject().create();
RevCommit refsMetaConfigHead =
projectOperations.project(projectWithoutLabelDefinitions).getHead(RefNames.REFS_CONFIG);
runMigration(projectWithoutLabelDefinitions);
// verify that refs/meta/config was not touched
assertThat(
projectOperations.project(projectWithoutLabelDefinitions).getHead(RefNames.REFS_CONFIG))
.isEqualTo(refsMetaConfigHead);
}
@Test
public void noFieldsToMigrate() throws Exception {
assertThat(projectOperations.project(project).getConfig().getSubsections(ProjectConfig.LABEL))
.containsExactly(LabelId.CODE_REVIEW, LabelId.VERIFIED);
// copyAllScoresIfNoChange=false is set in the test setup to override the default value
assertDeprecatedFieldsUnset(
LabelId.CODE_REVIEW, ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE);
assertDeprecatedFieldsUnset(LabelId.VERIFIED, ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE);
runMigration();
// verify that copyAllScoresIfNoChange=false (that was set in the test setup to override to
// default value) was removed
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertDeprecatedFieldsUnset(LabelId.VERIFIED);
}
@Test
public void noFieldsToMigrate_copyConditionExists() throws Exception {
String copyCondition = "is:MIN";
setCopyConditionOnCodeReviewLabel(copyCondition);
runMigration();
// verify that copyAllScoresIfNoChange=false (that was set in the test setup to override to
// default value) was removed
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
// verify that the copy condition was not changed
assertThat(getCopyConditionOfCodeReviewLabel()).isEqualTo(copyCondition);
}
@Test
public void noFieldsToMigrate_complexCopyConditionExists() throws Exception {
String copyCondition = "is:MIN has:unchanged-files";
setCopyConditionOnCodeReviewLabel(copyCondition);
runMigration();
// verify that copyAllScoresIfNoChange=false (that was set in the test setup to override to
// default value) was removed
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
// verify that the copy condition was not changed (e.g. no parentheses have been added around
// the
// copy condition)
assertThat(getCopyConditionOfCodeReviewLabel()).isEqualTo(copyCondition);
}
@Test
public void noFieldsToMigrate_nonOrderedCopyConditionExists() throws Exception {
String copyCondition = "is:MIN OR has:unchanged-files";
setCopyConditionOnCodeReviewLabel(copyCondition);
runMigration();
// verify that copyAllScoresIfNoChange=false (that was set in the test setup to override to
// default value) was removed
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
// verify that the copy condition was not changed (e.g. the order of OR conditions has not be
// changed and no parentheses have been added around the copy condition)
assertThat(getCopyConditionOfCodeReviewLabel()).isEqualTo(copyCondition);
}
@Test
public void migrateCopyAnyScore() throws Exception {
testFlagMigration(
ProjectConfig.KEY_COPY_ANY_SCORE,
copyCondition -> assertThat(copyCondition).isEqualTo("is:ANY"));
}
@Test
public void migrateCopyMinScore() throws Exception {
testFlagMigration(
ProjectConfig.KEY_COPY_MIN_SCORE,
copyCondition -> assertThat(copyCondition).isEqualTo("is:MIN"));
}
@Test
public void migrateCopyMaxScore() throws Exception {
testFlagMigration(
ProjectConfig.KEY_COPY_MAX_SCORE,
copyCondition -> assertThat(copyCondition).isEqualTo("is:MAX"));
}
@Test
public void migrateCopyValues_singleValue() throws Exception {
testCopyValueMigration(
ImmutableList.of(1), copyCondition -> assertThat(copyCondition).isEqualTo("is:1"));
}
@Test
public void migrateCopyValues_negativeValue() throws Exception {
testCopyValueMigration(
ImmutableList.of(-1), copyCondition -> assertThat(copyCondition).isEqualTo("is:\"-1\""));
}
@Test
public void migrateCopyValues_multipleValues() throws Exception {
testCopyValueMigration(
ImmutableList.of(-1, 1),
copyCondition -> assertThat(copyCondition).isEqualTo("is:\"-1\" OR is:1"));
}
@Test
public void migrateCopyValues_manyValues() throws Exception {
testCopyValueMigration(
ImmutableList.of(-2, -1, 1, 2),
copyCondition ->
assertThat(copyCondition).isEqualTo("is:\"-1\" OR is:\"-2\" OR is:1 OR is:2"));
}
@Test
public void migrateCopyAllScoresIfNoCange() throws Exception {
testFlagMigration(
ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
copyCondition ->
assertThat(copyCondition).isEqualTo("changekind:" + ChangeKind.NO_CHANGE.toString()));
}
@Test
public void migrateCopyAllScoresIfNoCodeCange() throws Exception {
testFlagMigration(
ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE,
copyCondition ->
assertThat(copyCondition)
.isEqualTo("changekind:" + ChangeKind.NO_CODE_CHANGE.toString()));
}
@Test
public void migrateCopyAllScoresOnMergeFirstParentUpdate() throws Exception {
testFlagMigration(
ProjectConfig.KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE,
copyCondition ->
assertThat(copyCondition)
.isEqualTo("changekind:" + ChangeKind.MERGE_FIRST_PARENT_UPDATE.toString()));
}
@Test
public void migrateCopyAllScoresOnTrivialRebase() throws Exception {
testFlagMigration(
ProjectConfig.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE,
copyCondition ->
assertThat(copyCondition)
.isEqualTo("changekind:" + ChangeKind.TRIVIAL_REBASE.toString()));
}
@Test
public void migrateCopyAllScoresIfListOfFilesDidNotChange() throws Exception {
testFlagMigration(
ProjectConfig.KEY_COPY_ALL_SCORES_IF_LIST_OF_FILES_DID_NOT_CHANGE,
copyCondition -> assertThat(copyCondition).isEqualTo("has:unchanged-files"));
}
@Test
public void migrateDefaultValues() throws Exception {
// remove copyAllScoresIfNoChange=false that was set in the test setup to override to default
// value
unset(LabelId.CODE_REVIEW, ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE);
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
// expect that the copy condition was set to "changekind:NO_CHANGE" since
// copyAllScoresIfNoChange was not set and has true as default value
assertThat(getCopyConditionOfCodeReviewLabel()).isEqualTo("changekind:NO_CHANGE");
}
@Test
public void migrateDefaultValues_copyConditionExists() throws Exception {
setCopyConditionOnCodeReviewLabel("is:MIN");
// remove copyAllScoresIfNoChange=false that was set in the test setup to override to default
// value
unset(LabelId.CODE_REVIEW, ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE);
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
// expect that the copy condition includes "changekind:NO_CHANGE" since
// copyAllScoresIfNoChange was not set and has true as default value
assertThat(getCopyConditionOfCodeReviewLabel()).isEqualTo("changekind:NO_CHANGE OR is:MIN");
}
@Test
public void migrateAll() throws Exception {
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ANY_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_MIN_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_MAX_SCORE);
setCopyValuesOnCodeReviewLabel(-2, -1, 1, 2);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_IF_LIST_OF_FILES_DID_NOT_CHANGE);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertThat(getCopyConditionOfCodeReviewLabel())
.isEqualTo(
"changekind:MERGE_FIRST_PARENT_UPDATE"
+ " OR changekind:NO_CHANGE"
+ " OR changekind:NO_CODE_CHANGE"
+ " OR changekind:TRIVIAL_REBASE"
+ " OR has:unchanged-files"
+ " OR is:\"-1\""
+ " OR is:\"-2\""
+ " OR is:1"
+ " OR is:2"
+ " OR is:ANY"
+ " OR is:MAX"
+ " OR is:MIN");
}
@Test
public void migrationMergesFlagsIntoExistingCopyCondition_mutualllyExclusive() throws Exception {
setCopyConditionOnCodeReviewLabel("is:ANY");
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_MIN_SCORE);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertThat(getCopyConditionOfCodeReviewLabel()).isEqualTo("is:ANY OR is:MIN");
}
@Test
public void migrationMergesFlagsIntoExistingCopyCondition_noDuplicatePredicate()
throws Exception {
setCopyConditionOnCodeReviewLabel("is:ANY");
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ANY_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_MIN_SCORE);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertThat(getCopyConditionOfCodeReviewLabel()).isEqualTo("is:ANY OR is:MIN");
}
@Test
public void migrationMergesFlagsIntoExistingCopyCondition_noDuplicatePredicates()
throws Exception {
setCopyConditionOnCodeReviewLabel("is:ANY OR is:MIN");
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ANY_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_MIN_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_MAX_SCORE);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertThat(getCopyConditionOfCodeReviewLabel()).isEqualTo("is:ANY OR is:MAX OR is:MIN");
}
@Test
public void migrationMergesFlagsIntoExistingCopyCondition_complexCopyCondition_v1()
throws Exception {
setCopyConditionOnCodeReviewLabel("is:ANY changekind:TRIVIAL_REBASE");
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ANY_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertThat(getCopyConditionOfCodeReviewLabel())
.isEqualTo("(is:ANY changekind:TRIVIAL_REBASE) OR changekind:TRIVIAL_REBASE OR is:ANY");
}
@Test
public void migrationMergesFlagsIntoExistingCopyCondition_complexCopyCondition_v2()
throws Exception {
setCopyConditionOnCodeReviewLabel(
"is:ANY AND (changekind:TRIVIAL_REBASE OR changekind:NO_CODE_CHANGE)");
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ANY_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertThat(getCopyConditionOfCodeReviewLabel())
.isEqualTo(
"(is:ANY AND (changekind:TRIVIAL_REBASE OR changekind:NO_CODE_CHANGE)) OR changekind:TRIVIAL_REBASE OR is:ANY");
}
@Test
public void migrationMergesFlagsIntoExistingCopyCondition_noUnnecessaryParenthesesAdded()
throws Exception {
setCopyConditionOnCodeReviewLabel("(is:ANY changekind:TRIVIAL_REBASE)");
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ANY_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertThat(getCopyConditionOfCodeReviewLabel())
.isEqualTo("(is:ANY changekind:TRIVIAL_REBASE) OR changekind:TRIVIAL_REBASE OR is:ANY");
}
@Test
public void migrationMergesFlagsIntoExistingCopyCondition_existingCopyConditionIsNotParseable()
throws Exception {
setCopyConditionOnCodeReviewLabel("NOT-PARSEABLE");
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ANY_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertThat(getCopyConditionOfCodeReviewLabel())
.isEqualTo("NOT-PARSEABLE OR changekind:TRIVIAL_REBASE OR is:ANY");
}
@Test
public void
migrationMergesFlagsIntoExistingCopyCondition_existingComplexCopyConditionIsNotParseable()
throws Exception {
setCopyConditionOnCodeReviewLabel("NOT PARSEABLE");
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ANY_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertThat(getCopyConditionOfCodeReviewLabel())
.isEqualTo("(NOT PARSEABLE) OR changekind:TRIVIAL_REBASE OR is:ANY");
}
@Test
public void migrateMultipleLabels() throws Exception {
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_MIN_SCORE);
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE);
setFlagOnVerifiedLabel(ProjectConfig.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE);
setFlagOnVerifiedLabel(ProjectConfig.KEY_COPY_MAX_SCORE);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertThat(getCopyConditionOfCodeReviewLabel()).isEqualTo("changekind:NO_CHANGE OR is:MIN");
assertDeprecatedFieldsUnset(LabelId.VERIFIED);
assertThat(getCopyConditionOfVerifiedLabel()).isEqualTo("changekind:TRIVIAL_REBASE OR is:MAX");
}
@Test
public void deprecatedFlagsThatAreSetToFalseAreUnset() throws Exception {
// set all flags to false
updateProjectConfig(
cfg -> {
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_ANY_SCORE,
/* value= */ false);
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_MIN_SCORE,
/* value= */ false);
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_MAX_SCORE,
/* value= */ false);
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
/* value= */ false);
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE,
/* value= */ false);
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE,
/* value= */ false);
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE,
/* value= */ false);
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_ALL_SCORES_IF_LIST_OF_FILES_DID_NOT_CHANGE,
/* value= */ false);
});
}
@Test
public void emptyCopyValueParameterIsUnset() throws Exception {
updateProjectConfig(
cfg ->
cfg.setString(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_VALUE,
/* value= */ ""));
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
}
@Test
public void migrationCreatesASingleCommit() throws Exception {
// Set flags on 2 labels (the migrations for both labels are expected to be done in a single
// commit)
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_MIN_SCORE);
setFlagOnVerifiedLabel(ProjectConfig.KEY_COPY_MAX_SCORE);
RevCommit refsMetaConfigHead = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
runMigration();
// verify that the new commit in refs/meta/config is a successor of the old head
assertThat(projectOperations.project(project).getHead(RefNames.REFS_CONFIG).getParent(0))
.isEqualTo(refsMetaConfigHead);
}
@Test
public void commitMessageIsDistinct() throws Exception {
// Set a flag so that the migration has to do something.
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_MIN_SCORE);
runMigration();
// Verify that the commit message is distinct (e.g. this is important in case there is an issue
// with the migration, having a distinct commit message allows to identify the commit that was
// done for the migration and would allow to revert it)
RevCommit refsMetaConfigHead = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
assertThat(refsMetaConfigHead.getShortMessage())
.isEqualTo(MigrateLabelConfigToCopyCondition.COMMIT_MESSAGE);
}
@Test
public void gerritIsAuthorAndCommitterOfTheMigrationCommit() throws Exception {
// Set a flag so that the migration has to do something.
setFlagOnCodeReviewLabel(ProjectConfig.KEY_COPY_MIN_SCORE);
runMigration();
RevCommit refsMetaConfigHead = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
assertThat(refsMetaConfigHead.getAuthorIdent().getEmailAddress())
.isEqualTo(serverIdent.get().getEmailAddress());
assertThat(refsMetaConfigHead.getAuthorIdent().getName())
.isEqualTo(serverIdent.get().getName());
assertThat(refsMetaConfigHead.getCommitterIdent())
.isEqualTo(refsMetaConfigHead.getAuthorIdent());
}
@Test
public void migrationFailsIfProjectConfigIsNotParseable() throws Exception {
projectOperations.project(project).forInvalidation().makeProjectConfigInvalid().invalidate();
RevCommit refsMetaConfigHead = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
ConfigInvalidException exception =
assertThrows(ConfigInvalidException.class, () -> runMigration());
assertThat(exception)
.hasMessageThat()
.contains(String.format("Invalid config file project.config in project %s", project));
// verify that refs/meta/config was not touched
assertThat(projectOperations.project(project).getHead(RefNames.REFS_CONFIG))
.isEqualTo(refsMetaConfigHead);
}
@Test
public void migrateWhenProjectConfigIsMissing() throws Exception {
deleteProjectConfig();
RevCommit refsMetaConfigHead = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
runMigration();
// verify that refs/meta/config was not touched (e.g. project.config was not created)
assertThat(projectOperations.project(project).getHead(RefNames.REFS_CONFIG))
.isEqualTo(refsMetaConfigHead);
}
@Test
public void migrateWhenRefsMetaConfigIsMissing() throws Exception {
try (TestRepository<Repository> testRepo =
new TestRepository<>(repoManager.openRepository(project))) {
testRepo.delete(RefNames.REFS_CONFIG);
}
runMigration();
// verify that refs/meta/config was not created
try (TestRepository<Repository> testRepo =
new TestRepository<>(repoManager.openRepository(project))) {
assertThat(testRepo.getRepository().exactRef(RefNames.REFS_CONFIG)).isNull();
}
}
@Test
public void migrationIsIdempotent_copyAllScoresIfNoChangeIsUnset() throws Exception {
testMigrationIsIdempotent(/* copyAllScoresIfNoChangeValue= */ null);
}
@Test
public void migrationIsIdempotent_copyAllScoresIfNoChangeIsFalse() throws Exception {
testMigrationIsIdempotent(/* copyAllScoresIfNoChangeValue= */ false);
}
@Test
public void migrationIsIdempotent_copyAllScoresIfNoChangeIsTrue() throws Exception {
testMigrationIsIdempotent(/* copyAllScoresIfNoChangeValue= */ true);
}
private void testMigrationIsIdempotent(@Nullable Boolean copyAllScoresIfNoChangeValue)
throws Exception {
updateProjectConfig(
cfg -> {
if (copyAllScoresIfNoChangeValue != null) {
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
copyAllScoresIfNoChangeValue);
cfg.setBoolean(
ProjectConfig.LABEL,
LabelId.VERIFIED,
ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE,
copyAllScoresIfNoChangeValue);
} else {
cfg.unset(
ProjectConfig.LABEL,
LabelId.CODE_REVIEW,
ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE);
cfg.unset(
ProjectConfig.LABEL,
LabelId.VERIFIED,
ProjectConfig.KEY_COPY_ALL_SCORES_IF_NO_CHANGE);
}
});
// Run the migration to update the label configuration.
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
assertDeprecatedFieldsUnset(LabelId.VERIFIED);
// default value for copyAllScoresIfNoChangeValue is true
if (copyAllScoresIfNoChangeValue == null || copyAllScoresIfNoChangeValue) {
assertThat(getCopyConditionOfCodeReviewLabel()).isEqualTo("changekind:NO_CHANGE");
} else {
assertThat(getCopyConditionOfCodeReviewLabel()).isNull();
}
// Running the migration again doesn't change anything.
RevCommit head = projectOperations.project(project).getHead(RefNames.REFS_CONFIG);
runMigration();
assertThat(projectOperations.project(project).getHead(RefNames.REFS_CONFIG)).isEqualTo(head);
}
private void testFlagMigration(String key, Consumer<String> copyConditionValidator)
throws Exception {
setFlagOnCodeReviewLabel(key);
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
copyConditionValidator.accept(getCopyConditionOfCodeReviewLabel());
}
private void testCopyValueMigration(List<Integer> values, Consumer<String> copyConditionValidator)
throws Exception {
setCopyValuesOnCodeReviewLabel(values.toArray(new Integer[0]));
runMigration();
assertDeprecatedFieldsUnset(LabelId.CODE_REVIEW);
copyConditionValidator.accept(getCopyConditionOfCodeReviewLabel());
}
private void runMigration() throws Exception {
runMigration(project);
}
private void runMigration(Project.NameKey project) throws Exception {
new MigrateLabelConfigToCopyCondition(repoManager, serverIdent.get()).execute(project);
}
private void setFlagOnCodeReviewLabel(String key) throws Exception {
setFlag(LabelId.CODE_REVIEW, key);
}
private void setFlagOnVerifiedLabel(String key) throws Exception {
setFlag(LabelId.VERIFIED, key);
}
private void setFlag(String labelName, String key) throws Exception {
updateProjectConfig(
cfg -> cfg.setBoolean(ProjectConfig.LABEL, labelName, key, /* value= */ true));
}
private void unset(String labelName, String key) throws Exception {
updateProjectConfig(cfg -> cfg.unset(ProjectConfig.LABEL, labelName, key));
}
private void setCopyValuesOnCodeReviewLabel(Integer... values) throws Exception {
setCopyValues(LabelId.CODE_REVIEW, values);
}
private void setCopyValues(String labelName, Integer... values) throws Exception {
updateProjectConfig(
cfg ->
cfg.setStringList(
ProjectConfig.LABEL,
labelName,
ProjectConfig.KEY_COPY_VALUE,
Arrays.stream(values).map(Object::toString).collect(toImmutableList())));
}
private void setCopyConditionOnCodeReviewLabel(String copyCondition) throws Exception {
setCopyCondition(LabelId.CODE_REVIEW, copyCondition);
}
private void setCopyCondition(String labelName, String copyCondition) throws Exception {
updateProjectConfig(
cfg ->
cfg.setString(
ProjectConfig.LABEL, labelName, ProjectConfig.KEY_COPY_CONDITION, copyCondition));
}
private void updateProjectConfig(Consumer<Config> configUpdater) throws Exception {
Config projectConfig = projectOperations.project(project).getConfig();
configUpdater.accept(projectConfig);
try (TestRepository<Repository> testRepo =
new TestRepository<>(repoManager.openRepository(project))) {
Ref ref = testRepo.getRepository().exactRef(RefNames.REFS_CONFIG);
RevCommit head = testRepo.getRevWalk().parseCommit(ref.getObjectId());
testRepo.update(
RefNames.REFS_CONFIG,
testRepo
.commit()
.parent(head)
.message("Update label config")
.add(ProjectConfig.PROJECT_CONFIG, projectConfig.toText()));
}
projectCache.evictAndReindex(project);
}
private void deleteProjectConfig() throws Exception {
try (TestRepository<Repository> testRepo =
new TestRepository<>(repoManager.openRepository(project))) {
Ref ref = testRepo.getRepository().exactRef(RefNames.REFS_CONFIG);
RevCommit head = testRepo.getRevWalk().parseCommit(ref.getObjectId());
testRepo.update(
RefNames.REFS_CONFIG,
testRepo
.commit()
.parent(head)
.message("Update label config")
.rm(ProjectConfig.PROJECT_CONFIG));
}
projectCache.evictAndReindex(project);
}
private void assertDeprecatedFieldsUnset(String labelName, String... excludedFields) {
for (String field :
Sets.difference(DEPRECATED_FIELDS, Sets.newHashSet(Arrays.asList(excludedFields)))) {
assertUnset(labelName, field);
}
}
private void assertUnset(String labelName, String key) {
assertThat(
projectOperations.project(project).getConfig().getNames(ProjectConfig.LABEL, labelName))
.doesNotContain(key);
}
private String getCopyConditionOfCodeReviewLabel() {
return getCopyCondition(LabelId.CODE_REVIEW);
}
private String getCopyConditionOfVerifiedLabel() {
return getCopyCondition(LabelId.VERIFIED);
}
private String getCopyCondition(String labelName) {
return projectOperations
.project(project)
.getConfig()
.getString(ProjectConfig.LABEL, labelName, ProjectConfig.KEY_COPY_CONDITION);
}
}