| // 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(MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE) |
| .add(MigrateLabelConfigToCopyCondition.KEY_COPY_MIN_SCORE) |
| .add(MigrateLabelConfigToCopyCondition.KEY_COPY_MAX_SCORE) |
| .add(MigrateLabelConfigToCopyCondition.KEY_COPY_VALUE) |
| .add(MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CHANGE) |
| .add(MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE) |
| .add(MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE) |
| .add(MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE) |
| .add( |
| MigrateLabelConfigToCopyCondition.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, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CHANGE, |
| /* value= */ false); |
| cfg.setBoolean( |
| ProjectConfig.LABEL, |
| LabelId.VERIFIED, |
| MigrateLabelConfigToCopyCondition.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, MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CHANGE); |
| assertDeprecatedFieldsUnset( |
| LabelId.VERIFIED, MigrateLabelConfigToCopyCondition.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( |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE, |
| copyCondition -> assertThat(copyCondition).isEqualTo("is:ANY")); |
| } |
| |
| @Test |
| public void migrateCopyMinScore() throws Exception { |
| testFlagMigration( |
| MigrateLabelConfigToCopyCondition.KEY_COPY_MIN_SCORE, |
| copyCondition -> assertThat(copyCondition).isEqualTo("is:MIN")); |
| } |
| |
| @Test |
| public void migrateCopyMaxScore() throws Exception { |
| testFlagMigration( |
| MigrateLabelConfigToCopyCondition.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( |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CHANGE, |
| copyCondition -> |
| assertThat(copyCondition).isEqualTo("changekind:" + ChangeKind.NO_CHANGE.toString())); |
| } |
| |
| @Test |
| public void migrateCopyAllScoresIfNoCodeCange() throws Exception { |
| testFlagMigration( |
| MigrateLabelConfigToCopyCondition.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( |
| MigrateLabelConfigToCopyCondition.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( |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE, |
| copyCondition -> |
| assertThat(copyCondition) |
| .isEqualTo("changekind:" + ChangeKind.TRIVIAL_REBASE.toString())); |
| } |
| |
| @Test |
| public void migrateCopyAllScoresIfListOfFilesDidNotChange() throws Exception { |
| testFlagMigration( |
| MigrateLabelConfigToCopyCondition.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, MigrateLabelConfigToCopyCondition.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, MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE); |
| setFlagOnCodeReviewLabel(MigrateLabelConfigToCopyCondition.KEY_COPY_MIN_SCORE); |
| setFlagOnCodeReviewLabel(MigrateLabelConfigToCopyCondition.KEY_COPY_MAX_SCORE); |
| setCopyValuesOnCodeReviewLabel(-2, -1, 1, 2); |
| setFlagOnCodeReviewLabel(MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CHANGE); |
| setFlagOnCodeReviewLabel( |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE); |
| setFlagOnCodeReviewLabel( |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE); |
| setFlagOnCodeReviewLabel( |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE); |
| setFlagOnCodeReviewLabel( |
| MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE); |
| setFlagOnCodeReviewLabel(MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE); |
| setFlagOnCodeReviewLabel(MigrateLabelConfigToCopyCondition.KEY_COPY_MIN_SCORE); |
| setFlagOnCodeReviewLabel(MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE); |
| setFlagOnCodeReviewLabel( |
| MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE); |
| setFlagOnCodeReviewLabel( |
| MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE); |
| setFlagOnCodeReviewLabel( |
| MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE); |
| setFlagOnCodeReviewLabel( |
| MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE); |
| setFlagOnCodeReviewLabel( |
| MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.KEY_COPY_MIN_SCORE); |
| setFlagOnCodeReviewLabel(MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CHANGE); |
| |
| setFlagOnVerifiedLabel(MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE); |
| setFlagOnVerifiedLabel(MigrateLabelConfigToCopyCondition.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, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ANY_SCORE, |
| /* value= */ false); |
| cfg.setBoolean( |
| ProjectConfig.LABEL, |
| LabelId.CODE_REVIEW, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_MIN_SCORE, |
| /* value= */ false); |
| cfg.setBoolean( |
| ProjectConfig.LABEL, |
| LabelId.CODE_REVIEW, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_MAX_SCORE, |
| /* value= */ false); |
| cfg.setBoolean( |
| ProjectConfig.LABEL, |
| LabelId.CODE_REVIEW, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CHANGE, |
| /* value= */ false); |
| cfg.setBoolean( |
| ProjectConfig.LABEL, |
| LabelId.CODE_REVIEW, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CODE_CHANGE, |
| /* value= */ false); |
| cfg.setBoolean( |
| ProjectConfig.LABEL, |
| LabelId.CODE_REVIEW, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_ON_MERGE_FIRST_PARENT_UPDATE, |
| /* value= */ false); |
| cfg.setBoolean( |
| ProjectConfig.LABEL, |
| LabelId.CODE_REVIEW, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_ON_TRIVIAL_REBASE, |
| /* value= */ false); |
| cfg.setBoolean( |
| ProjectConfig.LABEL, |
| LabelId.CODE_REVIEW, |
| MigrateLabelConfigToCopyCondition.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, |
| MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.KEY_COPY_MIN_SCORE); |
| setFlagOnVerifiedLabel(MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.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(MigrateLabelConfigToCopyCondition.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, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CHANGE, |
| copyAllScoresIfNoChangeValue); |
| cfg.setBoolean( |
| ProjectConfig.LABEL, |
| LabelId.VERIFIED, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CHANGE, |
| copyAllScoresIfNoChangeValue); |
| } else { |
| cfg.unset( |
| ProjectConfig.LABEL, |
| LabelId.CODE_REVIEW, |
| MigrateLabelConfigToCopyCondition.KEY_COPY_ALL_SCORES_IF_NO_CHANGE); |
| cfg.unset( |
| ProjectConfig.LABEL, |
| LabelId.VERIFIED, |
| MigrateLabelConfigToCopyCondition.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, |
| MigrateLabelConfigToCopyCondition.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); |
| } |
| } |