From: Léo Geoffroy Date: Tue, 21 Mar 2023 14:39:42 +0000 (+0100) Subject: SONAR-18679 Move remaining ITs X-Git-Tag: 10.0.0.68432~122 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=1e1840fac3a138fe76741a7675fc346119296bf5;p=sonarqube.git SONAR-18679 Move remaining ITs --- diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplIT.java new file mode 100644 index 00000000000..9c3c0412c49 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplIT.java @@ -0,0 +1,310 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.component; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.Collections; +import java.util.Optional; +import javax.annotation.Nullable; +import org.assertj.core.api.ThrowableAssert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sonar.api.config.internal.ConfigurationBridge; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.analysis.Branch; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; +import org.sonar.db.protobuf.DbProjectBranches; +import org.sonar.server.project.Project; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; +import static org.sonar.core.config.PurgeConstants.BRANCHES_TO_KEEP_WHEN_INACTIVE; +import static org.sonar.db.component.BranchType.BRANCH; +import static org.sonar.db.component.BranchType.PULL_REQUEST; + +@RunWith(DataProviderRunner.class) +public class BranchPersisterImplIT { + private final static Component MAIN = builder(Component.Type.PROJECT, 1, "PROJECT_KEY").setUuid("PROJECT_UUID").setName("p1").build(); + private final static Component BRANCH1 = builder(Component.Type.PROJECT, 2, "BRANCH_KEY").setUuid("BRANCH_UUID").build(); + private final static Component PR1 = builder(Component.Type.PROJECT, 3, "develop").setUuid("PR_UUID").build(); + private static final Project PROJECT = new Project(MAIN.getUuid(), MAIN.getKey(), MAIN.getName(), null, Collections.emptyList()); + + @Rule + public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + + private final MapSettings settings = new MapSettings(); + private final ConfigurationRepository configurationRepository = new TestSettingsRepository(new ConfigurationBridge(settings)); + private final BranchPersister underTest = new BranchPersisterImpl(dbTester.getDbClient(), treeRootHolder, analysisMetadataHolder, configurationRepository); + + @Test + public void persist_fails_with_ISE_if_no_component_for_main_branches() { + analysisMetadataHolder.setBranch(createBranch(BRANCH, true, "master")); + treeRootHolder.setRoot(MAIN); + DbSession dbSession = dbTester.getSession(); + + expectMissingComponentISE(() -> underTest.persist(dbSession)); + } + + @Test + public void persist_fails_with_ISE_if_no_component_for_branches() { + analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "foo")); + treeRootHolder.setRoot(BRANCH1); + DbSession dbSession = dbTester.getSession(); + + expectMissingComponentISE(() -> underTest.persist(dbSession)); + } + + @Test + public void persist_fails_with_ISE_if_no_component_for_pull_request() { + analysisMetadataHolder.setBranch(createBranch(BranchType.PULL_REQUEST, false, "12")); + treeRootHolder.setRoot(BRANCH1); + DbSession dbSession = dbTester.getSession(); + + expectMissingComponentISE(() -> underTest.persist(dbSession)); + } + + @Test + @UseDataProvider("nullOrNotNullString") + public void persist_creates_row_in_PROJECTS_BRANCHES_for_branch(@Nullable String mergeBranchUuid) { + String branchName = "branch"; + + // add project and branch in table PROJECTS + ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(MAIN.getUuid()).setKey(MAIN.getKey()); + ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, + new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH)); + dbTester.components().insertComponents(mainComponent, component); + // set project in metadata + treeRootHolder.setRoot(BRANCH1); + analysisMetadataHolder.setBranch(createBranch(BRANCH, false, branchName, mergeBranchUuid)); + analysisMetadataHolder.setProject(Project.from(mainComponent)); + + underTest.persist(dbTester.getSession()); + + dbTester.getSession().commit(); + + assertThat(dbTester.countRowsOfTable("components")).isEqualTo(2); + Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); + assertThat(branchDto).isPresent(); + assertThat(branchDto.get().getBranchType()).isEqualTo(BRANCH); + assertThat(branchDto.get().getKey()).isEqualTo(branchName); + assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo(mergeBranchUuid); + assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid()); + assertThat(branchDto.get().getPullRequestData()).isNull(); + } + + @Test + public void main_branch_is_excluded_from_branch_purge_by_default() { + analysisMetadataHolder.setBranch(createBranch(BRANCH, true, "master")); + treeRootHolder.setRoot(MAIN); + dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); + dbTester.commit(); + + underTest.persist(dbTester.getSession()); + + Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), MAIN.getUuid()); + assertThat(branchDto).isPresent(); + assertThat(branchDto.get().isExcludeFromPurge()).isTrue(); + } + + @Test + public void non_main_branch_is_excluded_from_branch_purge_if_matches_sonar_dbcleaner_keepFromPurge_property() { + settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "BRANCH.*"); + analysisMetadataHolder.setProject(PROJECT); + analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY")); + treeRootHolder.setRoot(BRANCH1); + ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); + ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, + new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH)); + dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component); + dbTester.commit(); + + underTest.persist(dbTester.getSession()); + + Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); + assertThat(branchDto).isPresent(); + assertThat(branchDto.get().isExcludeFromPurge()).isTrue(); + } + + @Test + public void branch_is_excluded_from_purge_when_it_matches_setting() { + analysisMetadataHolder.setProject(PROJECT); + analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY")); + treeRootHolder.setRoot(BRANCH1); + ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); + ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, + new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH)); + dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component); + settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "BRANCH.*"); + dbTester.commit(); + + underTest.persist(dbTester.getSession()); + + Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); + assertThat(branchDto).isPresent(); + assertThat(branchDto.get().isExcludeFromPurge()).isTrue(); + } + + @Test + public void branch_is_not_excluded_from_purge_when_it_does_not_match_setting() { + analysisMetadataHolder.setProject(PROJECT); + analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY")); + treeRootHolder.setRoot(BRANCH1); + ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); + ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, + new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH)); + dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component); + settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "abc.*"); + + dbTester.commit(); + + underTest.persist(dbTester.getSession()); + + Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); + assertThat(branchDto).isPresent(); + assertThat(branchDto.get().isExcludeFromPurge()).isFalse(); + } + + @Test + public void pull_request_is_never_excluded_from_branch_purge_even_if_its_source_branch_name_matches_sonar_dbcleaner_keepFromPurge_property() { + settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "develop"); + analysisMetadataHolder.setBranch(createPullRequest(PR1.getKey(), MAIN.getUuid())); + analysisMetadataHolder.setPullRequestKey(PR1.getKey()); + treeRootHolder.setRoot(PR1); + ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); + ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, new BranchDto() + .setUuid(PR1.getUuid()) + .setKey(PR1.getKey()) + .setProjectUuid(MAIN.getUuid()) + .setBranchType(PULL_REQUEST) + .setMergeBranchUuid(MAIN.getUuid())); + dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component); + dbTester.commit(); + + underTest.persist(dbTester.getSession()); + + Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), PR1.getUuid()); + assertThat(branchDto).isPresent(); + assertThat(branchDto.get().isExcludeFromPurge()).isFalse(); + } + + @Test + public void non_main_branch_is_included_in_branch_purge_if_branch_name_does_not_match_sonar_dbcleaner_keepFromPurge_property() { + settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "foobar-.*"); + analysisMetadataHolder.setProject(PROJECT); + analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY")); + treeRootHolder.setRoot(BRANCH1); + ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); + ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, + new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH)); + dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component); + dbTester.commit(); + + underTest.persist(dbTester.getSession()); + + Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); + assertThat(branchDto).isPresent(); + assertThat(branchDto.get().isExcludeFromPurge()).isFalse(); + } + + @DataProvider + public static Object[][] nullOrNotNullString() { + return new Object[][] { + {null}, + {randomAlphabetic(12)} + }; + } + + @Test + public void persist_creates_row_in_PROJECTS_BRANCHES_for_pull_request() { + String pullRequestId = "pr-123"; + + // add project and branch in table PROJECTS + ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(MAIN.getUuid()).setKey(MAIN.getKey()); + ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, + new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(PULL_REQUEST)); + dbTester.components().insertComponents(mainComponent, component); + // set project in metadata + treeRootHolder.setRoot(BRANCH1); + analysisMetadataHolder.setBranch(createBranch(PULL_REQUEST, false, pullRequestId, "mergeBanchUuid")); + analysisMetadataHolder.setProject(Project.from(mainComponent)); + analysisMetadataHolder.setPullRequestKey(pullRequestId); + + underTest.persist(dbTester.getSession()); + + dbTester.getSession().commit(); + + assertThat(dbTester.countRowsOfTable("components")).isEqualTo(2); + Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); + assertThat(branchDto).isPresent(); + assertThat(branchDto.get().getBranchType()).isEqualTo(PULL_REQUEST); + assertThat(branchDto.get().getKey()).isEqualTo(pullRequestId); + assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo("mergeBanchUuid"); + assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid()); + assertThat(branchDto.get().getPullRequestData()).isEqualTo(DbProjectBranches.PullRequestData.newBuilder() + .setBranch(pullRequestId) + .setTarget("mergeBanchUuid") + .setTitle(pullRequestId) + .build()); + } + + private static Branch createBranch(BranchType type, boolean isMain, String name) { + return createBranch(type, isMain, name, null); + } + + private static Branch createPullRequest(String key, String mergeBranchUuid) { + Branch branch = createBranch(PULL_REQUEST, false, key, mergeBranchUuid); + when(branch.getPullRequestKey()).thenReturn(key); + return branch; + } + + private static Branch createBranch(BranchType type, boolean isMain, String name, @Nullable String mergeBranchUuid) { + Branch branch = mock(Branch.class); + when(branch.getType()).thenReturn(type); + when(branch.getName()).thenReturn(name); + when(branch.isMain()).thenReturn(isMain); + when(branch.getReferenceBranchUuid()).thenReturn(mergeBranchUuid); + when(branch.getTargetBranchName()).thenReturn(mergeBranchUuid); + return branch; + } + + private void expectMissingComponentISE(ThrowableAssert.ThrowingCallable callable) { + assertThatThrownBy(callable) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Component has been deleted by end-user during analysis"); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT.java new file mode 100644 index 00000000000..a8e9be91e89 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT.java @@ -0,0 +1,710 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.filemove; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.IntStream; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.ce.task.projectanalysis.analysis.Analysis; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.FileAttributes; +import org.sonar.ce.task.projectanalysis.component.ReportComponent; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; +import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.core.hash.SourceLineHashesComputer; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; +import org.sonar.db.source.FileSourceDto; + +import static java.util.Arrays.stream; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; +import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStep.MIN_REQUIRED_SCORE; +import static org.sonar.db.component.BranchType.*; + +public class FileMoveDetectionStepIT { + + private static final String SNAPSHOT_UUID = "uuid_1"; + private static final Analysis ANALYSIS = new Analysis.Builder() + .setUuid(SNAPSHOT_UUID) + .setCreatedAt(86521) + .build(); + private static final int ROOT_REF = 1; + private static final int FILE_1_REF = 2; + private static final int FILE_2_REF = 3; + private static final int FILE_3_REF = 4; + private static final String[] CONTENT1 = { + "package org.sonar.ce.task.projectanalysis.filemove;", + "", + "public class Foo {", + " public String bar() {", + " return \"Doh!\";", + " }", + "}" + }; + + private static final String[] LESS_CONTENT1 = { + "package org.sonar.ce.task.projectanalysis.filemove;", + "", + "public class Foo {", + " public String foo() {", + " return \"Donut!\";", + " }", + "}" + }; + private static final String[] CONTENT_EMPTY = { + "" + }; + private static final String[] CONTENT2 = { + "package org.sonar.ce.queue;", + "", + "import com.google.common.base.MoreObjects;", + "import javax.annotation.CheckForNull;", + "import javax.annotation.Nullable;", + "import javax.annotation.concurrent.Immutable;", + "", + "import static com.google.common.base.Strings.emptyToNull;", + "import static java.util.Objects.requireNonNull;", + "", + "@Immutable", + "public class CeTask {", + "", + ", private final String type;", + ", private final String uuid;", + ", private final String componentUuid;", + ", private final String componentKey;", + ", private final String componentName;", + ", private final String submitterLogin;", + "", + ", private CeTask(Builder builder) {", + ", this.uuid = requireNonNull(emptyToNull(builder.uuid));", + ", this.type = requireNonNull(emptyToNull(builder.type));", + ", this.componentUuid = emptyToNull(builder.componentUuid);", + ", this.componentKey = emptyToNull(builder.componentKey);", + ", this.componentName = emptyToNull(builder.componentName);", + ", this.submitterLogin = emptyToNull(builder.submitterLogin);", + ", }", + "", + ", public String getUuid() {", + ", return uuid;", + ", }", + "", + ", public String getType() {", + ", return type;", + ", }", + "", + ", @CheckForNull", + ", public String getComponentUuid() {", + ", return componentUuid;", + ", }", + "", + ", @CheckForNull", + ", public String getComponentKey() {", + ", return componentKey;", + ", }", + "", + ", @CheckForNull", + ", public String getComponentName() {", + ", return componentName;", + ", }", + "", + ", @CheckForNull", + ", public String getSubmitterLogin() {", + ", return submitterLogin;", + ", }", + ",}", + }; + // removed immutable annotation + private static final String[] LESS_CONTENT2 = { + "package org.sonar.ce.queue;", + "", + "import com.google.common.base.MoreObjects;", + "import javax.annotation.CheckForNull;", + "import javax.annotation.Nullable;", + "", + "import static com.google.common.base.Strings.emptyToNull;", + "import static java.util.Objects.requireNonNull;", + "", + "public class CeTask {", + "", + ", private final String type;", + ", private final String uuid;", + ", private final String componentUuid;", + ", private final String componentKey;", + ", private final String componentName;", + ", private final String submitterLogin;", + "", + ", private CeTask(Builder builder) {", + ", this.uuid = requireNonNull(emptyToNull(builder.uuid));", + ", this.type = requireNonNull(emptyToNull(builder.type));", + ", this.componentUuid = emptyToNull(builder.componentUuid);", + ", this.componentKey = emptyToNull(builder.componentKey);", + ", this.componentName = emptyToNull(builder.componentName);", + ", this.submitterLogin = emptyToNull(builder.submitterLogin);", + ", }", + "", + ", public String getUuid() {", + ", return uuid;", + ", }", + "", + ", public String getType() {", + ", return type;", + ", }", + "", + ", @CheckForNull", + ", public String getComponentUuid() {", + ", return componentUuid;", + ", }", + "", + ", @CheckForNull", + ", public String getComponentKey() {", + ", return componentKey;", + ", }", + "", + ", @CheckForNull", + ", public String getComponentName() {", + ", return componentName;", + ", }", + "", + ", @CheckForNull", + ", public String getSubmitterLogin() {", + ", return submitterLogin;", + ", }", + ",}", + }; + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + @Rule + public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule(); + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public LogTester logTester = new LogTester(); + + private final DbClient dbClient = dbTester.getDbClient(); + private ComponentDto project; + + private final AnalysisMetadataHolderRule analysisMetadataHolder = mock(AnalysisMetadataHolderRule.class); + private final SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class); + private final FileSimilarity fileSimilarity = new FileSimilarityImpl(new SourceSimilarityImpl()); + private final CapturingScoreMatrixDumper scoreMatrixDumper = new CapturingScoreMatrixDumper(); + private final RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository(); + + private final FileMoveDetectionStep underTest = new FileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient, + fileSimilarity, movedFilesRepository, sourceLinesHash, scoreMatrixDumper, addedFileRepository); + + @Before + public void setUp() throws Exception { + project = dbTester.components().insertPrivateProject(); + treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF).setUuid(project.uuid()).build()); + } + + @Test + public void getDescription_returns_description() { + assertThat(underTest.getDescription()).isEqualTo("Detect file moves"); + } + + @Test + public void execute_detects_no_move_if_in_pull_request_scope() { + prepareAnalysis(PULL_REQUEST, ANALYSIS); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + verifyStatistics(context, null, null, null, null); + } + + @Test + public void execute_detects_no_move_on_first_analysis() { + prepareAnalysis(BRANCH, null); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + verifyStatistics(context, 0, null, null, null); + } + + @Test + public void execute_detects_no_move_if_baseSnapshot_has_no_file_and_report_has_no_file() { + prepareBranchAnalysis(ANALYSIS); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(addedFileRepository.getComponents()).isEmpty(); + verifyStatistics(context, 0, null, null, null); + } + + @Test + public void execute_detects_no_move_if_baseSnapshot_has_no_file() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, null); + setFilesInReport(file1, file2); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(addedFileRepository.getComponents()).containsOnly(file1, file2); + verifyStatistics(context, 2, 0, 2, null); + } + + @Test + public void execute_detects_no_move_if_there_is_no_file_in_report() { + prepareBranchAnalysis(ANALYSIS); + insertFiles( /* no components */); + setFilesInReport(); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(addedFileRepository.getComponents()).isEmpty(); + verifyStatistics(context, 0, null, null, null); + } + + @Test + public void execute_detects_no_move_if_file_key_exists_in_both_DB_and_report() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, null); + insertFiles(file1.getUuid(), file2.getUuid()); + insertContentOfFileInDb(file1.getUuid(), CONTENT1); + insertContentOfFileInDb(file2.getUuid(), CONTENT2); + setFilesInReport(file2, file1); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(addedFileRepository.getComponents()).isEmpty(); + verifyStatistics(context, 2, 2, 0, null); + } + + @Test + public void execute_detects_move_if_content_of_file_is_same_in_DB_and_report() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, CONTENT1); + ComponentDto[] dtos = insertFiles(file1.getUuid()); + insertContentOfFileInDb(file1.getUuid(), CONTENT1); + setFilesInReport(file2); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).containsExactly(file2); + MovedFilesRepository.OriginalFile originalFile = movedFilesRepository.getOriginalFile(file2).get(); + assertThat(originalFile.key()).isEqualTo(dtos[0].getKey()); + assertThat(originalFile.uuid()).isEqualTo(dtos[0].uuid()); + assertThat(addedFileRepository.getComponents()).isEmpty(); + verifyStatistics(context, 1, 1, 1, 1); + } + + @Test + public void execute_detects_no_move_if_content_of_file_is_not_similar_enough() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, LESS_CONTENT1); + insertFiles(file1.getKey()); + insertContentOfFileInDb(file1.getKey(), CONTENT1); + setFilesInReport(file2); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()) + .isPositive() + .isLessThan(MIN_REQUIRED_SCORE); + assertThat(addedFileRepository.getComponents()).contains(file2); + verifyStatistics(context, 1, 1, 1, 0); + } + + @Test + public void execute_detects_no_move_if_content_of_file_is_empty_in_DB() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, CONTENT1); + insertFiles(file1.getKey()); + insertContentOfFileInDb(file1.getKey(), CONTENT_EMPTY); + setFilesInReport(file2); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero(); + assertThat(addedFileRepository.getComponents()).contains(file2); + verifyStatistics(context, 1, 1, 1, 0); + } + + @Test + public void execute_detects_no_move_if_content_of_file_has_no_path_in_DB() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, CONTENT1); + insertFiles(key -> newComponentDto(key).setPath(null), file1.getKey()); + insertContentOfFileInDb(file1.getKey(), CONTENT1); + setFilesInReport(file2); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(scoreMatrixDumper.scoreMatrix).isNull(); + assertThat(addedFileRepository.getComponents()).containsOnly(file2); + verifyStatistics(context, 1, 0, 1, null); + } + + @Test + public void execute_detects_no_move_if_content_of_file_is_empty_in_report() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, CONTENT_EMPTY); + insertFiles(file1.getKey()); + insertContentOfFileInDb(file1.getKey(), CONTENT1); + setFilesInReport(file2); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero(); + assertThat(addedFileRepository.getComponents()).contains(file2); + verifyStatistics(context, 1, 1, 1, 0); + assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("max score in matrix is less than min required score (85). Do nothing."); + } + + @Test + public void execute_detects_no_move_if_two_added_files_have_same_content_as_the_one_in_db() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, CONTENT1); + Component file3 = fileComponent(FILE_3_REF, CONTENT1); + insertFiles(file1.getKey()); + insertContentOfFileInDb(file1.getKey(), CONTENT1); + setFilesInReport(file2, file3); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100); + assertThat(addedFileRepository.getComponents()).containsOnly(file2, file3); + verifyStatistics(context, 2, 1, 2, 0); + } + + @Test + public void execute_detects_no_move_if_two_deleted_files_have_same_content_as_the_one_added() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, null); + Component file3 = fileComponent(FILE_3_REF, CONTENT1); + insertFiles(file1.getUuid(), file2.getUuid()); + insertContentOfFileInDb(file1.getUuid(), CONTENT1); + insertContentOfFileInDb(file2.getUuid(), CONTENT1); + setFilesInReport(file3); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100); + assertThat(addedFileRepository.getComponents()).containsOnly(file3); + verifyStatistics(context, 1, 2, 1, 0); + } + + @Test + public void execute_detects_no_move_if_two_files_are_empty_in_DB() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, null); + insertFiles(file1.getUuid(), file2.getUuid()); + insertContentOfFileInDb(file1.getUuid(), null); + insertContentOfFileInDb(file2.getUuid(), null); + setFilesInReport(file1, file2); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(scoreMatrixDumper.scoreMatrix).isNull(); + assertThat(addedFileRepository.getComponents()).isEmpty(); + verifyStatistics(context, 2, 2, 0, null); + } + + @Test + public void execute_detects_several_moves() { + // testing: + // - file1 renamed to file3 + // - file2 deleted + // - file4 untouched + // - file5 renamed to file6 with a small change + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, null); + Component file3 = fileComponent(FILE_3_REF, CONTENT1); + Component file4 = fileComponent(5, new String[] {"a", "b"}); + Component file5 = fileComponent(6, null); + Component file6 = fileComponent(7, LESS_CONTENT2); + ComponentDto[] dtos = insertFiles(file1.getUuid(), file2.getUuid(), file4.getUuid(), file5.getUuid()); + insertContentOfFileInDb(file1.getUuid(), CONTENT1); + insertContentOfFileInDb(file2.getUuid(), LESS_CONTENT1); + insertContentOfFileInDb(file4.getUuid(), new String[] {"e", "f", "g", "h", "i"}); + insertContentOfFileInDb(file5.getUuid(), CONTENT2); + setFilesInReport(file3, file4, file6); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly(file3, file6); + MovedFilesRepository.OriginalFile originalFile2 = movedFilesRepository.getOriginalFile(file3).get(); + assertThat(originalFile2.key()).isEqualTo(dtos[0].getKey()); + assertThat(originalFile2.uuid()).isEqualTo(dtos[0].uuid()); + MovedFilesRepository.OriginalFile originalFile5 = movedFilesRepository.getOriginalFile(file6).get(); + assertThat(originalFile5.key()).isEqualTo(dtos[3].getKey()); + assertThat(originalFile5.uuid()).isEqualTo(dtos[3].uuid()); + assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isGreaterThan(MIN_REQUIRED_SCORE); + assertThat(addedFileRepository.getComponents()).isEmpty(); + verifyStatistics(context, 3, 4, 2, 2); + } + + @Test + public void execute_does_not_compute_any_distance_if_all_files_sizes_are_all_too_different() { + prepareBranchAnalysis(ANALYSIS); + Component file1 = fileComponent(FILE_1_REF, null); + Component file2 = fileComponent(FILE_2_REF, null); + Component file3 = fileComponent(FILE_3_REF, arrayOf(118)); + Component file4 = fileComponent(5, arrayOf(25)); + insertFiles(file1.getKey(), file2.getKey()); + insertContentOfFileInDb(file1.getKey(), arrayOf(100)); + insertContentOfFileInDb(file2.getKey(), arrayOf(30)); + setFilesInReport(file3, file4); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero(); + verifyStatistics(context, 2, 2, 2, 0); + } + + /** + * Creates an array of {@code numberOfElements} int values as String, starting with zero. + */ + private static String[] arrayOf(int numberOfElements) { + return IntStream.range(0, numberOfElements).mapToObj(String::valueOf).toArray(String[]::new); + } + + /** + * JH: A bug was encountered in the algorithm and I didn't manage to forge a simpler test case. + */ + @Test + public void real_life_use_case() throws Exception { + prepareBranchAnalysis(ANALYSIS); + for (File f : FileUtils.listFiles(new File("src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1"), null, false)) { + insertFiles("uuid_" + f.getName().hashCode()); + insertContentOfFileInDb("uuid_" + f.getName().hashCode(), readLines(f)); + } + + Map comps = new HashMap<>(); + int i = 1; + for (File f : FileUtils.listFiles(new File("src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2"), null, false)) { + String[] lines = readLines(f); + Component c = builder(Component.Type.FILE, i++) + .setUuid("uuid_" + f.getName().hashCode()) + .setKey(f.getName()) + .setName(f.getName()) + .setFileAttributes(new FileAttributes(false, null, lines.length)) + .build(); + + comps.put(f.getName(), c); + setFileLineHashesInReport(c, lines); + } + + setFilesInReport(comps.values().toArray(new Component[0])); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + Component makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex = comps.get("MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java"); + Component migrationRb1238 = comps.get("1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb"); + Component addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex = comps.get("AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java"); + assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly( + makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex, + migrationRb1238, + addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex); + + assertThat(movedFilesRepository.getOriginalFile(makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex).get().uuid()) + .isEqualTo("uuid_" + "MakeComponentUuidNotNullOnDuplicationsIndex.java".hashCode()); + assertThat(movedFilesRepository.getOriginalFile(migrationRb1238).get().uuid()) + .isEqualTo("uuid_" + "1242_make_analysis_uuid_not_null_on_duplications_index.rb".hashCode()); + assertThat(movedFilesRepository.getOriginalFile(addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex).get().uuid()) + .isEqualTo("uuid_" + "AddComponentUuidColumnToDuplicationsIndex.java".hashCode()); + verifyStatistics(context, comps.values().size(), 12, 6, 3); + } + + private String[] readLines(File filename) throws IOException { + return FileUtils + .readLines(filename, StandardCharsets.UTF_8) + .toArray(new String[0]); + } + + @CheckForNull + private FileSourceDto insertContentOfFileInDb(String uuid, @Nullable String[] content) { + return dbTester.getDbClient().componentDao().selectByUuid(dbTester.getSession(), uuid) + .map(file -> { + SourceLineHashesComputer linesHashesComputer = new SourceLineHashesComputer(); + if (content != null) { + stream(content).forEach(linesHashesComputer::addLine); + } + FileSourceDto fileSourceDto = new FileSourceDto() + .setUuid(Uuids.createFast()) + .setFileUuid(file.uuid()) + .setProjectUuid(file.branchUuid()) + .setLineHashes(linesHashesComputer.getLineHashes()); + dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto); + dbTester.commit(); + return fileSourceDto; + }).orElse(null); + } + + private void setFilesInReport(Component... files) { + treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF) + .setUuid(project.uuid()) + .addChildren(files) + .build()); + } + + private ComponentDto[] insertFiles(String... uuids) { + return insertFiles(this::newComponentDto, uuids); + } + + private ComponentDto[] insertFiles(Function newComponentDto, String... uuids) { + return stream(uuids) + .map(newComponentDto) + .map(dto -> dbTester.components().insertComponent(dto)) + .toArray(ComponentDto[]::new); + } + + private ComponentDto newComponentDto(String uuid) { + return ComponentTesting.newFileDto(project) + .setKey("key_" + uuid) + .setUuid(uuid) + .setPath("path_" + uuid); + } + + private Component fileComponent(int ref, @Nullable String[] content) { + ReportComponent component = builder(Component.Type.FILE, ref) + .setName("report_path" + ref) + .setFileAttributes(new FileAttributes(false, null, content == null ? 1 : content.length)) + .build(); + if (content != null) { + setFileLineHashesInReport(component, content); + } + return component; + } + + private void setFileLineHashesInReport(Component file, String[] content) { + SourceLineHashesComputer computer = new SourceLineHashesComputer(); + for (String line : content) { + computer.addLine(line); + } + when(sourceLinesHash.getLineHashesMatchingDBVersion(file)).thenReturn(computer.getLineHashes()); + } + + private static class CapturingScoreMatrixDumper implements ScoreMatrixDumper { + private ScoreMatrix scoreMatrix; + + @Override + public void dumpAsCsv(ScoreMatrix scoreMatrix) { + this.scoreMatrix = scoreMatrix; + } + } + + private void prepareBranchAnalysis(Analysis analysis) { + prepareAnalysis(BRANCH, analysis); + } + + private void prepareAnalysis(BranchType branch, Analysis analysis) { + mockBranchType(branch); + analysisMetadataHolder.setBaseAnalysis(analysis); + } + + private void mockBranchType(BranchType branchType) { + when(analysisMetadataHolder.isPullRequest()).thenReturn(branchType == PULL_REQUEST); + } + + public static void verifyStatistics(TestComputationStepContext context, + @Nullable Integer expectedReportFiles, @Nullable Integer expectedDbFiles, + @Nullable Integer expectedAddedFiles, @Nullable Integer expectedMovedFiles) { + context.getStatistics().assertValue("reportFiles", expectedReportFiles); + context.getStatistics().assertValue("dbFiles", expectedDbFiles); + context.getStatistics().assertValue("addedFiles", expectedAddedFiles); + context.getStatistics().assertValue("movedFiles", expectedMovedFiles); + } + + public static class RecordingMutableAddedFileRepository implements MutableAddedFileRepository { + private final List components = new ArrayList<>(); + + @Override + public void register(Component file) { + components.add(file); + } + + @Override + public boolean isAdded(Component component) { + throw new UnsupportedOperationException("isAdded should not be called"); + } + + public List getComponents() { + return components; + } + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepIT.java new file mode 100644 index 00000000000..044c69fe388 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepIT.java @@ -0,0 +1,385 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.filemove; + +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.Immutable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.LogTester; +import org.sonar.ce.task.projectanalysis.analysis.Analysis; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.analysis.Branch; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.FileAttributes; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; +import org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepIT.RecordingMutableAddedFileRepository; +import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository.OriginalFile; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; +import org.sonar.db.source.FileSourceDto; +import org.sonar.server.project.Project; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE; +import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT; +import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; +import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepIT.verifyStatistics; +import static org.sonar.db.component.BranchType.BRANCH; +import static org.sonar.db.component.BranchType.PULL_REQUEST; + +public class PullRequestFileMoveDetectionStepIT { + private static final String ROOT_REF = "0"; + private static final String FILE_1_REF = "1"; + private static final String FILE_2_REF = "2"; + private static final String FILE_3_REF = "3"; + private static final String FILE_4_REF = "4"; + private static final String FILE_5_REF = "5"; + private static final String FILE_6_REF = "6"; + private static final String FILE_7_REF = "7"; + private static final String TARGET_BRANCH = "target_branch"; + private static final String BRANCH_UUID = "branch_uuid"; + private static final String SNAPSHOT_UUID = "uuid_1"; + + private static final Analysis ANALYSIS = new Analysis.Builder() + .setUuid(SNAPSHOT_UUID) + .setCreatedAt(86521) + .build(); + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + @Rule + public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule(); + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public LogTester logTester = new LogTester(); + + private ComponentDto branch; + private ComponentDto project; + + private final DbClient dbClient = dbTester.getDbClient(); + private final AnalysisMetadataHolderRule analysisMetadataHolder = mock(AnalysisMetadataHolderRule.class); + private final RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository(); + private final PullRequestFileMoveDetectionStep underTest = new PullRequestFileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient, movedFilesRepository, addedFileRepository); + + @Before + public void setUp() throws Exception { + project = dbTester.components().insertPrivateProject(); + branch = dbTester.components().insertProjectBranch(project, branchDto -> branchDto.setUuid(BRANCH_UUID).setKey(TARGET_BRANCH)); + treeRootHolder.setRoot(builder(Component.Type.PROJECT, Integer.parseInt(ROOT_REF)).setUuid(project.uuid()).build()); + } + + @Test + public void getDescription_returns_description() { + assertThat(underTest.getDescription()).isEqualTo("Detect file moves in Pull Request scope"); + } + + @Test + public void execute_does_not_detect_any_files_if_not_in_pull_request_scope() { + prepareAnalysis(BRANCH, ANALYSIS); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + verifyStatistics(context, null, null, null, null); + } + + @Test + public void execute_detects_no_move_if_report_has_no_file() { + preparePullRequestAnalysis(ANALYSIS); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(addedFileRepository.getComponents()).isEmpty(); + verifyStatistics(context, 0, null, null, null); + } + + @Test + public void execute_detects_no_move_if_target_branch_has_no_files() { + preparePullRequestAnalysis(ANALYSIS); + Set fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF)); + Map reportFilesByUuid = initializeAnalysisReportComponents(fileReferences); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(addedFileRepository.getComponents()).containsOnlyOnceElementsOf(reportFilesByUuid.values()); + verifyStatistics(context, 2, 0, 2, null); + } + + @Test + public void execute_detects_no_move_if_there_are_no_files_in_report() { + preparePullRequestAnalysis(ANALYSIS); + initializeAnalysisReportComponents(Set.of()); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(addedFileRepository.getComponents()).isEmpty(); + verifyStatistics(context, 0, null, null, null); + } + + @Test + public void execute_detects_no_move_if_file_key_exists_in_both_database_and_report() { + preparePullRequestAnalysis(ANALYSIS); + + Set fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF)); + initializeAnalysisReportComponents(fileReferences); + initializeTargetBranchDatabaseComponents(fileReferences); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); + assertThat(addedFileRepository.getComponents()).isEmpty(); + verifyStatistics(context, 2, 2, 0, 0); + } + + @Test + public void execute_detects_renamed_file() { + // - FILE_1_REF on target branch is renamed to FILE_2_REF on Pull Request + preparePullRequestAnalysis(ANALYSIS); + + Set reportFileReferences = Set.of(FileReference.of(FILE_2_REF, FILE_1_REF)); + Set databaseFileReferences = Set.of(FileReference.of(FILE_1_REF)); + + Map reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences); + Map databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(addedFileRepository.getComponents()).isEmpty(); + assertThat(movedFilesRepository.getComponentsWithOriginal()).hasSize(1); + assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_2_REF, FILE_1_REF); + verifyStatistics(context, 1, 1, 0, 1); + } + + @Test + public void execute_detects_several_renamed_file() { + // - FILE_1_REF has been renamed to FILE_3_REF on Pull Request + // - FILE_2_REF has been deleted on Pull Request + // - FILE_4_REF has been left untouched + // - FILE_5_REF has been renamed to FILE_6_REF on Pull Request + // - FILE_7_REF has been added on Pull Request + preparePullRequestAnalysis(ANALYSIS); + + Set reportFileReferences = Set.of( + FileReference.of(FILE_3_REF, FILE_1_REF), + FileReference.of(FILE_4_REF), + FileReference.of(FILE_6_REF, FILE_5_REF), + FileReference.of(FILE_7_REF)); + + Set databaseFileReferences = Set.of( + FileReference.of(FILE_1_REF), + FileReference.of(FILE_2_REF), + FileReference.of(FILE_4_REF), + FileReference.of(FILE_5_REF)); + + Map reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences); + Map databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(addedFileRepository.getComponents()).hasSize(1); + assertThat(movedFilesRepository.getComponentsWithOriginal()).hasSize(2); + assertThatFileAdditionHasBeenDetected(reportFilesByUuid, FILE_7_REF); + assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_3_REF, FILE_1_REF); + assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_6_REF, FILE_5_REF); + verifyStatistics(context, 4, 4, 1, 2); + } + + private void assertThatFileAdditionHasBeenDetected(Map reportFilesByUuid, String fileInReportReference) { + Component fileInReport = reportFilesByUuid.get(fileInReportReference); + + assertThat(addedFileRepository.getComponents()).contains(fileInReport); + assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isEmpty(); + } + + + private void assertThatFileRenameHasBeenDetected(Map reportFilesByUuid, Map databaseFilesByUuid, String fileInReportReference, String originalFileInDatabaseReference) { + Component fileInReport = reportFilesByUuid.get(fileInReportReference); + Component originalFileInDatabase = databaseFilesByUuid.get(originalFileInDatabaseReference); + + assertThat(movedFilesRepository.getComponentsWithOriginal()).contains(fileInReport); + assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isPresent(); + + OriginalFile detectedOriginalFile = movedFilesRepository.getOriginalPullRequestFile(fileInReport).get(); + assertThat(detectedOriginalFile.key()).isEqualTo(originalFileInDatabase.getKey()); + assertThat(detectedOriginalFile.uuid()).isEqualTo(originalFileInDatabase.getUuid()); + } + + private Map initializeTargetBranchDatabaseComponents(Set references) { + Set fileComponents = createFileComponents(references); + insertFileComponentsInDatabase(fileComponents); + return toFileComponentsByUuidMap(fileComponents); + } + + private Map initializeAnalysisReportComponents(Set refs) { + Set fileComponents = createFileComponents(refs); + insertFileComponentsInReport(fileComponents); + return toFileComponentsByUuidMap(fileComponents); + } + + private Map toFileComponentsByUuidMap(Set fileComponents) { + return fileComponents + .stream() + .collect(toMap(Component::getUuid, identity())); + } + + private static Set createFileComponents(Set references) { + return references + .stream() + .map(PullRequestFileMoveDetectionStepIT::createReportFileComponent) + .collect(toSet()); + } + + private static Component createReportFileComponent(FileReference fileReference) { + return builder(FILE, Integer.parseInt(fileReference.getReference())) + .setUuid(fileReference.getReference()) + .setName("report_path" + fileReference.getReference()) + .setFileAttributes(new FileAttributes(false, null, 1, false, composeComponentPath(fileReference.getPastReference()))) + .build(); + } + + private void insertFileComponentsInReport(Set files) { + treeRootHolder + .setRoot(builder(PROJECT, Integer.parseInt(ROOT_REF)) + .setUuid(project.uuid()) + .addChildren(files.toArray(Component[]::new)) + .build()); + } + + private Set insertFileComponentsInDatabase(Set files) { + return files + .stream() + .map(Component::getUuid) + .map(this::composeComponentDto) + .peek(this::insertComponentDto) + .peek(this::insertContentOfFileInDatabase) + .collect(toSet()); + } + + private void insertComponentDto(ComponentDto component) { + dbTester.components().insertComponent(component); + } + + private ComponentDto composeComponentDto(String uuid) { + return ComponentTesting + .newFileDto(project) + .setBranchUuid(branch.uuid()) + .setKey("key_" + uuid) + .setUuid(uuid) + .setPath(composeComponentPath(uuid)); + } + + @CheckForNull + private static String composeComponentPath(@Nullable String reference) { + return Optional.ofNullable(reference) + .map(r -> String.join("_", "path", r)) + .orElse(null); + } + + private FileSourceDto insertContentOfFileInDatabase(ComponentDto file) { + FileSourceDto fileSourceDto = composeFileSourceDto(file); + persistFileSourceDto(fileSourceDto); + return fileSourceDto; + } + + private static FileSourceDto composeFileSourceDto(ComponentDto file) { + return new FileSourceDto() + .setUuid(Uuids.createFast()) + .setFileUuid(file.uuid()) + .setProjectUuid(file.branchUuid()); + } + + private void persistFileSourceDto(FileSourceDto fileSourceDto) { + dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto); + dbTester.commit(); + } + + private void preparePullRequestAnalysis(Analysis analysis) { + prepareAnalysis(PULL_REQUEST, analysis); + } + + private void prepareAnalysis(BranchType branch, Analysis analysis) { + mockBranchType(branch); + analysisMetadataHolder.setBaseAnalysis(analysis); + } + + private void mockBranchType(BranchType branchType) { + Branch branch = mock(Branch.class); + when(analysisMetadataHolder.getBranch()).thenReturn(branch); + when(analysisMetadataHolder.getBranch().getTargetBranchName()).thenReturn(TARGET_BRANCH); + when(analysisMetadataHolder.isPullRequest()).thenReturn(branchType == PULL_REQUEST); + when(analysisMetadataHolder.getProject()).thenReturn(Project.from(project)); + } + + @Immutable + private static class FileReference { + private final String reference; + private final String pastReference; + + private FileReference(String reference, @Nullable String pastReference) { + this.reference = reference; + this.pastReference = pastReference; + } + + public String getReference() { + return reference; + } + + @CheckForNull + public String getPastReference() { + return pastReference; + } + + public static FileReference of(String reference, String pastReference) { + return new FileReference(reference, pastReference); + } + + public static FileReference of(String reference) { + return new FileReference(reference, null); + } + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplIT.java new file mode 100644 index 00000000000..5f0aecb379a --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplIT.java @@ -0,0 +1,214 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.metric; + +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.metric.MetricDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class MetricRepositoryImplIT { + private static final String SOME_KEY = "some_key"; + private static final String SOME_UUID = "uuid"; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private DbClient dbClient = dbTester.getDbClient(); + private MetricRepositoryImpl underTest = new MetricRepositoryImpl(dbClient); + + @Test + public void getByKey_throws_NPE_if_arg_is_null() { + assertThatThrownBy(() -> underTest.getByKey(null)) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void getByKey_throws_ISE_if_start_has_not_been_called() { + assertThatThrownBy(() -> underTest.getByKey(SOME_KEY)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Metric cache has not been initialized"); + } + + @Test + public void getByKey_throws_ISE_of_Metric_does_not_exist() { + assertThatThrownBy(() -> { + underTest.start(); + underTest.getByKey(SOME_KEY); + }) + .isInstanceOf(IllegalStateException.class) + .hasMessage(String.format("Metric with key '%s' does not exist", SOME_KEY)); + } + + @Test + public void getByKey_throws_ISE_of_Metric_is_disabled() { + dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false)); + + underTest.start(); + + assertThatThrownBy(() -> underTest.getByKey("complexity")) + .isInstanceOf(IllegalStateException.class) + .hasMessage(String.format("Metric with key '%s' does not exist", "complexity")); + } + + @Test + public void getByKey_find_enabled_Metrics() { + MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true)); + MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true)); + + underTest.start(); + + assertThat(underTest.getByKey("ncloc").getUuid()).isEqualTo(ncloc.getUuid()); + assertThat(underTest.getByKey("coverage").getUuid()).isEqualTo(coverage.getUuid()); + } + + @Test + public void getById_throws_ISE_if_start_has_not_been_called() { + assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Metric cache has not been initialized"); + } + + @Test + public void getById_throws_ISE_of_Metric_does_not_exist() { + underTest.start(); + + assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID)) + .isInstanceOf(IllegalStateException.class) + .hasMessage(String.format("Metric with uuid '%s' does not exist", SOME_UUID)); + } + + @Test + public void getById_throws_ISE_of_Metric_is_disabled() { + dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false)); + + underTest.start(); + + assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID)) + .isInstanceOf(IllegalStateException.class) + .hasMessage(String.format("Metric with uuid '%s' does not exist", SOME_UUID)); + } + + @Test + public void getById_find_enabled_Metrics() { + MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true)); + MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true)); + + underTest.start(); + + assertThat(underTest.getByUuid(ncloc.getUuid()).getKey()).isEqualTo("ncloc"); + assertThat(underTest.getByUuid(coverage.getUuid()).getKey()).isEqualTo("coverage"); + } + + @Test + public void getOptionalById_throws_ISE_if_start_has_not_been_called() { + assertThatThrownBy(() -> underTest.getOptionalByUuid(SOME_UUID)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Metric cache has not been initialized"); + } + + @Test + public void getOptionalById_returns_empty_of_Metric_does_not_exist() { + underTest.start(); + + assertThat(underTest.getOptionalByUuid(SOME_UUID)).isEmpty(); + } + + @Test + public void getOptionalById_returns_empty_of_Metric_is_disabled() { + dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false)); + + underTest.start(); + + assertThat(underTest.getOptionalByUuid(SOME_UUID)).isEmpty(); + } + + @Test + public void getOptionalById_find_enabled_Metrics() { + MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true)); + MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true)); + + underTest.start(); + + assertThat(underTest.getOptionalByUuid(ncloc.getUuid()).get().getKey()).isEqualTo("ncloc"); + assertThat(underTest.getOptionalByUuid(coverage.getUuid()).get().getKey()).isEqualTo("coverage"); + } + + @Test + public void get_all_metrics() { + List enabledMetrics = IntStream.range(0, 1 + new Random().nextInt(12)) + .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true))) + .toList(); + IntStream.range(0, 1 + new Random().nextInt(12)) + .forEach(i -> dbTester.measures().insertMetric(t -> t.setKey("key_disabled_" + i).setEnabled(false))); + + underTest.start(); + assertThat(underTest.getAll()) + .extracting(Metric::getKey) + .containsOnly(enabledMetrics.stream().map(MetricDto::getKey).toArray(String[]::new)); + } + + @Test + public void getMetricsByType_givenRatingType_returnRatingMetrics() { + List enabledMetrics = IntStream.range(0, 1 + new Random().nextInt(12)) + .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("RATING"))) + .toList(); + + underTest.start(); + assertThat(underTest.getMetricsByType(Metric.MetricType.RATING)) + .extracting(Metric::getKey) + .containsOnly(enabledMetrics.stream().map(MetricDto::getKey).toArray(String[]::new)); + } + + @Test + public void getMetricsByType_givenRatingTypeAndWantedMilisecType_returnEmptyList() { + IntStream.range(0, 1 + new Random().nextInt(12)) + .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("RATING"))) + .collect(Collectors.toList()); + + underTest.start(); + assertThat(underTest.getMetricsByType(Metric.MetricType.MILLISEC)).isEmpty(); + } + + @Test + public void getMetricsByType_givenOnlyMilisecTypeAndWantedRatingMetrics_returnEmptyList() { + IntStream.range(0, 1 + new Random().nextInt(12)) + .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("MILISEC"))); + + underTest.start(); + assertThat(underTest.getMetricsByType(Metric.MetricType.RATING)).isEmpty(); + } + + @Test + public void getMetricsByType_givenMetricsAreNull_throwException() { + assertThatThrownBy(() -> underTest.getMetricsByType(Metric.MetricType.RATING)) + .isInstanceOf(IllegalStateException.class); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderIT.java new file mode 100644 index 00000000000..1591410151c --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderIT.java @@ -0,0 +1,249 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.scm; + +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.LogTester; +import org.sonar.ce.task.projectanalysis.analysis.Analysis; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.analysis.Branch; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids; +import org.sonar.ce.task.projectanalysis.filemove.MutableMovedFilesRepositoryRule; +import org.sonar.ce.task.projectanalysis.period.NewCodeReferenceBranchComponentUuids; +import org.sonar.ce.task.projectanalysis.period.Period; +import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule; +import org.sonar.core.hash.SourceHashComputer; +import org.sonar.core.util.Uuids; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchType; +import org.sonar.db.newcodeperiod.NewCodePeriodType; +import org.sonar.db.protobuf.DbFileSources; +import org.sonar.db.source.FileSourceDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.api.utils.log.LoggerLevel.TRACE; +import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; + +public class ScmInfoDbLoaderIT { + static final int FILE_REF = 1; + static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build(); + static final long DATE_1 = 123456789L; + + static Analysis baseProjectAnalysis = new Analysis.Builder() + .setUuid("uuid_1") + .setCreatedAt(123456789L) + .build(); + + @Rule + public LogTester logTester = new LogTester(); + @Rule + public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public BatchReportReaderRule reportReader = new BatchReportReaderRule(); + @Rule + public MutableMovedFilesRepositoryRule movedFiles = new MutableMovedFilesRepositoryRule(); + @Rule + public PeriodHolderRule periodHolder = new PeriodHolderRule(); + + private final Branch branch = mock(Branch.class); + private final ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class); + private final NewCodeReferenceBranchComponentUuids newCodeReferenceBranchComponentUuids = mock(NewCodeReferenceBranchComponentUuids.class); + + private final ScmInfoDbLoader underTest = new ScmInfoDbLoader(analysisMetadataHolder, movedFiles, dbTester.getDbClient(), referenceBranchComponentUuids, + newCodeReferenceBranchComponentUuids, periodHolder); + + @Before + public void before() { + periodHolder.setPeriod(new Period(NewCodePeriodType.PREVIOUS_VERSION.name(), null, null)); + } + + @Test + public void returns_ScmInfo_from_DB() { + analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); + analysisMetadataHolder.setBranch(null); + + String hash = computeSourceHash(1); + addFileSourceInDb("henry", DATE_1, "rev-1", hash); + + DbScmInfo scmInfo = underTest.getScmInfo(FILE).get(); + assertThat(scmInfo.getAllChangesets()).hasSize(1); + assertThat(scmInfo.fileHash()).isEqualTo(hash); + + assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'"); + } + + @Test + public void read_from_reference_branch_if_no_base() { + analysisMetadataHolder.setBaseAnalysis(null); + analysisMetadataHolder.setBranch(branch); + + String referenceFileUuid = "referenceFileUuid"; + String hash = computeSourceHash(1); + + when(referenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(referenceFileUuid); + addFileSourceInDb("henry", DATE_1, "rev-1", hash, referenceFileUuid); + + DbScmInfo scmInfo = underTest.getScmInfo(FILE).get(); + assertThat(scmInfo.getAllChangesets()).hasSize(1); + assertThat(scmInfo.fileHash()).isEqualTo(hash); + assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'referenceFileUuid'"); + } + + @Test + public void read_from_target_if_pullrequest() { + Branch branch = mock(Branch.class); + when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); + analysisMetadataHolder.setBaseAnalysis(null); + analysisMetadataHolder.setBranch(branch); + + String targetBranchFileUuid = "targetBranchFileUuid"; + String hash = computeSourceHash(1); + + when(referenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(targetBranchFileUuid); + addFileSourceInDb("henry", DATE_1, "rev-1", hash, targetBranchFileUuid); + + DbScmInfo scmInfo = underTest.getScmInfo(FILE).get(); + assertThat(scmInfo.getAllChangesets()).hasSize(1); + assertThat(scmInfo.fileHash()).isEqualTo(hash); + assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'targetBranchFileUuid'"); + } + + @Test + public void read_from_target_if_reference_branch() { + periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), null, null)); + + Branch branch = mock(Branch.class); + when(branch.getType()).thenReturn(BranchType.BRANCH); + analysisMetadataHolder.setBaseAnalysis(null); + analysisMetadataHolder.setBranch(branch); + + String targetBranchFileUuid = "targetBranchFileUuid"; + String hash = computeSourceHash(1); + + when(newCodeReferenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(targetBranchFileUuid); + addFileSourceInDb("henry", DATE_1, "rev-1", hash, targetBranchFileUuid); + + DbScmInfo scmInfo = underTest.getScmInfo(FILE).get(); + assertThat(scmInfo.getAllChangesets()).hasSize(1); + assertThat(scmInfo.fileHash()).isEqualTo(hash); + assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'targetBranchFileUuid'"); + } + + @Test + public void read_from_db_if_not_exist_in_reference_branch() { + periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), null, null)); + + Branch branch = mock(Branch.class); + when(branch.getType()).thenReturn(BranchType.BRANCH); + analysisMetadataHolder.setBaseAnalysis(null); + analysisMetadataHolder.setBranch(branch); + + String hash = computeSourceHash(1); + + addFileSourceInDb("henry", DATE_1, "rev-1", hash, FILE.getUuid()); + + DbScmInfo scmInfo = underTest.getScmInfo(FILE).get(); + assertThat(scmInfo.getAllChangesets()).hasSize(1); + assertThat(scmInfo.fileHash()).isEqualTo(hash); + assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'"); + } + + @Test + public void return_empty_if_no_dto_available() { + analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); + analysisMetadataHolder.setBranch(null); + + Optional scmInfo = underTest.getScmInfo(FILE); + + assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'"); + assertThat(scmInfo).isEmpty(); + } + + @Test + public void do_not_read_from_db_on_first_analysis_if_there_is_no_reference_branch() { + Branch branch = mock(Branch.class); + when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); + analysisMetadataHolder.setBaseAnalysis(null); + analysisMetadataHolder.setBranch(branch); + + assertThat(underTest.getScmInfo(FILE)).isEmpty(); + assertThat(logTester.logs(TRACE)).isEmpty(); + } + + private static List generateLines(int lineCount) { + ImmutableList.Builder builder = ImmutableList.builder(); + for (int i = 0; i < lineCount; i++) { + builder.add("line " + i); + } + return builder.build(); + } + + private static String computeSourceHash(int lineCount) { + SourceHashComputer sourceHashComputer = new SourceHashComputer(); + Iterator lines = generateLines(lineCount).iterator(); + while (lines.hasNext()) { + sourceHashComputer.addLine(lines.next(), lines.hasNext()); + } + return sourceHashComputer.getHash(); + } + + private void addFileSourceInDb(@Nullable String author, @Nullable Long date, @Nullable String revision, String srcHash) { + addFileSourceInDb(author, date, revision, srcHash, FILE.getUuid()); + } + + private void addFileSourceInDb(@Nullable String author, @Nullable Long date, @Nullable String revision, String srcHash, String fileUuid) { + DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); + DbFileSources.Line.Builder builder = fileDataBuilder.addLinesBuilder() + .setLine(1); + if (author != null) { + builder.setScmAuthor(author); + } + if (date != null) { + builder.setScmDate(date); + } + if (revision != null) { + builder.setScmRevision(revision); + } + dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), new FileSourceDto() + .setUuid(Uuids.createFast()) + .setLineHashes(Collections.singletonList("lineHash")) + .setFileUuid(fileUuid) + .setProjectUuid("PROJECT_UUID") + .setSourceData(fileDataBuilder.build()) + .setSrcHash(srcHash)); + dbTester.commit(); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepIT.java new file mode 100644 index 00000000000..141a461ff31 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepIT.java @@ -0,0 +1,586 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.step; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.projectanalysis.analysis.Branch; +import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl; +import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.scanner.protocol.output.ScannerReport; +import org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType; +import org.sonar.scanner.protocol.output.ScannerReport.Component.FileStatus; +import org.sonar.server.project.Project; + +import static java.util.Optional.ofNullable; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME; +import static org.sonar.db.component.ComponentTesting.newDirectory; +import static org.sonar.db.component.ComponentTesting.newFileDto; +import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; +import static org.sonar.db.component.SnapshotTesting.newAnalysis; +import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.FILE; +import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.PROJECT; + +@RunWith(DataProviderRunner.class) +public class BuildComponentTreeStepIT { + private static final String NO_SCANNER_PROJECT_VERSION = null; + private static final String NO_SCANNER_BUILD_STRING = null; + + private static final int ROOT_REF = 1; + private static final int FILE_1_REF = 4; + private static final int FILE_2_REF = 5; + private static final int FILE_3_REF = 7; + private static final int UNCHANGED_FILE_REF = 10; + + private static final String REPORT_PROJECT_KEY = "REPORT_PROJECT_KEY"; + private static final String REPORT_DIR_PATH_1 = "src/main/java/dir1"; + private static final String REPORT_FILE_PATH_1 = "src/main/java/dir1/File1.java"; + private static final String REPORT_FILE_NAME_1 = "File1.java"; + private static final String REPORT_DIR_PATH_2 = "src/main/java/dir2"; + private static final String REPORT_FILE_PATH_2 = "src/main/java/dir2/File2.java"; + private static final String REPORT_FILE_PATH_3 = "src/main/java/dir2/File3.java"; + private static final String REPORT_UNCHANGED_FILE_PATH = "src/main/File3.java"; + + private static final long ANALYSIS_DATE = 123456789L; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public BatchReportReaderRule reportReader = new BatchReportReaderRule().setMetadata(createReportMetadata(NO_SCANNER_PROJECT_VERSION, NO_SCANNER_BUILD_STRING)); + @Rule + public MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule(); + @Rule + public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule(); + + private DbClient dbClient = dbTester.getDbClient(); + private BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder); + + @Test + public void fails_if_root_component_does_not_exist_in_reportReader() { + setAnalysisMetadataHolder(); + + assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext())) + .isInstanceOf(NullPointerException.class); + } + + @Test + public void verify_tree_is_correctly_built() { + setAnalysisMetadataHolder(); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF, FILE_3_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); + reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2)); + reportReader.putComponent(componentWithPath(FILE_3_REF, FILE, REPORT_FILE_PATH_3)); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + Component root = treeRootHolder.getRoot(); + assertThat(root).isNotNull(); + verifyComponent(root, Component.Type.PROJECT, ROOT_REF, 1); + + Component dir = root.getChildren().iterator().next(); + verifyComponent(dir, Component.Type.DIRECTORY, null, 2); + + Component dir1 = dir.getChildren().get(0); + verifyComponent(dir1, Component.Type.DIRECTORY, null, 1); + verifyComponent(dir1.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0); + + Component dir2 = dir.getChildren().get(1); + verifyComponent(dir2, Component.Type.DIRECTORY, null, 2); + verifyComponent(dir2.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0); + verifyComponent(dir2.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0); + + context.getStatistics().assertValue("components", 7); + } + + @Test + public void verify_tree_is_correctly_built_in_prs() { + setAnalysisMetadataHolder(true); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF, FILE_3_REF, UNCHANGED_FILE_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); + reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2)); + reportReader.putComponent(componentWithPath(FILE_3_REF, FILE, REPORT_FILE_PATH_3)); + reportReader.putComponent(unchangedComponentWithPath(UNCHANGED_FILE_REF, FILE, REPORT_UNCHANGED_FILE_PATH)); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + // modified root + Component mRoot = treeRootHolder.getRoot(); + verifyComponent(mRoot, Component.Type.PROJECT, ROOT_REF, 1); + + Component mDir = mRoot.getChildren().get(0); + assertThat(mDir.getName()).isEqualTo("src/main/java"); + verifyComponent(mDir, Component.Type.DIRECTORY, null, 2); + + Component mDir1 = mDir.getChildren().get(0); + assertThat(mDir1.getName()).isEqualTo("src/main/java/dir1"); + verifyComponent(mDir1, Component.Type.DIRECTORY, null, 1); + verifyComponent(mDir1.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0); + + Component mDir2 = mDir.getChildren().get(1); + assertThat(mDir2.getName()).isEqualTo("src/main/java/dir2"); + verifyComponent(mDir2, Component.Type.DIRECTORY, null, 2); + verifyComponent(mDir2.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0); + verifyComponent(mDir2.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0); + + // root + Component root = treeRootHolder.getReportTreeRoot(); + verifyComponent(root, Component.Type.PROJECT, ROOT_REF, 1); + + Component dir = root.getChildren().get(0); + assertThat(dir.getName()).isEqualTo("src/main"); + verifyComponent(dir, Component.Type.DIRECTORY, null, 2); + + Component dir1 = dir.getChildren().get(0); + assertThat(dir1.getName()).isEqualTo("src/main/java"); + verifyComponent(dir1, Component.Type.DIRECTORY, null, 2); + verifyComponent(dir1.getChildren().get(0), Component.Type.DIRECTORY, null, 1); + verifyComponent(dir1.getChildren().get(1), Component.Type.DIRECTORY, null, 2); + + Component dir2 = dir1.getChildren().get(0); + assertThat(dir2.getName()).isEqualTo("src/main/java/dir1"); + verifyComponent(dir2, Component.Type.DIRECTORY, null, 1); + verifyComponent(dir2.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0); + + Component dir3 = dir1.getChildren().get(1); + assertThat(dir3.getName()).isEqualTo("src/main/java/dir2"); + verifyComponent(dir3, Component.Type.DIRECTORY, null, 2); + verifyComponent(dir3.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0); + verifyComponent(dir3.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0); + + context.getStatistics().assertValue("components", 7); + } + + /** + * SONAR-13262 + */ + @Test + public void verify_tree_is_correctly_built_in_prs_with_repeated_names() { + setAnalysisMetadataHolder(true); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_PROJECT_KEY + "/file.js")); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + // modified root + Component mRoot = treeRootHolder.getRoot(); + verifyComponent(mRoot, Component.Type.PROJECT, ROOT_REF, 1); + + Component dir = mRoot.getChildren().get(0); + assertThat(dir.getName()).isEqualTo(REPORT_PROJECT_KEY); + assertThat(dir.getShortName()).isEqualTo(REPORT_PROJECT_KEY); + + verifyComponent(dir, Component.Type.DIRECTORY, null, 1); + } + + @Test + public void compute_keys_and_uuids() { + setAnalysisMetadataHolder(); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); + + underTest.execute(new TestComputationStepContext()); + + verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName()); + verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1); + verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1); + } + + @Test + public void return_existing_uuids() { + setAnalysisMetadataHolder(); + ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY)); + ComponentDto directory = newDirectory(project, "CDEF", REPORT_DIR_PATH_1); + insertComponent(directory.setKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1)); + insertComponent(newFileDto(project, directory, "DEFG") + .setKey(REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1) + .setPath(REPORT_FILE_PATH_1)); + + // new structure, without modules + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); + + underTest.execute(new TestComputationStepContext()); + + verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName(), "ABCD"); + verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1, "CDEF"); + verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, "DEFG"); + } + + @Test + public void generate_keys_when_using_new_branch() { + Branch branch = mock(Branch.class); + when(branch.getName()).thenReturn("origin/feature"); + when(branch.isMain()).thenReturn(false); + when(branch.generateKey(any(), any())).thenReturn("generated"); + analysisMetadataHolder.setRootComponentRef(ROOT_REF) + .setAnalysisDate(ANALYSIS_DATE) + .setProject(Project.from(newPrivateProjectDto().setKey(REPORT_PROJECT_KEY))) + .setBranch(branch); + BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); + + underTest.execute(new TestComputationStepContext()); + + verifyComponentByRef(ROOT_REF, "generated", analysisMetadataHolder.getProject().getName(), null); + verifyComponentByRef(FILE_1_REF, "generated", REPORT_FILE_NAME_1, null); + } + + @Test + public void generate_keys_when_using_existing_branch() { + ComponentDto projectDto = dbTester.components().insertPublicProject(); + String branchName = randomAlphanumeric(248); + ComponentDto componentDto = dbTester.components().insertProjectBranch(projectDto, b -> b.setKey(branchName)); + Branch branch = mock(Branch.class); + when(branch.getName()).thenReturn(branchName); + when(branch.isMain()).thenReturn(false); + when(branch.generateKey(any(), any())).thenReturn(componentDto.getKey()); + analysisMetadataHolder.setRootComponentRef(ROOT_REF) + .setAnalysisDate(ANALYSIS_DATE) + .setProject(Project.from(projectDto)) + .setBranch(branch); + BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder); + reportReader.putComponent(component(ROOT_REF, PROJECT, componentDto.getKey())); + + underTest.execute(new TestComputationStepContext()); + + verifyComponentByRef(ROOT_REF, componentDto.getKey(), analysisMetadataHolder.getProject().getName(), componentDto.uuid()); + } + + @Test + public void generate_keys_when_using_main_branch() { + setAnalysisMetadataHolder(); + BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); + + underTest.execute(new TestComputationStepContext()); + + verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName(), null); + verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1); + verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, null); + } + + @Test + public void compute_keys_and_uuids_on_project_having_module_and_directory() { + setAnalysisMetadataHolder(); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); + reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2)); + + underTest.execute(new TestComputationStepContext()); + + verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName()); + verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, "dir1"); + verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1); + verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_2, "dir2"); + verifyComponentByRef(FILE_2_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_2, "File2.java"); + } + + @Test + public void compute_keys_and_uuids_on_multi_modules() { + setAnalysisMetadataHolder(); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); + reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); + + underTest.execute(new TestComputationStepContext()); + + verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName()); + verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1); + verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1); + } + + @Test + public void set_no_base_project_snapshot_when_no_snapshot() { + setAnalysisMetadataHolder(); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); + underTest.execute(new TestComputationStepContext()); + + assertThat(analysisMetadataHolder.isFirstAnalysis()).isTrue(); + } + + @Test + public void set_no_base_project_snapshot_when_no_last_snapshot() { + setAnalysisMetadataHolder(); + ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY)); + insertSnapshot(newAnalysis(project).setLast(false)); + + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); + underTest.execute(new TestComputationStepContext()); + + assertThat(analysisMetadataHolder.isFirstAnalysis()).isTrue(); + } + + @Test + public void set_base_project_snapshot_when_last_snapshot_exist() { + setAnalysisMetadataHolder(); + ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY)); + insertSnapshot(newAnalysis(project).setLast(true)); + + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); + underTest.execute(new TestComputationStepContext()); + + assertThat(analysisMetadataHolder.isFirstAnalysis()).isFalse(); + } + + @Test + public void set_projectVersion_to_not_provided_when_not_set_on_first_analysis() { + setAnalysisMetadataHolder(); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion()).isEqualTo("not provided"); + } + + @Test + @UseDataProvider("oneParameterNullNonNullCombinations") + public void set_projectVersion_to_previous_analysis_when_not_set(@Nullable String previousAnalysisProjectVersion) { + setAnalysisMetadataHolder(); + ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY)); + insertSnapshot(newAnalysis(project).setProjectVersion(previousAnalysisProjectVersion).setLast(true)); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); + + underTest.execute(new TestComputationStepContext()); + + String projectVersion = treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion(); + if (previousAnalysisProjectVersion == null) { + assertThat(projectVersion).isEqualTo("not provided"); + } else { + assertThat(projectVersion).isEqualTo(previousAnalysisProjectVersion); + } + } + + @Test + public void set_projectVersion_when_it_is_set_on_first_analysis() { + String scannerProjectVersion = randomAlphabetic(12); + setAnalysisMetadataHolder(); + reportReader.setMetadata(createReportMetadata(scannerProjectVersion, NO_SCANNER_BUILD_STRING)); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion()) + .isEqualTo(scannerProjectVersion); + } + + @Test + @UseDataProvider("oneParameterNullNonNullCombinations") + public void set_projectVersion_when_it_is_set_on_later_analysis(@Nullable String previousAnalysisProjectVersion) { + String scannerProjectVersion = randomAlphabetic(12); + setAnalysisMetadataHolder(); + reportReader.setMetadata(createReportMetadata(scannerProjectVersion, NO_SCANNER_BUILD_STRING)); + ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY)); + insertSnapshot(newAnalysis(project).setProjectVersion(previousAnalysisProjectVersion).setLast(true)); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion()) + .isEqualTo(scannerProjectVersion); + } + + @Test + @UseDataProvider("oneParameterNullNonNullCombinations") + public void set_buildString(@Nullable String buildString) { + String projectVersion = randomAlphabetic(7); + setAnalysisMetadataHolder(); + reportReader.setMetadata(createReportMetadata(projectVersion, buildString)); + reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getBuildString()).isEqualTo(Optional.ofNullable(buildString)); + } + + @DataProvider + public static Object[][] oneParameterNullNonNullCombinations() { + return new Object[][] { + {null}, + {randomAlphabetic(7)} + }; + } + + private void verifyComponent(Component component, Component.Type type, @Nullable Integer componentRef, int size) { + assertThat(component.getType()).isEqualTo(type); + assertThat(component.getReportAttributes().getRef()).isEqualTo(componentRef); + assertThat(component.getChildren()).hasSize(size); + } + + private void verifyComponentByRef(int ref, String key, String shortName) { + verifyComponentByRef(ref, key, shortName, null); + } + + private void verifyComponentByKey(String key, String shortName) { + verifyComponentByKey(key, shortName, null); + } + + private void verifyComponentByKey(String key, String shortName, @Nullable String uuid) { + Map componentsByKey = indexAllComponentsInTreeByKey(treeRootHolder.getRoot()); + Component component = componentsByKey.get(key); + assertThat(component.getKey()).isEqualTo(key); + assertThat(component.getReportAttributes().getRef()).isNull(); + assertThat(component.getShortName()).isEqualTo(shortName); + if (uuid != null) { + assertThat(component.getUuid()).isEqualTo(uuid); + } else { + assertThat(component.getUuid()).isNotNull(); + } + } + + private void verifyComponentByRef(int ref, String key, String shortName, @Nullable String uuid) { + Map componentsByRef = indexAllComponentsInTreeByRef(treeRootHolder.getRoot()); + Component component = componentsByRef.get(ref); + assertThat(component.getKey()).isEqualTo(key); + assertThat(component.getShortName()).isEqualTo(shortName); + if (uuid != null) { + assertThat(component.getUuid()).isEqualTo(uuid); + } else { + assertThat(component.getUuid()).isNotNull(); + } + } + + private static ScannerReport.Component component(int componentRef, ComponentType componentType, String key, int... children) { + return component(componentRef, componentType, key, FileStatus.CHANGED, null, children); + } + + private static ScannerReport.Component unchangedComponentWithPath(int componentRef, ComponentType componentType, String path, int... children) { + return component(componentRef, componentType, REPORT_PROJECT_KEY + ":" + path, FileStatus.SAME, path, children); + } + + private static ScannerReport.Component componentWithPath(int componentRef, ComponentType componentType, String path, int... children) { + return component(componentRef, componentType, REPORT_PROJECT_KEY + ":" + path, FileStatus.CHANGED, path, children); + } + + private static ScannerReport.Component component(int componentRef, ComponentType componentType, String key, FileStatus status, @Nullable String path, int... children) { + ScannerReport.Component.Builder builder = ScannerReport.Component.newBuilder() + .setType(componentType) + .setRef(componentRef) + .setName(key) + .setStatus(status) + .setLines(1) + .setKey(key); + if (path != null) { + builder.setProjectRelativePath(path); + } + for (int child : children) { + builder.addChildRef(child); + } + return builder.build(); + } + + private static Map indexAllComponentsInTreeByRef(Component root) { + Map componentsByRef = new HashMap<>(); + feedComponentByRef(root, componentsByRef); + return componentsByRef; + } + + private static Map indexAllComponentsInTreeByKey(Component root) { + Map componentsByKey = new HashMap<>(); + feedComponentByKey(root, componentsByKey); + return componentsByKey; + } + + private static void feedComponentByKey(Component component, Map map) { + map.put(component.getKey(), component); + for (Component child : component.getChildren()) { + feedComponentByKey(child, map); + } + } + + private static void feedComponentByRef(Component component, Map map) { + if (component.getReportAttributes().getRef() != null) { + map.put(component.getReportAttributes().getRef(), component); + } + for (Component child : component.getChildren()) { + feedComponentByRef(child, map); + } + } + + private ComponentDto insertComponent(ComponentDto component) { + return dbTester.components().insertComponent(component); + } + + private SnapshotDto insertSnapshot(SnapshotDto snapshot) { + dbClient.snapshotDao().insert(dbTester.getSession(), snapshot); + dbTester.getSession().commit(); + return snapshot; + } + + private void setAnalysisMetadataHolder() { + setAnalysisMetadataHolder(false); + } + + private void setAnalysisMetadataHolder(boolean isPr) { + Branch branch = isPr ? new PrBranch(DEFAULT_MAIN_BRANCH_NAME) : new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME); + analysisMetadataHolder.setRootComponentRef(ROOT_REF) + .setAnalysisDate(ANALYSIS_DATE) + .setBranch(branch) + .setProject(Project.from(newPrivateProjectDto().setKey(REPORT_PROJECT_KEY).setName(REPORT_PROJECT_KEY))); + } + + public static ScannerReport.Metadata createReportMetadata(@Nullable String projectVersion, @Nullable String buildString) { + ScannerReport.Metadata.Builder builder = ScannerReport.Metadata.newBuilder() + .setProjectKey(REPORT_PROJECT_KEY) + .setRootComponentRef(ROOT_REF); + ofNullable(projectVersion).ifPresent(builder::setProjectVersion); + ofNullable(buildString).ifPresent(builder::setBuildString); + return builder.build(); + } + + private static class PrBranch extends DefaultBranchImpl { + public PrBranch(String branch) { + super(branch); + } + + @Override + public BranchType getType() { + return BranchType.PULL_REQUEST; + } + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepIT.java new file mode 100644 index 00000000000..1816523c111 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepIT.java @@ -0,0 +1,346 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.step; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.projectanalysis.analysis.Analysis; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.FileAttributes; +import org.sonar.ce.task.projectanalysis.component.ReportComponent; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; +import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolder; +import org.sonar.ce.task.projectanalysis.duplication.IntegrateCrossProjectDuplications; +import org.sonar.ce.task.step.ComputationStep; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.ComponentTesting; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.component.SnapshotTesting; +import org.sonar.db.duplication.DuplicationUnitDto; +import org.sonar.duplications.block.Block; +import org.sonar.duplications.block.ByteArray; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE; +import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT; + +public class LoadCrossProjectDuplicationsRepositoryStepIT { + + + private static final String XOO_LANGUAGE = "xoo"; + private static final int PROJECT_REF = 1; + private static final int FILE_REF = 2; + private static final String CURRENT_FILE_KEY = "FILE_KEY"; + + private static final Component CURRENT_FILE = ReportComponent.builder(FILE, FILE_REF) + .setKey(CURRENT_FILE_KEY) + .setFileAttributes(new FileAttributes(false, XOO_LANGUAGE, 1)) + .build(); + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot( + ReportComponent.builder(PROJECT, PROJECT_REF) + .addChildren(CURRENT_FILE).build()); + + @Rule + public BatchReportReaderRule batchReportReader = new BatchReportReaderRule(); + + @Rule + public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); + + private CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder = mock(CrossProjectDuplicationStatusHolder.class); + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private DbClient dbClient = dbTester.getDbClient(); + private DbSession dbSession = dbTester.getSession(); + private IntegrateCrossProjectDuplications integrateCrossProjectDuplications = mock(IntegrateCrossProjectDuplications.class); + private Analysis baseProjectAnalysis; + + private ComputationStep underTest = new LoadCrossProjectDuplicationsRepositoryStep(treeRootHolder, batchReportReader, analysisMetadataHolder, crossProjectDuplicationStatusHolder, + integrateCrossProjectDuplications, dbClient); + + @Before + public void setUp() { + ComponentDto project = ComponentTesting.newPrivateProjectDto(); + dbTester.components().insertComponent(project); + SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(project); + dbClient.snapshotDao().insert(dbSession, projectSnapshot); + dbSession.commit(); + + baseProjectAnalysis = new Analysis.Builder() + .setUuid(projectSnapshot.getUuid()) + .setCreatedAt(projectSnapshot.getCreatedAt()) + .build(); + } + + @Test + public void call_compute_cpd_on_one_duplication() { + when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); + analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); + + ComponentDto otherProject = createProject("OTHER_PROJECT_KEY"); + SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject); + + ComponentDto otherFile = createFile("OTHER_FILE_KEY", otherProject); + + String hash = "a8998353e96320ec"; + DuplicationUnitDto duplicate = new DuplicationUnitDto() + .setHash(hash) + .setStartLine(40) + .setEndLine(55) + .setIndexInFile(0) + .setAnalysisUuid(otherProjectSnapshot.getUuid()) + .setComponentUuid(otherFile.uuid()); + dbClient.duplicationDao().insert(dbSession, duplicate); + dbSession.commit(); + + ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder() + .setHash(hash) + .setStartLine(30) + .setEndLine(45) + .setStartTokenIndex(0) + .setEndTokenIndex(10) + .build(); + batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock)); + + underTest.execute(new TestComputationStepContext()); + + verify(integrateCrossProjectDuplications).computeCpd(CURRENT_FILE, + singletonList( + new Block.Builder() + .setResourceId(CURRENT_FILE_KEY) + .setBlockHash(new ByteArray(hash)) + .setIndexInFile(0) + .setLines(originBlock.getStartLine(), originBlock.getEndLine()) + .setUnit(originBlock.getStartTokenIndex(), originBlock.getEndTokenIndex()) + .build()), + singletonList( + new Block.Builder() + .setResourceId(otherFile.getKey()) + .setBlockHash(new ByteArray(hash)) + .setIndexInFile(duplicate.getIndexInFile()) + .setLines(duplicate.getStartLine(), duplicate.getEndLine()) + .build())); + } + + @Test + public void call_compute_cpd_on_many_duplication() { + when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); + analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); + + ComponentDto otherProject = createProject("OTHER_PROJECT_KEY"); + SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject); + + ComponentDto otherFile = createFile("OTHER_FILE_KEY", otherProject); + + ScannerReport.CpdTextBlock originBlock1 = ScannerReport.CpdTextBlock.newBuilder() + .setHash("a8998353e96320ec") + .setStartLine(30) + .setEndLine(45) + .setStartTokenIndex(0) + .setEndTokenIndex(10) + .build(); + ScannerReport.CpdTextBlock originBlock2 = ScannerReport.CpdTextBlock.newBuilder() + .setHash("b1234353e96320ff") + .setStartLine(10) + .setEndLine(25) + .setStartTokenIndex(5) + .setEndTokenIndex(15) + .build(); + batchReportReader.putDuplicationBlocks(FILE_REF, asList(originBlock1, originBlock2)); + + DuplicationUnitDto duplicate1 = new DuplicationUnitDto() + .setHash(originBlock1.getHash()) + .setStartLine(40) + .setEndLine(55) + .setIndexInFile(0) + .setAnalysisUuid(otherProjectSnapshot.getUuid()) + .setComponentUuid(otherFile.uuid()); + + DuplicationUnitDto duplicate2 = new DuplicationUnitDto() + .setHash(originBlock2.getHash()) + .setStartLine(20) + .setEndLine(35) + .setIndexInFile(1) + .setAnalysisUuid(otherProjectSnapshot.getUuid()) + .setComponentUuid(otherFile.uuid()); + dbClient.duplicationDao().insert(dbSession, duplicate1); + dbClient.duplicationDao().insert(dbSession, duplicate2); + dbSession.commit(); + + underTest.execute(new TestComputationStepContext()); + + ArgumentCaptor> originBlocks = ArgumentCaptor.forClass(List.class); + ArgumentCaptor> duplicationBlocks = ArgumentCaptor.forClass(List.class); + + verify(integrateCrossProjectDuplications).computeCpd(eq(CURRENT_FILE), originBlocks.capture(), duplicationBlocks.capture()); + + Map originBlocksByIndex = blocksByIndexInFile(originBlocks.getValue()); + assertThat(originBlocksByIndex).containsExactly( + Map.entry(0, new Block.Builder() + .setResourceId(CURRENT_FILE_KEY) + .setBlockHash(new ByteArray(originBlock1.getHash())) + .setIndexInFile(0) + .setLines(originBlock1.getStartLine(), originBlock1.getEndLine()) + .setUnit(originBlock1.getStartTokenIndex(), originBlock1.getEndTokenIndex()) + .build()), + Map.entry(1, new Block.Builder() + .setResourceId(CURRENT_FILE_KEY) + .setBlockHash(new ByteArray(originBlock2.getHash())) + .setIndexInFile(1) + .setLines(originBlock2.getStartLine(), originBlock2.getEndLine()) + .setUnit(originBlock2.getStartTokenIndex(), originBlock2.getEndTokenIndex()) + .build())); + + Map duplicationBlocksByIndex = blocksByIndexInFile(duplicationBlocks.getValue()); + assertThat(duplicationBlocksByIndex).containsExactly( + Map.entry(0, new Block.Builder() + .setResourceId(otherFile.getKey()) + .setBlockHash(new ByteArray(originBlock1.getHash())) + .setIndexInFile(duplicate1.getIndexInFile()) + .setLines(duplicate1.getStartLine(), duplicate1.getEndLine()) + .build()), + Map.entry(1, new Block.Builder() + .setResourceId(otherFile.getKey()) + .setBlockHash(new ByteArray(originBlock2.getHash())) + .setIndexInFile(duplicate2.getIndexInFile()) + .setLines(duplicate2.getStartLine(), duplicate2.getEndLine()) + .build())); + } + + @Test + public void nothing_to_do_when_cross_project_duplication_is_disabled() { + when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(false); + analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); + + ComponentDto otherProject = createProject("OTHER_PROJECT_KEY"); + SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject); + + ComponentDto otherFIle = createFile("OTHER_FILE_KEY", otherProject); + + String hash = "a8998353e96320ec"; + DuplicationUnitDto duplicate = new DuplicationUnitDto() + .setHash(hash) + .setStartLine(40) + .setEndLine(55) + .setIndexInFile(0) + .setAnalysisUuid(otherProjectSnapshot.getUuid()) + .setComponentUuid(otherFIle.uuid()); + dbClient.duplicationDao().insert(dbSession, duplicate); + dbSession.commit(); + + ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder() + .setHash(hash) + .setStartLine(30) + .setEndLine(45) + .setStartTokenIndex(0) + .setEndTokenIndex(10) + .build(); + batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock)); + + underTest.execute(new TestComputationStepContext()); + + verifyNoInteractions(integrateCrossProjectDuplications); + } + + @Test + public void nothing_to_do_when_no_cpd_text_blocks_found() { + when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); + analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); + + batchReportReader.putDuplicationBlocks(FILE_REF, Collections.emptyList()); + + underTest.execute(new TestComputationStepContext()); + + verifyNoInteractions(integrateCrossProjectDuplications); + } + + @Test + public void nothing_to_do_when_cpd_text_blocks_exists_but_no_duplicated_found() { + when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); + analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); + + ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder() + .setHash("a8998353e96320ec") + .setStartLine(30) + .setEndLine(45) + .setStartTokenIndex(0) + .setEndTokenIndex(10) + .build(); + batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock)); + + underTest.execute(new TestComputationStepContext()); + + verifyNoInteractions(integrateCrossProjectDuplications); + } + + private ComponentDto createProject(String projectKey) { + ComponentDto project = ComponentTesting.newPrivateProjectDto().setKey(projectKey); + return dbTester.components().insertComponent(project); + } + + private SnapshotDto createProjectSnapshot(ComponentDto project) { + SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(project); + dbClient.snapshotDao().insert(dbSession, projectSnapshot); + dbSession.commit(); + return projectSnapshot; + } + + private ComponentDto createFile(String fileKey, ComponentDto project) { + ComponentDto file = ComponentTesting.newFileDto(project, null) + .setKey(fileKey) + .setLanguage(XOO_LANGUAGE); + dbClient.componentDao().insert(dbSession, file); + dbSession.commit(); + return file; + } + + private static Map blocksByIndexInFile(List blocks) { + Map blocksByIndexInFile = new HashMap<>(); + for (Block block : blocks) { + blocksByIndexInFile.put(block.getIndexInFile(), block); + } + return blocksByIndexInFile; + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepIT.java new file mode 100644 index 00000000000..0430fa3d8a3 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepIT.java @@ -0,0 +1,138 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.step; + +import com.google.common.collect.ImmutableList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.core.util.CloseableIterator; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.DbTester; +import org.sonar.db.component.AnalysisPropertyDto; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PersistAnalysisPropertiesStepIT { + private static final String SNAPSHOT_UUID = randomAlphanumeric(40); + private static final String SMALL_VALUE1 = randomAlphanumeric(50); + private static final String SMALL_VALUE2 = randomAlphanumeric(50); + private static final String SMALL_VALUE3 = randomAlphanumeric(50); + private static final String BIG_VALUE = randomAlphanumeric(5000); + private static final String VALUE_PREFIX_FOR_PR_PROPERTIES = "pr_"; + private static final List PROPERTIES = Arrays.asList( + newContextProperty("key1", "value1"), + newContextProperty("key2", "value1"), + newContextProperty("sonar.analysis", SMALL_VALUE1), + newContextProperty("sonar.analysis.branch", SMALL_VALUE2), + newContextProperty("sonar.analysis.empty_string", ""), + newContextProperty("sonar.analysis.big_value", BIG_VALUE), + newContextProperty("sonar.analysis.", SMALL_VALUE3), + newContextProperty("sonar.pullrequest", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE1), + newContextProperty("sonar.pullrequest.branch", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE2), + newContextProperty("sonar.pullrequest.empty_string", ""), + newContextProperty("sonar.pullrequest.big_value", VALUE_PREFIX_FOR_PR_PROPERTIES + BIG_VALUE), + newContextProperty("sonar.pullrequest.", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE3)); + private static final String SCM_REV_ID = "sha1"; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private BatchReportReader batchReportReader = mock(BatchReportReader.class); + private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class); + private PersistAnalysisPropertiesStep underTest = new PersistAnalysisPropertiesStep(dbTester.getDbClient(), analysisMetadataHolder, batchReportReader, + UuidFactoryFast.getInstance()); + + @Test + public void persist_should_stores_sonarDotAnalysisDot_and_sonarDotPullRequestDot_properties() { + when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.from(PROPERTIES.iterator())); + when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID); + when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(dbTester.countRowsOfTable("analysis_properties")).isEqualTo(8); + List propertyDtos = dbTester.getDbClient() + .analysisPropertiesDao().selectByAnalysisUuid(dbTester.getSession(), SNAPSHOT_UUID); + + assertThat(propertyDtos) + .extracting(AnalysisPropertyDto::getAnalysisUuid, AnalysisPropertyDto::getKey, AnalysisPropertyDto::getValue) + .containsExactlyInAnyOrder( + tuple(SNAPSHOT_UUID, "sonar.analysis.branch", SMALL_VALUE2), + tuple(SNAPSHOT_UUID, "sonar.analysis.empty_string", ""), + tuple(SNAPSHOT_UUID, "sonar.analysis.big_value", BIG_VALUE), + tuple(SNAPSHOT_UUID, "sonar.analysis.", SMALL_VALUE3), + tuple(SNAPSHOT_UUID, "sonar.pullrequest.branch", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE2), + tuple(SNAPSHOT_UUID, "sonar.pullrequest.empty_string", ""), + tuple(SNAPSHOT_UUID, "sonar.pullrequest.big_value", VALUE_PREFIX_FOR_PR_PROPERTIES + BIG_VALUE), + tuple(SNAPSHOT_UUID, "sonar.pullrequest.", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE3)); + } + + @Test + public void persist_filtering_of_properties_is_case_sensitive() { + when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID)); + when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.from(ImmutableList.of( + newContextProperty("sonar.ANALYSIS.foo", "foo"), + newContextProperty("sonar.anaLysis.bar", "bar"), + newContextProperty("sonar.anaLYSIS.doo", "doh"), + newContextProperty("sonar.PULLREQUEST.foo", "foo"), + newContextProperty("sonar.pullRequest.bar", "bar"), + newContextProperty("sonar.pullREQUEST.doo", "doh")).iterator())); + when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID); + + underTest.execute(new TestComputationStepContext()); + + assertThat(dbTester.countRowsOfTable("analysis_properties")).isZero(); + } + + @Test + public void persist_should_store_nothing_if_there_are_no_context_properties() { + when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID)); + when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.emptyCloseableIterator()); + when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID); + + underTest.execute(new TestComputationStepContext()); + + assertThat(dbTester.countRowsOfTable("analysis_properties")).isZero(); + } + + @Test + public void verify_description_value() { + assertThat(underTest.getDescription()).isEqualTo("Persist analysis properties"); + } + + private static ScannerReport.ContextProperty newContextProperty(String key, String value) { + return ScannerReport.ContextProperty.newBuilder() + .setKey(key) + .setValue(value) + .build(); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepIT.java new file mode 100644 index 00000000000..003826e72ab --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepIT.java @@ -0,0 +1,161 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.step; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.projectanalysis.analysis.Analysis; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.ReportComponent; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; +import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolder; +import org.sonar.ce.task.step.ComputationStep; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.scanner.protocol.output.ScannerReport; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class PersistCrossProjectDuplicationIndexStepIT { + + private static final int FILE_1_REF = 2; + private static final int FILE_2_REF = 3; + private static final String FILE_2_UUID = "file2"; + + private static final Component FILE_1 = ReportComponent.builder(Component.Type.FILE, FILE_1_REF).build(); + private static final Component FILE_2 = ReportComponent.builder(Component.Type.FILE, FILE_2_REF) + .setStatus(Component.Status.SAME).setUuid(FILE_2_UUID).build(); + + private static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1) + .addChildren(FILE_1) + .addChildren(FILE_2) + .build(); + + private static final ScannerReport.CpdTextBlock CPD_TEXT_BLOCK = ScannerReport.CpdTextBlock.newBuilder() + .setHash("a8998353e96320ec") + .setStartLine(30) + .setEndLine(45) + .build(); + private static final String ANALYSIS_UUID = "analysis uuid"; + private static final String BASE_ANALYSIS_UUID = "base analysis uuid"; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public BatchReportReaderRule reportReader = new BatchReportReaderRule(); + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT); + @Rule + public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); + + private CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder = mock(CrossProjectDuplicationStatusHolder.class); + private Analysis baseAnalysis = mock(Analysis.class); + private DbClient dbClient = dbTester.getDbClient(); + + private ComputationStep underTest; + + @Before + public void setUp() { + when(baseAnalysis.getUuid()).thenReturn(BASE_ANALYSIS_UUID); + analysisMetadataHolder.setUuid(ANALYSIS_UUID); + analysisMetadataHolder.setBaseAnalysis(baseAnalysis); + underTest = new PersistCrossProjectDuplicationIndexStep(crossProjectDuplicationStatusHolder, dbClient, treeRootHolder, analysisMetadataHolder, reportReader); + } + + @Test + public void persist_cpd_text_block() { + when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); + reportReader.putDuplicationBlocks(FILE_1_REF, singletonList(CPD_TEXT_BLOCK)); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + Map dto = dbTester.selectFirst("select HASH, START_LINE, END_LINE, INDEX_IN_FILE, COMPONENT_UUID, ANALYSIS_UUID from duplications_index"); + assertThat(dto) + .containsEntry("HASH", CPD_TEXT_BLOCK.getHash()) + .containsEntry("START_LINE", 30L) + .containsEntry("END_LINE", 45L) + .containsEntry("INDEX_IN_FILE", 0L) + .containsEntry("COMPONENT_UUID", FILE_1.getUuid()) + .containsEntry("ANALYSIS_UUID", ANALYSIS_UUID); + context.getStatistics().assertValue("inserts", 1); + } + + @Test + public void persist_many_cpd_text_blocks() { + when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); + reportReader.putDuplicationBlocks(FILE_1_REF, Arrays.asList( + CPD_TEXT_BLOCK, + ScannerReport.CpdTextBlock.newBuilder() + .setHash("b1234353e96320ff") + .setStartLine(20) + .setEndLine(15) + .build())); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + List> dtos = dbTester.select("select HASH, START_LINE, END_LINE, INDEX_IN_FILE, COMPONENT_UUID, ANALYSIS_UUID from duplications_index"); + assertThat(dtos).extracting("HASH").containsOnly(CPD_TEXT_BLOCK.getHash(), "b1234353e96320ff"); + assertThat(dtos).extracting("START_LINE").containsOnly(30L, 20L); + assertThat(dtos).extracting("END_LINE").containsOnly(45L, 15L); + assertThat(dtos).extracting("INDEX_IN_FILE").containsOnly(0L, 1L); + assertThat(dtos).extracting("COMPONENT_UUID").containsOnly(FILE_1.getUuid()); + assertThat(dtos).extracting("ANALYSIS_UUID").containsOnly(ANALYSIS_UUID); + context.getStatistics().assertValue("inserts", 2); + } + + @Test + public void nothing_to_persist_when_no_cpd_text_blocks_in_report() { + when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); + reportReader.putDuplicationBlocks(FILE_1_REF, Collections.emptyList()); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(dbTester.countRowsOfTable("duplications_index")).isZero(); + context.getStatistics().assertValue("inserts", 0); + } + + @Test + public void nothing_to_do_when_cross_project_duplication_is_disabled() { + when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(false); + reportReader.putDuplicationBlocks(FILE_1_REF, singletonList(CPD_TEXT_BLOCK)); + + TestComputationStepContext context = new TestComputationStepContext(); + underTest.execute(context); + + assertThat(dbTester.countRowsOfTable("duplications_index")).isZero(); + context.getStatistics().assertValue("inserts", null); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepIT.java new file mode 100644 index 00000000000..b900fc3368e --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepIT.java @@ -0,0 +1,237 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.step; + +import com.google.common.collect.ImmutableList; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.ReportComponent; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; +import org.sonar.ce.task.projectanalysis.event.Event; +import org.sonar.ce.task.projectanalysis.event.EventRepository; +import org.sonar.ce.task.step.ComputationStep; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.core.util.UuidFactory; +import org.sonar.core.util.UuidFactoryImpl; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.event.EventDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY; +import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE; +import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT; +import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; +import static org.sonar.db.event.EventDto.CATEGORY_ALERT; +import static org.sonar.db.event.EventDto.CATEGORY_PROFILE; +import static org.sonar.db.event.EventDto.CATEGORY_VERSION; + +public class PersistEventsStepIT extends BaseStepTest { + + private static final long NOW = 1225630680000L; + private static final ReportComponent ROOT = builder(PROJECT, 1) + .setUuid("ABCD") + .setProjectVersion("version_1") + .addChildren( + builder(DIRECTORY, 2) + .setUuid("BCDE") + .addChildren( + builder(DIRECTORY, 3) + .setUuid("Q") + .addChildren( + builder(FILE, 4) + .setUuid("Z") + .build()) + .build()) + .build()) + .build(); + private static final String ANALYSIS_UUID = "uuid_1"; + + System2 system2 = mock(System2.class); + + @Rule + public DbTester dbTester = DbTester.create(system2); + @Rule + public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); + + private Date someDate = new Date(150000000L); + + private EventRepository eventRepository = mock(EventRepository.class); + private UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE; + + private PersistEventsStep underTest; + + @Before + public void setup() { + analysisMetadataHolder.setAnalysisDate(someDate.getTime()).setUuid(ANALYSIS_UUID); + underTest = new PersistEventsStep(dbTester.getDbClient(), system2, treeRootHolder, analysisMetadataHolder, eventRepository, uuidFactory); + when(eventRepository.getEvents(any(Component.class))).thenReturn(Collections.emptyList()); + } + + @Override + protected ComputationStep step() { + return underTest; + } + + @Test + public void create_version_event() { + when(system2.now()).thenReturn(NOW); + Component project = builder(PROJECT, 1) + .setUuid("ABCD") + .setProjectVersion("1.0") + .addChildren( + builder(DIRECTORY, 2) + .setUuid("BCDE") + .addChildren( + builder(DIRECTORY, 3) + .setUuid("Q") + .addChildren( + builder(FILE, 4) + .setUuid("Z") + .build()) + .build()) + .build()) + .build(); + treeRootHolder.setRoot(project); + + underTest.execute(new TestComputationStepContext()); + + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isOne(); + List eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid()); + assertThat(eventDtos).hasSize(1); + EventDto eventDto = eventDtos.iterator().next(); + assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid()); + assertThat(eventDto.getName()).isEqualTo("1.0"); + assertThat(eventDto.getDescription()).isNull(); + assertThat(eventDto.getCategory()).isEqualTo(CATEGORY_VERSION); + assertThat(eventDto.getData()).isNull(); + assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate()); + assertThat(eventDto.getCreatedAt()).isEqualTo(NOW); + } + + @Test + public void persist_alert_events_on_root() { + when(system2.now()).thenReturn(NOW); + treeRootHolder.setRoot(ROOT); + Event alert = Event.createAlert("Failed", null, "Open issues > 0"); + when(eventRepository.getEvents(ROOT)).thenReturn(ImmutableList.of(alert)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(2); + List eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid()); + assertThat(eventDtos) + .extracting(EventDto::getCategory) + .containsOnly(CATEGORY_ALERT, CATEGORY_VERSION); + EventDto eventDto = eventDtos.stream().filter(t -> CATEGORY_ALERT.equals(t.getCategory())).findAny().get(); + assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid()); + assertThat(eventDto.getName()).isEqualTo(alert.getName()); + assertThat(eventDto.getDescription()).isEqualTo(alert.getDescription()); + assertThat(eventDto.getCategory()).isEqualTo(CATEGORY_ALERT); + assertThat(eventDto.getData()).isNull(); + assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate()); + assertThat(eventDto.getCreatedAt()).isEqualTo(NOW); + } + + @Test + public void persist_profile_events_on_root() { + when(system2.now()).thenReturn(NOW); + treeRootHolder.setRoot(ROOT); + Event profile = Event.createProfile("foo", null, "bar"); + when(eventRepository.getEvents(ROOT)).thenReturn(ImmutableList.of(profile)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(2); + List eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid()); + assertThat(eventDtos) + .extracting(EventDto::getCategory) + .containsOnly(CATEGORY_PROFILE, CATEGORY_VERSION); + EventDto eventDto = eventDtos.stream().filter(t -> CATEGORY_PROFILE.equals(t.getCategory())).findAny().get(); + assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid()); + assertThat(eventDto.getName()).isEqualTo(profile.getName()); + assertThat(eventDto.getDescription()).isEqualTo(profile.getDescription()); + assertThat(eventDto.getCategory()).isEqualTo(EventDto.CATEGORY_PROFILE); + assertThat(eventDto.getData()).isNull(); + assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate()); + assertThat(eventDto.getCreatedAt()).isEqualTo(NOW); + } + + @Test + public void keep_one_event_by_version() { + ComponentDto projectDto = dbTester.components().insertPublicProject(); + EventDto[] existingEvents = new EventDto[] { + dbTester.events().insertEvent(newVersionEventDto(projectDto, 120_000_000L, "1.3-SNAPSHOT")), + dbTester.events().insertEvent(newVersionEventDto(projectDto, 130_000_000L, "1.4")), + dbTester.events().insertEvent(newVersionEventDto(projectDto, 140_000_000L, "1.5-SNAPSHOT")) + }; + + Component project = builder(PROJECT, 1) + .setUuid(projectDto.uuid()) + .setProjectVersion("1.5-SNAPSHOT") + .addChildren( + builder(DIRECTORY, 2) + .setUuid("BCDE") + .addChildren( + builder(DIRECTORY, 3) + .setUuid("Q") + .addChildren( + builder(FILE, 4) + .setUuid("Z") + .build()) + .build()) + .build()) + .build(); + treeRootHolder.setRoot(project); + + underTest.execute(new TestComputationStepContext()); + + assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(3); + List eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), projectDto.uuid()); + assertThat(eventDtos).hasSize(3); + assertThat(eventDtos) + .extracting(EventDto::getName) + .containsOnly("1.3-SNAPSHOT", "1.4", "1.5-SNAPSHOT"); + assertThat(eventDtos) + .extracting(EventDto::getUuid) + .contains(existingEvents[0].getUuid(), existingEvents[1].getUuid()) + .doesNotContain(existingEvents[2].getUuid()); + } + + private EventDto newVersionEventDto(ComponentDto project, long date, String name) { + return new EventDto().setUuid(uuidFactory.create()).setComponentUuid(project.uuid()) + .setAnalysisUuid("analysis_uuid") + .setCategory(CATEGORY_VERSION) + .setName(name).setDate(date).setCreatedAt(date); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepIT.java new file mode 100644 index 00000000000..1ee3b62296f --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepIT.java @@ -0,0 +1,109 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.taskprocessor; + +import java.util.Optional; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.ce.task.CeTask; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.db.component.BranchType.BRANCH; + +public class IgnoreOrphanBranchStepIT { + + private String BRANCH_UUID = "branch_uuid"; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private CeTask.Component component = new CeTask.Component(BRANCH_UUID, "component key", "component name"); + private CeTask ceTask = new CeTask.Builder() + .setType("type") + .setUuid("uuid") + .setComponent(component) + .setMainComponent(component) + .build(); + + private DbClient dbClient = dbTester.getDbClient(); + private IgnoreOrphanBranchStep underTest = new IgnoreOrphanBranchStep(ceTask, dbClient); + + @Test + public void execute() { + BranchDto branchDto = new BranchDto() + .setBranchType(BRANCH) + .setKey("branchName") + .setUuid(BRANCH_UUID) + .setProjectUuid("project_uuid") + .setNeedIssueSync(true); + dbClient.branchDao().insert(dbTester.getSession(), branchDto); + dbTester.commit(); + + underTest.execute(() -> null); + + Optional branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), BRANCH_UUID); + assertThat(branch.get().isNeedIssueSync()).isFalse(); + assertThat(branch.get().isExcludeFromPurge()).isFalse(); + } + + @Test + public void execute_on_already_indexed_branch() { + BranchDto branchDto = new BranchDto() + .setBranchType(BRANCH) + .setKey("branchName") + .setUuid(BRANCH_UUID) + .setProjectUuid("project_uuid") + .setNeedIssueSync(false); + dbClient.branchDao().insert(dbTester.getSession(), branchDto); + dbTester.commit(); + + underTest.execute(() -> null); + + Optional branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), BRANCH_UUID); + assertThat(branch.get().isNeedIssueSync()).isFalse(); + assertThat(branch.get().isExcludeFromPurge()).isFalse(); + } + + @Test + public void fail_if_missing_main_component_in_task() { + CeTask ceTask = new CeTask.Builder() + .setType("type") + .setUuid("uuid") + .setComponent(null) + .setMainComponent(null) + .build(); + IgnoreOrphanBranchStep underTest = new IgnoreOrphanBranchStep(ceTask, dbClient); + + assertThatThrownBy(() -> underTest.execute(() -> null)) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessage("main component not found in task"); + } + + @Test + public void verify_step_description() { + assertThat(underTest.getDescription()).isEqualTo("Ignore orphan component"); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepIT.java new file mode 100644 index 00000000000..4b3d12f2be8 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepIT.java @@ -0,0 +1,143 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectexport.component; + +import com.google.common.collect.ImmutableSet; +import com.sonarsource.governance.projectdump.protobuf.ProjectDump; +import java.util.Date; +import java.util.List; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.resources.Qualifiers; +import org.sonar.api.resources.Scopes; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.ce.task.projectexport.steps.DumpElement; +import org.sonar.ce.task.projectexport.steps.FakeDumpWriter; +import org.sonar.ce.task.projectexport.steps.ProjectHolder; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.tuple; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT; +import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR; + +public class ExportComponentsStepIT { + + private static final String PROJECT_UUID = "PROJECT_UUID"; + private static final ComponentDto PROJECT = new ComponentDto() + // no id yet + .setScope(Scopes.PROJECT) + .setQualifier(Qualifiers.PROJECT) + .setKey("the_project") + .setName("The Project") + .setDescription("The project description") + .setEnabled(true) + .setUuid(PROJECT_UUID) + .setUuidPath(UUID_PATH_OF_ROOT) + .setCreatedAt(new Date(1596749115856L)) + .setBranchUuid(PROJECT_UUID); + + private static final String FILE_UUID = "FILE_UUID"; + private static final String FILE_UUID_PATH = PROJECT_UUID + FILE_UUID + UUID_PATH_SEPARATOR; + private static final ComponentDto FILE = new ComponentDto() + // no id yet + .setScope(Scopes.FILE) + .setQualifier(Qualifiers.FILE) + .setKey("the_file") + .setName("The File") + .setUuid(FILE_UUID) + .setUuidPath(FILE_UUID_PATH) + .setEnabled(true) + .setCreatedAt(new Date(1596749148406L)) + .setBranchUuid(PROJECT_UUID); + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + @Rule + public LogTester logTester = new LogTester(); + + private final FakeDumpWriter dumpWriter = new FakeDumpWriter(); + private final ProjectHolder projectHolder = mock(ProjectHolder.class); + private final MutableComponentRepository componentRepository = new ComponentRepositoryImpl(); + private final ExportComponentsStep underTest = new ExportComponentsStep(dbTester.getDbClient(), projectHolder, componentRepository, dumpWriter); + + @After + public void tearDown() { + dbTester.getSession().close(); + } + + @Test + public void export_components_including_project() { + dbTester.components().insertPublicProject(PROJECT); + dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE); + dbTester.commit(); + when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 components exported"); + List components = dumpWriter.getWrittenMessagesOf(DumpElement.COMPONENTS); + assertThat(components).extracting(ProjectDump.Component::getQualifier, ProjectDump.Component::getUuid, ProjectDump.Component::getUuidPath) + .containsExactlyInAnyOrder( + tuple(Qualifiers.FILE, FILE_UUID, FILE_UUID_PATH), + tuple(Qualifiers.PROJECT, PROJECT_UUID, UUID_PATH_OF_ROOT)); + } + + @Test + public void execute_register_all_components_uuids_as_their_id_in_ComponentRepository() { + dbTester.components().insertPublicProject(PROJECT); + dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE); + dbTester.commit(); + when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT)); + + underTest.execute(new TestComputationStepContext()); + + assertThat(ImmutableSet.of( + componentRepository.getRef(PROJECT.uuid()), + componentRepository.getRef(FILE.uuid()))).containsExactlyInAnyOrder(1L, 2L); + } + + @Test + public void throws_ISE_if_error() { + dbTester.components().insertPublicProject(PROJECT); + dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE); + dbTester.commit(); + when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT)); + dumpWriter.failIfMoreThan(1, DumpElement.COMPONENTS); + + assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext())) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Component Export failed after processing 1 components successfully"); + } + + @Test + public void getDescription_is_defined() { + assertThat(underTest.getDescription()).isEqualTo("Export components"); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepIT.java b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepIT.java new file mode 100644 index 00000000000..4bed242c8de --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepIT.java @@ -0,0 +1,259 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectexport.steps; + +import com.sonarsource.governance.projectdump.protobuf.ProjectDump; +import java.util.List; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.ce.task.projectexport.component.ComponentRepositoryImpl; +import org.sonar.ce.task.step.TestComputationStepContext; +import org.sonar.db.DbTester; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.measure.MeasureDto; +import org.sonar.db.metric.MetricDto; + +import static com.google.common.collect.Lists.newArrayList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT; +import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR; +import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED; +import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED; + +public class ExportMeasuresStepIT { + + private static final ComponentDto PROJECT = new ComponentDto() + .setKey("project_key") + .setUuid("project_uuid") + .setBranchUuid("project_uuid") + .setUuidPath(UUID_PATH_OF_ROOT) + .setEnabled(true); + private static final ComponentDto FILE = new ComponentDto() + .setKey("file_key") + .setUuid("file_uuid") + .setBranchUuid("project_uuid") + .setUuidPath(UUID_PATH_OF_ROOT + PROJECT.uuid() + UUID_PATH_SEPARATOR) + .setEnabled(true); + private static final ComponentDto ANOTHER_PROJECT = new ComponentDto() + .setKey("another_project_key") + .setUuid("another_project_uuid") + .setBranchUuid("another_project_uuid") + .setUuidPath(UUID_PATH_OF_ROOT) + .setEnabled(true); + + private static final MetricDto NCLOC = new MetricDto() + .setUuid("3") + .setKey("ncloc") + .setShortName("Lines of code") + .setEnabled(true); + + private static final MetricDto DISABLED_METRIC = new MetricDto() + .setUuid("4") + .setKey("coverage") + .setShortName("Coverage") + .setEnabled(false); + + private static final MetricDto NEW_NCLOC = new MetricDto() + .setUuid("5") + .setKey("new_ncloc") + .setShortName("New Lines of code") + .setEnabled(true); + + private static final List BRANCHES = newArrayList( + new BranchDto() + .setBranchType(BranchType.BRANCH) + .setKey("master") + .setUuid(PROJECT.uuid()) + .setProjectUuid(PROJECT.uuid())); + + + @Rule + public LogTester logTester = new LogTester(); + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private ComponentRepositoryImpl componentRepository = new ComponentRepositoryImpl(); + private MutableMetricRepository metricRepository = new MutableMetricRepositoryImpl(); + private ProjectHolder projectHolder = mock(ProjectHolder.class); + private FakeDumpWriter dumpWriter = new FakeDumpWriter(); + private ExportMeasuresStep underTest = new ExportMeasuresStep(dbTester.getDbClient(), projectHolder, componentRepository, metricRepository, dumpWriter); + + @Before + public void setUp() { + String projectUuid = dbTester.components().insertPublicProject(PROJECT).uuid(); + componentRepository.register(1, projectUuid, false); + dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE, ANOTHER_PROJECT); + dbTester.getDbClient().metricDao().insert(dbTester.getSession(), NCLOC, DISABLED_METRIC, NEW_NCLOC); + dbTester.commit(); + when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT)); + when(projectHolder.branches()).thenReturn(BRANCHES); + } + + @Test + public void export_zero_measures() { + underTest.execute(new TestComputationStepContext()); + + assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES)).isEmpty(); + assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 measures exported"); + assertThat(metricRepository.getRefByUuid()).isEmpty(); + } + + @Test + public void export_measures() { + SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED); + insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(NCLOC.getUuid())); + SnapshotDto secondAnalysis = insertSnapshot("U_2", PROJECT, STATUS_PROCESSED); + insertMeasure(secondAnalysis, PROJECT, new MeasureDto().setValue(110.0).setMetricUuid(NCLOC.getUuid())); + SnapshotDto anotherProjectAnalysis = insertSnapshot("U_3", ANOTHER_PROJECT, STATUS_PROCESSED); + insertMeasure(anotherProjectAnalysis, ANOTHER_PROJECT, new MeasureDto().setValue(500.0).setMetricUuid(NCLOC.getUuid())); + dbTester.commit(); + + underTest.execute(new TestComputationStepContext()); + + List exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES); + assertThat(exportedMeasures).hasSize(2); + assertThat(exportedMeasures).extracting(ProjectDump.Measure::getAnalysisUuid).containsOnly(firstAnalysis.getUuid(), secondAnalysis.getUuid()); + assertThat(exportedMeasures).extracting(ProjectDump.Measure::getMetricRef).containsOnly(0); + assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 measures exported"); + assertThat(metricRepository.getRefByUuid()).containsOnlyKeys(NCLOC.getUuid()); + } + + @Test + public void do_not_export_measures_on_unprocessed_snapshots() { + SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_UNPROCESSED); + insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(NCLOC.getUuid())); + dbTester.commit(); + + underTest.execute(new TestComputationStepContext()); + + List exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES); + assertThat(exportedMeasures).isEmpty(); + } + + @Test + public void do_not_export_measures_on_disabled_metrics() { + SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED); + insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(DISABLED_METRIC.getUuid())); + dbTester.commit(); + + underTest.execute(new TestComputationStepContext()); + + List exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES); + assertThat(exportedMeasures).isEmpty(); + } + + @Test + public void test_exported_fields() { + SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED); + MeasureDto dto = new MeasureDto() + .setMetricUuid(NCLOC.getUuid()) + .setValue(100.0) + .setData("data") + .setAlertStatus("OK") + .setAlertText("alert text"); + insertMeasure(analysis, PROJECT, dto); + dbTester.commit(); + + underTest.execute(new TestComputationStepContext()); + + List exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES); + ProjectDump.Measure measure = exportedMeasures.get(0); + assertThat(measure.getAlertStatus()).isEqualTo(dto.getAlertStatus()); + assertThat(measure.getAlertText()).isEqualTo(dto.getAlertText()); + assertThat(measure.getDoubleValue().getValue()).isEqualTo(dto.getValue()); + assertThat(measure.getTextValue()).isEqualTo(dto.getData()); + assertThat(measure.getMetricRef()).isZero(); + assertThat(measure.getAnalysisUuid()).isEqualTo(analysis.getUuid()); + assertThat(measure.getVariation1().getValue()).isZero(); + } + + @Test + public void test_exported_fields_new_metric() { + SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED); + MeasureDto dto = new MeasureDto() + .setMetricUuid(NEW_NCLOC.getUuid()) + .setValue(100.0) + .setData("data") + .setAlertStatus("OK") + .setAlertText("alert text"); + insertMeasure(analysis, PROJECT, dto); + dbTester.commit(); + + underTest.execute(new TestComputationStepContext()); + + List exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES); + ProjectDump.Measure measure = exportedMeasures.get(0); + assertThat(measure.getAlertStatus()).isEqualTo(dto.getAlertStatus()); + assertThat(measure.getAlertText()).isEqualTo(dto.getAlertText()); + assertThat(measure.getDoubleValue().getValue()).isZero(); + assertThat(measure.getTextValue()).isEqualTo(dto.getData()); + assertThat(measure.getMetricRef()).isZero(); + assertThat(measure.getAnalysisUuid()).isEqualTo(analysis.getUuid()); + assertThat(measure.getVariation1().getValue()).isEqualTo(dto.getValue()); + } + + @Test + public void test_null_exported_fields() { + SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED); + insertMeasure(analysis, PROJECT, new MeasureDto().setMetricUuid(NCLOC.getUuid())); + dbTester.commit(); + + underTest.execute(new TestComputationStepContext()); + + ProjectDump.Measure measure = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES).get(0); + assertThat(measure.getAlertStatus()).isEmpty(); + assertThat(measure.getAlertText()).isEmpty(); + assertThat(measure.hasDoubleValue()).isFalse(); + assertThat(measure.getTextValue()).isEmpty(); + assertThat(measure.hasVariation1()).isFalse(); + } + + @Test + public void test_getDescription() { + assertThat(underTest.getDescription()).isEqualTo("Export measures"); + } + + private SnapshotDto insertSnapshot(String snapshotUuid, ComponentDto project, String status) { + SnapshotDto snapshot = new SnapshotDto() + .setUuid(snapshotUuid) + .setComponentUuid(project.uuid()) + .setStatus(status) + .setLast(true); + dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), snapshot); + return snapshot; + } + + private void insertMeasure(SnapshotDto analysisDto, ComponentDto componentDto, MeasureDto measureDto) { + measureDto + .setAnalysisUuid(analysisDto.getUuid()) + .setComponentUuid(componentDto.uuid()); + dbTester.getDbClient().measureDao().insert(dbTester.getSession(), measureDto); + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1235_add_component_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1235_add_component_uuid_to_duplications_index.rb new file mode 100644 index 00000000000..34dfbce8de4 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1235_add_component_uuid_to_duplications_index.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class AddComponentUuidToDuplicationsIndex < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.AddComponentUuidColumnToDuplicationsIndex') + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1236_populate_component_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1236_populate_component_uuid_of_duplications_index.rb new file mode 100644 index 00000000000..6f9d4ddea8d --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1236_populate_component_uuid_of_duplications_index.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class PopulateComponentUuidOfDuplicationsIndex < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.PopulateComponentUuidOfDuplicationsIndex') + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1237_delete_orphan_duplications_index_rows_without_component.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1237_delete_orphan_duplications_index_rows_without_component.rb new file mode 100644 index 00000000000..f4f67a968f3 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1237_delete_orphan_duplications_index_rows_without_component.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class DeleteOrphanDuplicationsIndexRowsWithoutComponent < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutComponent') + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1238_make_component_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1238_make_component_uuid_not_null_on_duplications_index.rb new file mode 100644 index 00000000000..eece62dbab3 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1238_make_component_uuid_not_null_on_duplications_index.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class MakeComponentUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.MakeComponentUuidNotNullOnDuplicationsIndex') + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1239_add_analysis_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1239_add_analysis_uuid_to_duplications_index.rb new file mode 100644 index 00000000000..bb5011b7f9a --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1239_add_analysis_uuid_to_duplications_index.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class AddAnalysisUuidToDuplicationsIndex < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.AddAnalysisUuidColumnToDuplicationsIndex') + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1240_populate_analysis_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1240_populate_analysis_uuid_of_duplications_index.rb new file mode 100644 index 00000000000..6451732adad --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1240_populate_analysis_uuid_of_duplications_index.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class PopulateAnalysisUuidOfDuplicationsIndex < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.PopulateAnalysisUuidOfDuplicationsIndex') + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb new file mode 100644 index 00000000000..5afedbe9ebd --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class DeleteOrphanDuplicationsIndexRowsWithoutAnalysis < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutAnalysis') + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb new file mode 100644 index 00000000000..961cd6e902f --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb @@ -0,0 +1,31 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class MakeAnalysisUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.MakeAnalysisUuidNotNullOnDuplicationsIndex') + + add_index :duplications_index, [:analysis_uuid, :component_uuid], :name => 'duplication_analysis_component' + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddAnalysisUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddAnalysisUuidColumnToDuplicationsIndex.java new file mode 100644 index 00000000000..f641c800d7f --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddAnalysisUuidColumnToDuplicationsIndex.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.version.AddColumnsBuilder; + +import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE; +import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class AddAnalysisUuidColumnToDuplicationsIndex extends DdlChange { + + private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index"; + + public AddAnalysisUuidColumnToDuplicationsIndex(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX) + .addColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build()) + .build()); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddComponentUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddComponentUuidColumnToDuplicationsIndex.java new file mode 100644 index 00000000000..b6f6763cb3c --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/AddComponentUuidColumnToDuplicationsIndex.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.version.AddColumnsBuilder; + +import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE; +import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex extends DdlChange { + + private static final String TABLE_PUBLICATIONS_INDEX = "duplications_index"; + + public AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_PUBLICATIONS_INDEX) + .addColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build()) + .addColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build()) + .build()); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java new file mode 100644 index 00000000000..eb7ed0b79aa --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.server.platform.db.migration.step.MassUpdate; + +public class DeleteOrphanDuplicationsIndexRowsWithoutComponent extends BaseDataChange { + + public DeleteOrphanDuplicationsIndexRowsWithoutComponent(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select("SELECT id from duplications_index where component_uuid is null"); + massUpdate.update("DELETE from duplications_index WHERE id=?"); + massUpdate.rowPluralName("resources_index entries"); + massUpdate.execute((row, update) -> { + update.setLong(1, row.getLong(1)); + return true; + }); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java new file mode 100644 index 00000000000..e4d1f35dfa6 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.version.AlterColumnsBuilder; + +import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE; +import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class MakeComponentUuidNotNullOnDuplicationsIndex extends DdlChange { + + private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index"; + + public MakeComponentUuidNotNullOnDuplicationsIndex(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AlterColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX) + .updateColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build()) + .build()); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb new file mode 100644 index 00000000000..c7ecc23e1ad --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class AddComponentUuidAndAnalysisUuidToDuplicationsIndex < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex') + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb new file mode 100644 index 00000000000..024aa7f5ca1 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class PopulateComponentUuidAndAnalysisUuidOfDuplicationsIndex < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.PopulateComponentUuidAndAnalysisUuidOfDuplicationsIndex') + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb new file mode 100644 index 00000000000..caa887bffe4 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb @@ -0,0 +1,29 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class DeleteOrphanDuplicationsIndexRowsWithoutComponentOrAnalysis < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutComponentOrAnalysis') + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb new file mode 100644 index 00000000000..72e090fe509 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb @@ -0,0 +1,31 @@ +# +# SonarQube, open source software quality management tool. +# Copyright (C) 2008-2016 SonarSource +# mailto:contact AT sonarsource DOT com +# +# SonarQube is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 3 of the License, or (at your option) any later version. +# +# SonarQube is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + +# +# SonarQube 6.0 +# +class MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration + + def self.up + execute_java_migration('org.sonar.db.version.v60.MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex') + + add_index :duplications_index, [:analysis_uuid, :component_uuid], :name => 'duplication_analysis_component' + end +end diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java new file mode 100644 index 00000000000..d3df009edd7 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java @@ -0,0 +1,44 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v2; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.version.AddColumnsBuilder; + +import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE; +import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class AddComponentUuidColumnToDuplicationsIndex extends DdlChange { + + private static final String TABLE_PUBLICATIONS_INDEX = "duplications_index"; + + public AddComponentUuidColumnToDuplicationsIndex(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_PUBLICATIONS_INDEX) + .addColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build()) + .build()); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java new file mode 100644 index 00000000000..01f56100fa9 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/it/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepIT/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java @@ -0,0 +1,45 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v2; + +import java.sql.SQLException; +import org.sonar.db.Database; +import org.sonar.db.version.AlterColumnsBuilder; + +import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE; +import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder; + +public class MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex extends DdlChange { + + private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index"; + + public MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex(Database db) { + super(db); + } + + @Override + public void execute(Context context) throws SQLException { + context.execute(new AlterColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX) + .updateColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build()) + .updateColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build()) + .build()); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplTest.java deleted file mode 100644 index 91e915533d6..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/BranchPersisterImplTest.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.component; - -import com.tngtech.java.junit.dataprovider.DataProvider; -import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import com.tngtech.java.junit.dataprovider.UseDataProvider; -import java.util.Collections; -import java.util.Optional; -import javax.annotation.Nullable; -import org.assertj.core.api.ThrowableAssert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.sonar.api.config.internal.ConfigurationBridge; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.utils.System2; -import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; -import org.sonar.ce.task.projectanalysis.analysis.Branch; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.component.BranchDto; -import org.sonar.db.component.BranchType; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.component.ComponentTesting; -import org.sonar.db.protobuf.DbProjectBranches; -import org.sonar.server.project.Project; - -import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; -import static org.sonar.core.config.PurgeConstants.BRANCHES_TO_KEEP_WHEN_INACTIVE; -import static org.sonar.db.component.BranchType.BRANCH; -import static org.sonar.db.component.BranchType.PULL_REQUEST; - -@RunWith(DataProviderRunner.class) -public class BranchPersisterImplTest { - private final static Component MAIN = builder(Component.Type.PROJECT, 1, "PROJECT_KEY").setUuid("PROJECT_UUID").setName("p1").build(); - private final static Component BRANCH1 = builder(Component.Type.PROJECT, 2, "BRANCH_KEY").setUuid("BRANCH_UUID").build(); - private final static Component PR1 = builder(Component.Type.PROJECT, 3, "develop").setUuid("PR_UUID").build(); - private static final Project PROJECT = new Project(MAIN.getUuid(), MAIN.getKey(), MAIN.getName(), null, Collections.emptyList()); - - @Rule - public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - @Rule - public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); - - private final MapSettings settings = new MapSettings(); - private final ConfigurationRepository configurationRepository = new TestSettingsRepository(new ConfigurationBridge(settings)); - private final BranchPersister underTest = new BranchPersisterImpl(dbTester.getDbClient(), treeRootHolder, analysisMetadataHolder, configurationRepository); - - @Test - public void persist_fails_with_ISE_if_no_component_for_main_branches() { - analysisMetadataHolder.setBranch(createBranch(BRANCH, true, "master")); - treeRootHolder.setRoot(MAIN); - DbSession dbSession = dbTester.getSession(); - - expectMissingComponentISE(() -> underTest.persist(dbSession)); - } - - @Test - public void persist_fails_with_ISE_if_no_component_for_branches() { - analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "foo")); - treeRootHolder.setRoot(BRANCH1); - DbSession dbSession = dbTester.getSession(); - - expectMissingComponentISE(() -> underTest.persist(dbSession)); - } - - @Test - public void persist_fails_with_ISE_if_no_component_for_pull_request() { - analysisMetadataHolder.setBranch(createBranch(BranchType.PULL_REQUEST, false, "12")); - treeRootHolder.setRoot(BRANCH1); - DbSession dbSession = dbTester.getSession(); - - expectMissingComponentISE(() -> underTest.persist(dbSession)); - } - - @Test - @UseDataProvider("nullOrNotNullString") - public void persist_creates_row_in_PROJECTS_BRANCHES_for_branch(@Nullable String mergeBranchUuid) { - String branchName = "branch"; - - // add project and branch in table PROJECTS - ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(MAIN.getUuid()).setKey(MAIN.getKey()); - ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, - new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH)); - dbTester.components().insertComponents(mainComponent, component); - // set project in metadata - treeRootHolder.setRoot(BRANCH1); - analysisMetadataHolder.setBranch(createBranch(BRANCH, false, branchName, mergeBranchUuid)); - analysisMetadataHolder.setProject(Project.from(mainComponent)); - - underTest.persist(dbTester.getSession()); - - dbTester.getSession().commit(); - - assertThat(dbTester.countRowsOfTable("components")).isEqualTo(2); - Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); - assertThat(branchDto).isPresent(); - assertThat(branchDto.get().getBranchType()).isEqualTo(BRANCH); - assertThat(branchDto.get().getKey()).isEqualTo(branchName); - assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo(mergeBranchUuid); - assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid()); - assertThat(branchDto.get().getPullRequestData()).isNull(); - } - - @Test - public void main_branch_is_excluded_from_branch_purge_by_default() { - analysisMetadataHolder.setBranch(createBranch(BRANCH, true, "master")); - treeRootHolder.setRoot(MAIN); - dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); - dbTester.commit(); - - underTest.persist(dbTester.getSession()); - - Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), MAIN.getUuid()); - assertThat(branchDto).isPresent(); - assertThat(branchDto.get().isExcludeFromPurge()).isTrue(); - } - - @Test - public void non_main_branch_is_excluded_from_branch_purge_if_matches_sonar_dbcleaner_keepFromPurge_property() { - settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "BRANCH.*"); - analysisMetadataHolder.setProject(PROJECT); - analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY")); - treeRootHolder.setRoot(BRANCH1); - ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); - ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, - new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH)); - dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component); - dbTester.commit(); - - underTest.persist(dbTester.getSession()); - - Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); - assertThat(branchDto).isPresent(); - assertThat(branchDto.get().isExcludeFromPurge()).isTrue(); - } - - @Test - public void branch_is_excluded_from_purge_when_it_matches_setting() { - analysisMetadataHolder.setProject(PROJECT); - analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY")); - treeRootHolder.setRoot(BRANCH1); - ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); - ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, - new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH)); - dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component); - settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "BRANCH.*"); - dbTester.commit(); - - underTest.persist(dbTester.getSession()); - - Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); - assertThat(branchDto).isPresent(); - assertThat(branchDto.get().isExcludeFromPurge()).isTrue(); - } - - @Test - public void branch_is_not_excluded_from_purge_when_it_does_not_match_setting() { - analysisMetadataHolder.setProject(PROJECT); - analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY")); - treeRootHolder.setRoot(BRANCH1); - ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); - ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, - new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH)); - dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component); - settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "abc.*"); - - dbTester.commit(); - - underTest.persist(dbTester.getSession()); - - Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); - assertThat(branchDto).isPresent(); - assertThat(branchDto.get().isExcludeFromPurge()).isFalse(); - } - - @Test - public void pull_request_is_never_excluded_from_branch_purge_even_if_its_source_branch_name_matches_sonar_dbcleaner_keepFromPurge_property() { - settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "develop"); - analysisMetadataHolder.setBranch(createPullRequest(PR1.getKey(), MAIN.getUuid())); - analysisMetadataHolder.setPullRequestKey(PR1.getKey()); - treeRootHolder.setRoot(PR1); - ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); - ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, new BranchDto() - .setUuid(PR1.getUuid()) - .setKey(PR1.getKey()) - .setProjectUuid(MAIN.getUuid()) - .setBranchType(PULL_REQUEST) - .setMergeBranchUuid(MAIN.getUuid())); - dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component); - dbTester.commit(); - - underTest.persist(dbTester.getSession()); - - Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), PR1.getUuid()); - assertThat(branchDto).isPresent(); - assertThat(branchDto.get().isExcludeFromPurge()).isFalse(); - } - - @Test - public void non_main_branch_is_included_in_branch_purge_if_branch_name_does_not_match_sonar_dbcleaner_keepFromPurge_property() { - settings.setProperty(BRANCHES_TO_KEEP_WHEN_INACTIVE, "foobar-.*"); - analysisMetadataHolder.setProject(PROJECT); - analysisMetadataHolder.setBranch(createBranch(BRANCH, false, "BRANCH_KEY")); - treeRootHolder.setRoot(BRANCH1); - ComponentDto mainComponent = dbTester.components().insertPublicProject(p -> p.setKey(MAIN.getKey()).setUuid(MAIN.getUuid())); - ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, - new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(BRANCH)); - dbTester.getDbClient().componentDao().insert(dbTester.getSession(), component); - dbTester.commit(); - - underTest.persist(dbTester.getSession()); - - Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); - assertThat(branchDto).isPresent(); - assertThat(branchDto.get().isExcludeFromPurge()).isFalse(); - } - - @DataProvider - public static Object[][] nullOrNotNullString() { - return new Object[][] { - {null}, - {randomAlphabetic(12)} - }; - } - - @Test - public void persist_creates_row_in_PROJECTS_BRANCHES_for_pull_request() { - String pullRequestId = "pr-123"; - - // add project and branch in table PROJECTS - ComponentDto mainComponent = ComponentTesting.newPrivateProjectDto(MAIN.getUuid()).setKey(MAIN.getKey()); - ComponentDto component = ComponentTesting.newBranchComponent(mainComponent, - new BranchDto().setUuid(BRANCH1.getUuid()).setKey(BRANCH1.getKey()).setBranchType(PULL_REQUEST)); - dbTester.components().insertComponents(mainComponent, component); - // set project in metadata - treeRootHolder.setRoot(BRANCH1); - analysisMetadataHolder.setBranch(createBranch(PULL_REQUEST, false, pullRequestId, "mergeBanchUuid")); - analysisMetadataHolder.setProject(Project.from(mainComponent)); - analysisMetadataHolder.setPullRequestKey(pullRequestId); - - underTest.persist(dbTester.getSession()); - - dbTester.getSession().commit(); - - assertThat(dbTester.countRowsOfTable("components")).isEqualTo(2); - Optional branchDto = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), BRANCH1.getUuid()); - assertThat(branchDto).isPresent(); - assertThat(branchDto.get().getBranchType()).isEqualTo(PULL_REQUEST); - assertThat(branchDto.get().getKey()).isEqualTo(pullRequestId); - assertThat(branchDto.get().getMergeBranchUuid()).isEqualTo("mergeBanchUuid"); - assertThat(branchDto.get().getProjectUuid()).isEqualTo(MAIN.getUuid()); - assertThat(branchDto.get().getPullRequestData()).isEqualTo(DbProjectBranches.PullRequestData.newBuilder() - .setBranch(pullRequestId) - .setTarget("mergeBanchUuid") - .setTitle(pullRequestId) - .build()); - } - - private static Branch createBranch(BranchType type, boolean isMain, String name) { - return createBranch(type, isMain, name, null); - } - - private static Branch createPullRequest(String key, String mergeBranchUuid) { - Branch branch = createBranch(PULL_REQUEST, false, key, mergeBranchUuid); - when(branch.getPullRequestKey()).thenReturn(key); - return branch; - } - - private static Branch createBranch(BranchType type, boolean isMain, String name, @Nullable String mergeBranchUuid) { - Branch branch = mock(Branch.class); - when(branch.getType()).thenReturn(type); - when(branch.getName()).thenReturn(name); - when(branch.isMain()).thenReturn(isMain); - when(branch.getReferenceBranchUuid()).thenReturn(mergeBranchUuid); - when(branch.getTargetBranchName()).thenReturn(mergeBranchUuid); - return branch; - } - - private void expectMissingComponentISE(ThrowableAssert.ThrowingCallable callable) { - assertThatThrownBy(callable) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Component has been deleted by end-user during analysis"); - } -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest.java deleted file mode 100644 index c3116579358..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest.java +++ /dev/null @@ -1,710 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.filemove; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.IntStream; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.log.LogTester; -import org.sonar.api.utils.log.LoggerLevel; -import org.sonar.ce.task.projectanalysis.analysis.Analysis; -import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; -import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.FileAttributes; -import org.sonar.ce.task.projectanalysis.component.ReportComponent; -import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; -import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository; -import org.sonar.ce.task.step.TestComputationStepContext; -import org.sonar.core.hash.SourceLineHashesComputer; -import org.sonar.core.util.Uuids; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.component.BranchType; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.component.ComponentTesting; -import org.sonar.db.source.FileSourceDto; - -import static java.util.Arrays.stream; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; -import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStep.MIN_REQUIRED_SCORE; -import static org.sonar.db.component.BranchType.*; - -public class FileMoveDetectionStepTest { - - private static final String SNAPSHOT_UUID = "uuid_1"; - private static final Analysis ANALYSIS = new Analysis.Builder() - .setUuid(SNAPSHOT_UUID) - .setCreatedAt(86521) - .build(); - private static final int ROOT_REF = 1; - private static final int FILE_1_REF = 2; - private static final int FILE_2_REF = 3; - private static final int FILE_3_REF = 4; - private static final String[] CONTENT1 = { - "package org.sonar.ce.task.projectanalysis.filemove;", - "", - "public class Foo {", - " public String bar() {", - " return \"Doh!\";", - " }", - "}" - }; - - private static final String[] LESS_CONTENT1 = { - "package org.sonar.ce.task.projectanalysis.filemove;", - "", - "public class Foo {", - " public String foo() {", - " return \"Donut!\";", - " }", - "}" - }; - private static final String[] CONTENT_EMPTY = { - "" - }; - private static final String[] CONTENT2 = { - "package org.sonar.ce.queue;", - "", - "import com.google.common.base.MoreObjects;", - "import javax.annotation.CheckForNull;", - "import javax.annotation.Nullable;", - "import javax.annotation.concurrent.Immutable;", - "", - "import static com.google.common.base.Strings.emptyToNull;", - "import static java.util.Objects.requireNonNull;", - "", - "@Immutable", - "public class CeTask {", - "", - ", private final String type;", - ", private final String uuid;", - ", private final String componentUuid;", - ", private final String componentKey;", - ", private final String componentName;", - ", private final String submitterLogin;", - "", - ", private CeTask(Builder builder) {", - ", this.uuid = requireNonNull(emptyToNull(builder.uuid));", - ", this.type = requireNonNull(emptyToNull(builder.type));", - ", this.componentUuid = emptyToNull(builder.componentUuid);", - ", this.componentKey = emptyToNull(builder.componentKey);", - ", this.componentName = emptyToNull(builder.componentName);", - ", this.submitterLogin = emptyToNull(builder.submitterLogin);", - ", }", - "", - ", public String getUuid() {", - ", return uuid;", - ", }", - "", - ", public String getType() {", - ", return type;", - ", }", - "", - ", @CheckForNull", - ", public String getComponentUuid() {", - ", return componentUuid;", - ", }", - "", - ", @CheckForNull", - ", public String getComponentKey() {", - ", return componentKey;", - ", }", - "", - ", @CheckForNull", - ", public String getComponentName() {", - ", return componentName;", - ", }", - "", - ", @CheckForNull", - ", public String getSubmitterLogin() {", - ", return submitterLogin;", - ", }", - ",}", - }; - // removed immutable annotation - private static final String[] LESS_CONTENT2 = { - "package org.sonar.ce.queue;", - "", - "import com.google.common.base.MoreObjects;", - "import javax.annotation.CheckForNull;", - "import javax.annotation.Nullable;", - "", - "import static com.google.common.base.Strings.emptyToNull;", - "import static java.util.Objects.requireNonNull;", - "", - "public class CeTask {", - "", - ", private final String type;", - ", private final String uuid;", - ", private final String componentUuid;", - ", private final String componentKey;", - ", private final String componentName;", - ", private final String submitterLogin;", - "", - ", private CeTask(Builder builder) {", - ", this.uuid = requireNonNull(emptyToNull(builder.uuid));", - ", this.type = requireNonNull(emptyToNull(builder.type));", - ", this.componentUuid = emptyToNull(builder.componentUuid);", - ", this.componentKey = emptyToNull(builder.componentKey);", - ", this.componentName = emptyToNull(builder.componentName);", - ", this.submitterLogin = emptyToNull(builder.submitterLogin);", - ", }", - "", - ", public String getUuid() {", - ", return uuid;", - ", }", - "", - ", public String getType() {", - ", return type;", - ", }", - "", - ", @CheckForNull", - ", public String getComponentUuid() {", - ", return componentUuid;", - ", }", - "", - ", @CheckForNull", - ", public String getComponentKey() {", - ", return componentKey;", - ", }", - "", - ", @CheckForNull", - ", public String getComponentName() {", - ", return componentName;", - ", }", - "", - ", @CheckForNull", - ", public String getSubmitterLogin() {", - ", return submitterLogin;", - ", }", - ",}", - }; - - @Rule - public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); - @Rule - public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule(); - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - @Rule - public LogTester logTester = new LogTester(); - - private final DbClient dbClient = dbTester.getDbClient(); - private ComponentDto project; - - private final AnalysisMetadataHolderRule analysisMetadataHolder = mock(AnalysisMetadataHolderRule.class); - private final SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class); - private final FileSimilarity fileSimilarity = new FileSimilarityImpl(new SourceSimilarityImpl()); - private final CapturingScoreMatrixDumper scoreMatrixDumper = new CapturingScoreMatrixDumper(); - private final RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository(); - - private final FileMoveDetectionStep underTest = new FileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient, - fileSimilarity, movedFilesRepository, sourceLinesHash, scoreMatrixDumper, addedFileRepository); - - @Before - public void setUp() throws Exception { - project = dbTester.components().insertPrivateProject(); - treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF).setUuid(project.uuid()).build()); - } - - @Test - public void getDescription_returns_description() { - assertThat(underTest.getDescription()).isEqualTo("Detect file moves"); - } - - @Test - public void execute_detects_no_move_if_in_pull_request_scope() { - prepareAnalysis(PULL_REQUEST, ANALYSIS); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - verifyStatistics(context, null, null, null, null); - } - - @Test - public void execute_detects_no_move_on_first_analysis() { - prepareAnalysis(BRANCH, null); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - verifyStatistics(context, 0, null, null, null); - } - - @Test - public void execute_detects_no_move_if_baseSnapshot_has_no_file_and_report_has_no_file() { - prepareBranchAnalysis(ANALYSIS); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(addedFileRepository.getComponents()).isEmpty(); - verifyStatistics(context, 0, null, null, null); - } - - @Test - public void execute_detects_no_move_if_baseSnapshot_has_no_file() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, null); - setFilesInReport(file1, file2); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(addedFileRepository.getComponents()).containsOnly(file1, file2); - verifyStatistics(context, 2, 0, 2, null); - } - - @Test - public void execute_detects_no_move_if_there_is_no_file_in_report() { - prepareBranchAnalysis(ANALYSIS); - insertFiles( /* no components */); - setFilesInReport(); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(addedFileRepository.getComponents()).isEmpty(); - verifyStatistics(context, 0, null, null, null); - } - - @Test - public void execute_detects_no_move_if_file_key_exists_in_both_DB_and_report() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, null); - insertFiles(file1.getUuid(), file2.getUuid()); - insertContentOfFileInDb(file1.getUuid(), CONTENT1); - insertContentOfFileInDb(file2.getUuid(), CONTENT2); - setFilesInReport(file2, file1); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(addedFileRepository.getComponents()).isEmpty(); - verifyStatistics(context, 2, 2, 0, null); - } - - @Test - public void execute_detects_move_if_content_of_file_is_same_in_DB_and_report() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, CONTENT1); - ComponentDto[] dtos = insertFiles(file1.getUuid()); - insertContentOfFileInDb(file1.getUuid(), CONTENT1); - setFilesInReport(file2); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).containsExactly(file2); - MovedFilesRepository.OriginalFile originalFile = movedFilesRepository.getOriginalFile(file2).get(); - assertThat(originalFile.key()).isEqualTo(dtos[0].getKey()); - assertThat(originalFile.uuid()).isEqualTo(dtos[0].uuid()); - assertThat(addedFileRepository.getComponents()).isEmpty(); - verifyStatistics(context, 1, 1, 1, 1); - } - - @Test - public void execute_detects_no_move_if_content_of_file_is_not_similar_enough() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, LESS_CONTENT1); - insertFiles(file1.getKey()); - insertContentOfFileInDb(file1.getKey(), CONTENT1); - setFilesInReport(file2); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()) - .isPositive() - .isLessThan(MIN_REQUIRED_SCORE); - assertThat(addedFileRepository.getComponents()).contains(file2); - verifyStatistics(context, 1, 1, 1, 0); - } - - @Test - public void execute_detects_no_move_if_content_of_file_is_empty_in_DB() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, CONTENT1); - insertFiles(file1.getKey()); - insertContentOfFileInDb(file1.getKey(), CONTENT_EMPTY); - setFilesInReport(file2); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero(); - assertThat(addedFileRepository.getComponents()).contains(file2); - verifyStatistics(context, 1, 1, 1, 0); - } - - @Test - public void execute_detects_no_move_if_content_of_file_has_no_path_in_DB() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, CONTENT1); - insertFiles(key -> newComponentDto(key).setPath(null), file1.getKey()); - insertContentOfFileInDb(file1.getKey(), CONTENT1); - setFilesInReport(file2); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(scoreMatrixDumper.scoreMatrix).isNull(); - assertThat(addedFileRepository.getComponents()).containsOnly(file2); - verifyStatistics(context, 1, 0, 1, null); - } - - @Test - public void execute_detects_no_move_if_content_of_file_is_empty_in_report() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, CONTENT_EMPTY); - insertFiles(file1.getKey()); - insertContentOfFileInDb(file1.getKey(), CONTENT1); - setFilesInReport(file2); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero(); - assertThat(addedFileRepository.getComponents()).contains(file2); - verifyStatistics(context, 1, 1, 1, 0); - assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("max score in matrix is less than min required score (85). Do nothing."); - } - - @Test - public void execute_detects_no_move_if_two_added_files_have_same_content_as_the_one_in_db() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, CONTENT1); - Component file3 = fileComponent(FILE_3_REF, CONTENT1); - insertFiles(file1.getKey()); - insertContentOfFileInDb(file1.getKey(), CONTENT1); - setFilesInReport(file2, file3); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100); - assertThat(addedFileRepository.getComponents()).containsOnly(file2, file3); - verifyStatistics(context, 2, 1, 2, 0); - } - - @Test - public void execute_detects_no_move_if_two_deleted_files_have_same_content_as_the_one_added() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, null); - Component file3 = fileComponent(FILE_3_REF, CONTENT1); - insertFiles(file1.getUuid(), file2.getUuid()); - insertContentOfFileInDb(file1.getUuid(), CONTENT1); - insertContentOfFileInDb(file2.getUuid(), CONTENT1); - setFilesInReport(file3); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isEqualTo(100); - assertThat(addedFileRepository.getComponents()).containsOnly(file3); - verifyStatistics(context, 1, 2, 1, 0); - } - - @Test - public void execute_detects_no_move_if_two_files_are_empty_in_DB() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, null); - insertFiles(file1.getUuid(), file2.getUuid()); - insertContentOfFileInDb(file1.getUuid(), null); - insertContentOfFileInDb(file2.getUuid(), null); - setFilesInReport(file1, file2); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(scoreMatrixDumper.scoreMatrix).isNull(); - assertThat(addedFileRepository.getComponents()).isEmpty(); - verifyStatistics(context, 2, 2, 0, null); - } - - @Test - public void execute_detects_several_moves() { - // testing: - // - file1 renamed to file3 - // - file2 deleted - // - file4 untouched - // - file5 renamed to file6 with a small change - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, null); - Component file3 = fileComponent(FILE_3_REF, CONTENT1); - Component file4 = fileComponent(5, new String[] {"a", "b"}); - Component file5 = fileComponent(6, null); - Component file6 = fileComponent(7, LESS_CONTENT2); - ComponentDto[] dtos = insertFiles(file1.getUuid(), file2.getUuid(), file4.getUuid(), file5.getUuid()); - insertContentOfFileInDb(file1.getUuid(), CONTENT1); - insertContentOfFileInDb(file2.getUuid(), LESS_CONTENT1); - insertContentOfFileInDb(file4.getUuid(), new String[] {"e", "f", "g", "h", "i"}); - insertContentOfFileInDb(file5.getUuid(), CONTENT2); - setFilesInReport(file3, file4, file6); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly(file3, file6); - MovedFilesRepository.OriginalFile originalFile2 = movedFilesRepository.getOriginalFile(file3).get(); - assertThat(originalFile2.key()).isEqualTo(dtos[0].getKey()); - assertThat(originalFile2.uuid()).isEqualTo(dtos[0].uuid()); - MovedFilesRepository.OriginalFile originalFile5 = movedFilesRepository.getOriginalFile(file6).get(); - assertThat(originalFile5.key()).isEqualTo(dtos[3].getKey()); - assertThat(originalFile5.uuid()).isEqualTo(dtos[3].uuid()); - assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isGreaterThan(MIN_REQUIRED_SCORE); - assertThat(addedFileRepository.getComponents()).isEmpty(); - verifyStatistics(context, 3, 4, 2, 2); - } - - @Test - public void execute_does_not_compute_any_distance_if_all_files_sizes_are_all_too_different() { - prepareBranchAnalysis(ANALYSIS); - Component file1 = fileComponent(FILE_1_REF, null); - Component file2 = fileComponent(FILE_2_REF, null); - Component file3 = fileComponent(FILE_3_REF, arrayOf(118)); - Component file4 = fileComponent(5, arrayOf(25)); - insertFiles(file1.getKey(), file2.getKey()); - insertContentOfFileInDb(file1.getKey(), arrayOf(100)); - insertContentOfFileInDb(file2.getKey(), arrayOf(30)); - setFilesInReport(file3, file4); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(scoreMatrixDumper.scoreMatrix.getMaxScore()).isZero(); - verifyStatistics(context, 2, 2, 2, 0); - } - - /** - * Creates an array of {@code numberOfElements} int values as String, starting with zero. - */ - private static String[] arrayOf(int numberOfElements) { - return IntStream.range(0, numberOfElements).mapToObj(String::valueOf).toArray(String[]::new); - } - - /** - * JH: A bug was encountered in the algorithm and I didn't manage to forge a simpler test case. - */ - @Test - public void real_life_use_case() throws Exception { - prepareBranchAnalysis(ANALYSIS); - for (File f : FileUtils.listFiles(new File("src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1"), null, false)) { - insertFiles("uuid_" + f.getName().hashCode()); - insertContentOfFileInDb("uuid_" + f.getName().hashCode(), readLines(f)); - } - - Map comps = new HashMap<>(); - int i = 1; - for (File f : FileUtils.listFiles(new File("src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2"), null, false)) { - String[] lines = readLines(f); - Component c = builder(Component.Type.FILE, i++) - .setUuid("uuid_" + f.getName().hashCode()) - .setKey(f.getName()) - .setName(f.getName()) - .setFileAttributes(new FileAttributes(false, null, lines.length)) - .build(); - - comps.put(f.getName(), c); - setFileLineHashesInReport(c, lines); - } - - setFilesInReport(comps.values().toArray(new Component[0])); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - Component makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex = comps.get("MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java"); - Component migrationRb1238 = comps.get("1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb"); - Component addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex = comps.get("AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java"); - assertThat(movedFilesRepository.getComponentsWithOriginal()).containsOnly( - makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex, - migrationRb1238, - addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex); - - assertThat(movedFilesRepository.getOriginalFile(makeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex).get().uuid()) - .isEqualTo("uuid_" + "MakeComponentUuidNotNullOnDuplicationsIndex.java".hashCode()); - assertThat(movedFilesRepository.getOriginalFile(migrationRb1238).get().uuid()) - .isEqualTo("uuid_" + "1242_make_analysis_uuid_not_null_on_duplications_index.rb".hashCode()); - assertThat(movedFilesRepository.getOriginalFile(addComponentUuidAndAnalysisUuidColumnToDuplicationsIndex).get().uuid()) - .isEqualTo("uuid_" + "AddComponentUuidColumnToDuplicationsIndex.java".hashCode()); - verifyStatistics(context, comps.values().size(), 12, 6, 3); - } - - private String[] readLines(File filename) throws IOException { - return FileUtils - .readLines(filename, StandardCharsets.UTF_8) - .toArray(new String[0]); - } - - @CheckForNull - private FileSourceDto insertContentOfFileInDb(String uuid, @Nullable String[] content) { - return dbTester.getDbClient().componentDao().selectByUuid(dbTester.getSession(), uuid) - .map(file -> { - SourceLineHashesComputer linesHashesComputer = new SourceLineHashesComputer(); - if (content != null) { - stream(content).forEach(linesHashesComputer::addLine); - } - FileSourceDto fileSourceDto = new FileSourceDto() - .setUuid(Uuids.createFast()) - .setFileUuid(file.uuid()) - .setProjectUuid(file.branchUuid()) - .setLineHashes(linesHashesComputer.getLineHashes()); - dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto); - dbTester.commit(); - return fileSourceDto; - }).orElse(null); - } - - private void setFilesInReport(Component... files) { - treeRootHolder.setRoot(builder(Component.Type.PROJECT, ROOT_REF) - .setUuid(project.uuid()) - .addChildren(files) - .build()); - } - - private ComponentDto[] insertFiles(String... uuids) { - return insertFiles(this::newComponentDto, uuids); - } - - private ComponentDto[] insertFiles(Function newComponentDto, String... uuids) { - return stream(uuids) - .map(newComponentDto) - .map(dto -> dbTester.components().insertComponent(dto)) - .toArray(ComponentDto[]::new); - } - - private ComponentDto newComponentDto(String uuid) { - return ComponentTesting.newFileDto(project) - .setKey("key_" + uuid) - .setUuid(uuid) - .setPath("path_" + uuid); - } - - private Component fileComponent(int ref, @Nullable String[] content) { - ReportComponent component = builder(Component.Type.FILE, ref) - .setName("report_path" + ref) - .setFileAttributes(new FileAttributes(false, null, content == null ? 1 : content.length)) - .build(); - if (content != null) { - setFileLineHashesInReport(component, content); - } - return component; - } - - private void setFileLineHashesInReport(Component file, String[] content) { - SourceLineHashesComputer computer = new SourceLineHashesComputer(); - for (String line : content) { - computer.addLine(line); - } - when(sourceLinesHash.getLineHashesMatchingDBVersion(file)).thenReturn(computer.getLineHashes()); - } - - private static class CapturingScoreMatrixDumper implements ScoreMatrixDumper { - private ScoreMatrix scoreMatrix; - - @Override - public void dumpAsCsv(ScoreMatrix scoreMatrix) { - this.scoreMatrix = scoreMatrix; - } - } - - private void prepareBranchAnalysis(Analysis analysis) { - prepareAnalysis(BRANCH, analysis); - } - - private void prepareAnalysis(BranchType branch, Analysis analysis) { - mockBranchType(branch); - analysisMetadataHolder.setBaseAnalysis(analysis); - } - - private void mockBranchType(BranchType branchType) { - when(analysisMetadataHolder.isPullRequest()).thenReturn(branchType == PULL_REQUEST); - } - - public static void verifyStatistics(TestComputationStepContext context, - @Nullable Integer expectedReportFiles, @Nullable Integer expectedDbFiles, - @Nullable Integer expectedAddedFiles, @Nullable Integer expectedMovedFiles) { - context.getStatistics().assertValue("reportFiles", expectedReportFiles); - context.getStatistics().assertValue("dbFiles", expectedDbFiles); - context.getStatistics().assertValue("addedFiles", expectedAddedFiles); - context.getStatistics().assertValue("movedFiles", expectedMovedFiles); - } - - public static class RecordingMutableAddedFileRepository implements MutableAddedFileRepository { - private final List components = new ArrayList<>(); - - @Override - public void register(Component file) { - components.add(file); - } - - @Override - public boolean isAdded(Component component) { - throw new UnsupportedOperationException("isAdded should not be called"); - } - - public List getComponents() { - return components; - } - } -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepTest.java deleted file mode 100644 index 5c734d08786..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/filemove/PullRequestFileMoveDetectionStepTest.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.filemove; - -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import javax.annotation.concurrent.Immutable; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.log.LogTester; -import org.sonar.ce.task.projectanalysis.analysis.Analysis; -import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; -import org.sonar.ce.task.projectanalysis.analysis.Branch; -import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.FileAttributes; -import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; -import org.sonar.ce.task.projectanalysis.filemove.MovedFilesRepository.OriginalFile; -import org.sonar.ce.task.step.TestComputationStepContext; -import org.sonar.core.util.Uuids; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.component.BranchType; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.component.ComponentTesting; -import org.sonar.db.source.FileSourceDto; -import org.sonar.server.project.Project; - -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; -import static java.util.stream.Collectors.toSet; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE; -import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT; -import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; -import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.RecordingMutableAddedFileRepository; -import static org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.verifyStatistics; -import static org.sonar.db.component.BranchType.BRANCH; -import static org.sonar.db.component.BranchType.PULL_REQUEST; - -public class PullRequestFileMoveDetectionStepTest { - private static final String ROOT_REF = "0"; - private static final String FILE_1_REF = "1"; - private static final String FILE_2_REF = "2"; - private static final String FILE_3_REF = "3"; - private static final String FILE_4_REF = "4"; - private static final String FILE_5_REF = "5"; - private static final String FILE_6_REF = "6"; - private static final String FILE_7_REF = "7"; - private static final String TARGET_BRANCH = "target_branch"; - private static final String BRANCH_UUID = "branch_uuid"; - private static final String SNAPSHOT_UUID = "uuid_1"; - - private static final Analysis ANALYSIS = new Analysis.Builder() - .setUuid(SNAPSHOT_UUID) - .setCreatedAt(86521) - .build(); - - @Rule - public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); - @Rule - public MutableMovedFilesRepositoryRule movedFilesRepository = new MutableMovedFilesRepositoryRule(); - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - @Rule - public LogTester logTester = new LogTester(); - - private ComponentDto branch; - private ComponentDto project; - - private final DbClient dbClient = dbTester.getDbClient(); - private final AnalysisMetadataHolderRule analysisMetadataHolder = mock(AnalysisMetadataHolderRule.class); - private final RecordingMutableAddedFileRepository addedFileRepository = new RecordingMutableAddedFileRepository(); - private final PullRequestFileMoveDetectionStep underTest = new PullRequestFileMoveDetectionStep(analysisMetadataHolder, treeRootHolder, dbClient, movedFilesRepository, addedFileRepository); - - @Before - public void setUp() throws Exception { - project = dbTester.components().insertPrivateProject(); - branch = dbTester.components().insertProjectBranch(project, branchDto -> branchDto.setUuid(BRANCH_UUID).setKey(TARGET_BRANCH)); - treeRootHolder.setRoot(builder(Component.Type.PROJECT, Integer.parseInt(ROOT_REF)).setUuid(project.uuid()).build()); - } - - @Test - public void getDescription_returns_description() { - assertThat(underTest.getDescription()).isEqualTo("Detect file moves in Pull Request scope"); - } - - @Test - public void execute_does_not_detect_any_files_if_not_in_pull_request_scope() { - prepareAnalysis(BRANCH, ANALYSIS); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - verifyStatistics(context, null, null, null, null); - } - - @Test - public void execute_detects_no_move_if_report_has_no_file() { - preparePullRequestAnalysis(ANALYSIS); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(addedFileRepository.getComponents()).isEmpty(); - verifyStatistics(context, 0, null, null, null); - } - - @Test - public void execute_detects_no_move_if_target_branch_has_no_files() { - preparePullRequestAnalysis(ANALYSIS); - Set fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF)); - Map reportFilesByUuid = initializeAnalysisReportComponents(fileReferences); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(addedFileRepository.getComponents()).containsOnlyOnceElementsOf(reportFilesByUuid.values()); - verifyStatistics(context, 2, 0, 2, null); - } - - @Test - public void execute_detects_no_move_if_there_are_no_files_in_report() { - preparePullRequestAnalysis(ANALYSIS); - initializeAnalysisReportComponents(Set.of()); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(addedFileRepository.getComponents()).isEmpty(); - verifyStatistics(context, 0, null, null, null); - } - - @Test - public void execute_detects_no_move_if_file_key_exists_in_both_database_and_report() { - preparePullRequestAnalysis(ANALYSIS); - - Set fileReferences = Set.of(FileReference.of(FILE_1_REF), FileReference.of(FILE_2_REF)); - initializeAnalysisReportComponents(fileReferences); - initializeTargetBranchDatabaseComponents(fileReferences); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).isEmpty(); - assertThat(addedFileRepository.getComponents()).isEmpty(); - verifyStatistics(context, 2, 2, 0, 0); - } - - @Test - public void execute_detects_renamed_file() { - // - FILE_1_REF on target branch is renamed to FILE_2_REF on Pull Request - preparePullRequestAnalysis(ANALYSIS); - - Set reportFileReferences = Set.of(FileReference.of(FILE_2_REF, FILE_1_REF)); - Set databaseFileReferences = Set.of(FileReference.of(FILE_1_REF)); - - Map reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences); - Map databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(addedFileRepository.getComponents()).isEmpty(); - assertThat(movedFilesRepository.getComponentsWithOriginal()).hasSize(1); - assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_2_REF, FILE_1_REF); - verifyStatistics(context, 1, 1, 0, 1); - } - - @Test - public void execute_detects_several_renamed_file() { - // - FILE_1_REF has been renamed to FILE_3_REF on Pull Request - // - FILE_2_REF has been deleted on Pull Request - // - FILE_4_REF has been left untouched - // - FILE_5_REF has been renamed to FILE_6_REF on Pull Request - // - FILE_7_REF has been added on Pull Request - preparePullRequestAnalysis(ANALYSIS); - - Set reportFileReferences = Set.of( - FileReference.of(FILE_3_REF, FILE_1_REF), - FileReference.of(FILE_4_REF), - FileReference.of(FILE_6_REF, FILE_5_REF), - FileReference.of(FILE_7_REF)); - - Set databaseFileReferences = Set.of( - FileReference.of(FILE_1_REF), - FileReference.of(FILE_2_REF), - FileReference.of(FILE_4_REF), - FileReference.of(FILE_5_REF)); - - Map reportFilesByUuid = initializeAnalysisReportComponents(reportFileReferences); - Map databaseFilesByUuid = initializeTargetBranchDatabaseComponents(databaseFileReferences); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(addedFileRepository.getComponents()).hasSize(1); - assertThat(movedFilesRepository.getComponentsWithOriginal()).hasSize(2); - assertThatFileAdditionHasBeenDetected(reportFilesByUuid, FILE_7_REF); - assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_3_REF, FILE_1_REF); - assertThatFileRenameHasBeenDetected(reportFilesByUuid, databaseFilesByUuid, FILE_6_REF, FILE_5_REF); - verifyStatistics(context, 4, 4, 1, 2); - } - - private void assertThatFileAdditionHasBeenDetected(Map reportFilesByUuid, String fileInReportReference) { - Component fileInReport = reportFilesByUuid.get(fileInReportReference); - - assertThat(addedFileRepository.getComponents()).contains(fileInReport); - assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isEmpty(); - } - - - private void assertThatFileRenameHasBeenDetected(Map reportFilesByUuid, Map databaseFilesByUuid, String fileInReportReference, String originalFileInDatabaseReference) { - Component fileInReport = reportFilesByUuid.get(fileInReportReference); - Component originalFileInDatabase = databaseFilesByUuid.get(originalFileInDatabaseReference); - - assertThat(movedFilesRepository.getComponentsWithOriginal()).contains(fileInReport); - assertThat(movedFilesRepository.getOriginalPullRequestFile(fileInReport)).isPresent(); - - OriginalFile detectedOriginalFile = movedFilesRepository.getOriginalPullRequestFile(fileInReport).get(); - assertThat(detectedOriginalFile.key()).isEqualTo(originalFileInDatabase.getKey()); - assertThat(detectedOriginalFile.uuid()).isEqualTo(originalFileInDatabase.getUuid()); - } - - private Map initializeTargetBranchDatabaseComponents(Set references) { - Set fileComponents = createFileComponents(references); - insertFileComponentsInDatabase(fileComponents); - return toFileComponentsByUuidMap(fileComponents); - } - - private Map initializeAnalysisReportComponents(Set refs) { - Set fileComponents = createFileComponents(refs); - insertFileComponentsInReport(fileComponents); - return toFileComponentsByUuidMap(fileComponents); - } - - private Map toFileComponentsByUuidMap(Set fileComponents) { - return fileComponents - .stream() - .collect(toMap(Component::getUuid, identity())); - } - - private static Set createFileComponents(Set references) { - return references - .stream() - .map(PullRequestFileMoveDetectionStepTest::createReportFileComponent) - .collect(toSet()); - } - - private static Component createReportFileComponent(FileReference fileReference) { - return builder(FILE, Integer.parseInt(fileReference.getReference())) - .setUuid(fileReference.getReference()) - .setName("report_path" + fileReference.getReference()) - .setFileAttributes(new FileAttributes(false, null, 1, false, composeComponentPath(fileReference.getPastReference()))) - .build(); - } - - private void insertFileComponentsInReport(Set files) { - treeRootHolder - .setRoot(builder(PROJECT, Integer.parseInt(ROOT_REF)) - .setUuid(project.uuid()) - .addChildren(files.toArray(Component[]::new)) - .build()); - } - - private Set insertFileComponentsInDatabase(Set files) { - return files - .stream() - .map(Component::getUuid) - .map(this::composeComponentDto) - .peek(this::insertComponentDto) - .peek(this::insertContentOfFileInDatabase) - .collect(toSet()); - } - - private void insertComponentDto(ComponentDto component) { - dbTester.components().insertComponent(component); - } - - private ComponentDto composeComponentDto(String uuid) { - return ComponentTesting - .newFileDto(project) - .setBranchUuid(branch.uuid()) - .setKey("key_" + uuid) - .setUuid(uuid) - .setPath(composeComponentPath(uuid)); - } - - @CheckForNull - private static String composeComponentPath(@Nullable String reference) { - return Optional.ofNullable(reference) - .map(r -> String.join("_", "path", r)) - .orElse(null); - } - - private FileSourceDto insertContentOfFileInDatabase(ComponentDto file) { - FileSourceDto fileSourceDto = composeFileSourceDto(file); - persistFileSourceDto(fileSourceDto); - return fileSourceDto; - } - - private static FileSourceDto composeFileSourceDto(ComponentDto file) { - return new FileSourceDto() - .setUuid(Uuids.createFast()) - .setFileUuid(file.uuid()) - .setProjectUuid(file.branchUuid()); - } - - private void persistFileSourceDto(FileSourceDto fileSourceDto) { - dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), fileSourceDto); - dbTester.commit(); - } - - private void preparePullRequestAnalysis(Analysis analysis) { - prepareAnalysis(PULL_REQUEST, analysis); - } - - private void prepareAnalysis(BranchType branch, Analysis analysis) { - mockBranchType(branch); - analysisMetadataHolder.setBaseAnalysis(analysis); - } - - private void mockBranchType(BranchType branchType) { - Branch branch = mock(Branch.class); - when(analysisMetadataHolder.getBranch()).thenReturn(branch); - when(analysisMetadataHolder.getBranch().getTargetBranchName()).thenReturn(TARGET_BRANCH); - when(analysisMetadataHolder.isPullRequest()).thenReturn(branchType == PULL_REQUEST); - when(analysisMetadataHolder.getProject()).thenReturn(Project.from(project)); - } - - @Immutable - private static class FileReference { - private final String reference; - private final String pastReference; - - private FileReference(String reference, @Nullable String pastReference) { - this.reference = reference; - this.pastReference = pastReference; - } - - public String getReference() { - return reference; - } - - @CheckForNull - public String getPastReference() { - return pastReference; - } - - public static FileReference of(String reference, String pastReference) { - return new FileReference(reference, pastReference); - } - - public static FileReference of(String reference) { - return new FileReference(reference, null); - } - } -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplTest.java deleted file mode 100644 index 91ee2234dc0..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/metric/MetricRepositoryImplTest.java +++ /dev/null @@ -1,214 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.metric; - -import java.util.List; -import java.util.Random; -import java.util.stream.Collectors; -import java.util.stream.IntStream; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.metric.MetricDto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -public class MetricRepositoryImplTest { - private static final String SOME_KEY = "some_key"; - private static final String SOME_UUID = "uuid"; - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private DbClient dbClient = dbTester.getDbClient(); - private MetricRepositoryImpl underTest = new MetricRepositoryImpl(dbClient); - - @Test - public void getByKey_throws_NPE_if_arg_is_null() { - assertThatThrownBy(() -> underTest.getByKey(null)) - .isInstanceOf(NullPointerException.class); - } - - @Test - public void getByKey_throws_ISE_if_start_has_not_been_called() { - assertThatThrownBy(() -> underTest.getByKey(SOME_KEY)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Metric cache has not been initialized"); - } - - @Test - public void getByKey_throws_ISE_of_Metric_does_not_exist() { - assertThatThrownBy(() -> { - underTest.start(); - underTest.getByKey(SOME_KEY); - }) - .isInstanceOf(IllegalStateException.class) - .hasMessage(String.format("Metric with key '%s' does not exist", SOME_KEY)); - } - - @Test - public void getByKey_throws_ISE_of_Metric_is_disabled() { - dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false)); - - underTest.start(); - - assertThatThrownBy(() -> underTest.getByKey("complexity")) - .isInstanceOf(IllegalStateException.class) - .hasMessage(String.format("Metric with key '%s' does not exist", "complexity")); - } - - @Test - public void getByKey_find_enabled_Metrics() { - MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true)); - MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true)); - - underTest.start(); - - assertThat(underTest.getByKey("ncloc").getUuid()).isEqualTo(ncloc.getUuid()); - assertThat(underTest.getByKey("coverage").getUuid()).isEqualTo(coverage.getUuid()); - } - - @Test - public void getById_throws_ISE_if_start_has_not_been_called() { - assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Metric cache has not been initialized"); - } - - @Test - public void getById_throws_ISE_of_Metric_does_not_exist() { - underTest.start(); - - assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID)) - .isInstanceOf(IllegalStateException.class) - .hasMessage(String.format("Metric with uuid '%s' does not exist", SOME_UUID)); - } - - @Test - public void getById_throws_ISE_of_Metric_is_disabled() { - dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false)); - - underTest.start(); - - assertThatThrownBy(() -> underTest.getByUuid(SOME_UUID)) - .isInstanceOf(IllegalStateException.class) - .hasMessage(String.format("Metric with uuid '%s' does not exist", SOME_UUID)); - } - - @Test - public void getById_find_enabled_Metrics() { - MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true)); - MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true)); - - underTest.start(); - - assertThat(underTest.getByUuid(ncloc.getUuid()).getKey()).isEqualTo("ncloc"); - assertThat(underTest.getByUuid(coverage.getUuid()).getKey()).isEqualTo("coverage"); - } - - @Test - public void getOptionalById_throws_ISE_if_start_has_not_been_called() { - assertThatThrownBy(() -> underTest.getOptionalByUuid(SOME_UUID)) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Metric cache has not been initialized"); - } - - @Test - public void getOptionalById_returns_empty_of_Metric_does_not_exist() { - underTest.start(); - - assertThat(underTest.getOptionalByUuid(SOME_UUID)).isEmpty(); - } - - @Test - public void getOptionalById_returns_empty_of_Metric_is_disabled() { - dbTester.measures().insertMetric(t -> t.setKey("complexity").setEnabled(false)); - - underTest.start(); - - assertThat(underTest.getOptionalByUuid(SOME_UUID)).isEmpty(); - } - - @Test - public void getOptionalById_find_enabled_Metrics() { - MetricDto ncloc = dbTester.measures().insertMetric(t -> t.setKey("ncloc").setEnabled(true)); - MetricDto coverage = dbTester.measures().insertMetric(t -> t.setKey("coverage").setEnabled(true)); - - underTest.start(); - - assertThat(underTest.getOptionalByUuid(ncloc.getUuid()).get().getKey()).isEqualTo("ncloc"); - assertThat(underTest.getOptionalByUuid(coverage.getUuid()).get().getKey()).isEqualTo("coverage"); - } - - @Test - public void get_all_metrics() { - List enabledMetrics = IntStream.range(0, 1 + new Random().nextInt(12)) - .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true))) - .toList(); - IntStream.range(0, 1 + new Random().nextInt(12)) - .forEach(i -> dbTester.measures().insertMetric(t -> t.setKey("key_disabled_" + i).setEnabled(false))); - - underTest.start(); - assertThat(underTest.getAll()) - .extracting(Metric::getKey) - .containsOnly(enabledMetrics.stream().map(MetricDto::getKey).toArray(String[]::new)); - } - - @Test - public void getMetricsByType_givenRatingType_returnRatingMetrics() { - List enabledMetrics = IntStream.range(0, 1 + new Random().nextInt(12)) - .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("RATING"))) - .toList(); - - underTest.start(); - assertThat(underTest.getMetricsByType(Metric.MetricType.RATING)) - .extracting(Metric::getKey) - .containsOnly(enabledMetrics.stream().map(MetricDto::getKey).toArray(String[]::new)); - } - - @Test - public void getMetricsByType_givenRatingTypeAndWantedMilisecType_returnEmptyList() { - IntStream.range(0, 1 + new Random().nextInt(12)) - .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("RATING"))) - .collect(Collectors.toList()); - - underTest.start(); - assertThat(underTest.getMetricsByType(Metric.MetricType.MILLISEC)).isEmpty(); - } - - @Test - public void getMetricsByType_givenOnlyMilisecTypeAndWantedRatingMetrics_returnEmptyList() { - IntStream.range(0, 1 + new Random().nextInt(12)) - .mapToObj(i -> dbTester.measures().insertMetric(t -> t.setKey("key_enabled_" + i).setEnabled(true).setValueType("MILISEC"))); - - underTest.start(); - assertThat(underTest.getMetricsByType(Metric.MetricType.RATING)).isEmpty(); - } - - @Test - public void getMetricsByType_givenMetricsAreNull_throwException() { - assertThatThrownBy(() -> underTest.getMetricsByType(Metric.MetricType.RATING)) - .isInstanceOf(IllegalStateException.class); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderTest.java deleted file mode 100644 index 93c4b40380c..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/scm/ScmInfoDbLoaderTest.java +++ /dev/null @@ -1,249 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.scm; - -import com.google.common.collect.ImmutableList; -import java.util.Collections; -import java.util.Iterator; -import java.util.List; -import java.util.Optional; -import javax.annotation.Nullable; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.log.LogTester; -import org.sonar.ce.task.projectanalysis.analysis.Analysis; -import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; -import org.sonar.ce.task.projectanalysis.analysis.Branch; -import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; -import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.ReferenceBranchComponentUuids; -import org.sonar.ce.task.projectanalysis.filemove.MutableMovedFilesRepositoryRule; -import org.sonar.ce.task.projectanalysis.period.NewCodeReferenceBranchComponentUuids; -import org.sonar.ce.task.projectanalysis.period.Period; -import org.sonar.ce.task.projectanalysis.period.PeriodHolderRule; -import org.sonar.core.hash.SourceHashComputer; -import org.sonar.core.util.Uuids; -import org.sonar.db.DbTester; -import org.sonar.db.component.BranchType; -import org.sonar.db.newcodeperiod.NewCodePeriodType; -import org.sonar.db.protobuf.DbFileSources; -import org.sonar.db.source.FileSourceDto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.api.utils.log.LoggerLevel.TRACE; -import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; - -public class ScmInfoDbLoaderTest { - static final int FILE_REF = 1; - static final Component FILE = builder(Component.Type.FILE, FILE_REF).setKey("FILE_KEY").setUuid("FILE_UUID").build(); - static final long DATE_1 = 123456789L; - - static Analysis baseProjectAnalysis = new Analysis.Builder() - .setUuid("uuid_1") - .setCreatedAt(123456789L) - .build(); - - @Rule - public LogTester logTester = new LogTester(); - @Rule - public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - @Rule - public BatchReportReaderRule reportReader = new BatchReportReaderRule(); - @Rule - public MutableMovedFilesRepositoryRule movedFiles = new MutableMovedFilesRepositoryRule(); - @Rule - public PeriodHolderRule periodHolder = new PeriodHolderRule(); - - private final Branch branch = mock(Branch.class); - private final ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class); - private final NewCodeReferenceBranchComponentUuids newCodeReferenceBranchComponentUuids = mock(NewCodeReferenceBranchComponentUuids.class); - - private final ScmInfoDbLoader underTest = new ScmInfoDbLoader(analysisMetadataHolder, movedFiles, dbTester.getDbClient(), referenceBranchComponentUuids, - newCodeReferenceBranchComponentUuids, periodHolder); - - @Before - public void before() { - periodHolder.setPeriod(new Period(NewCodePeriodType.PREVIOUS_VERSION.name(), null, null)); - } - - @Test - public void returns_ScmInfo_from_DB() { - analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); - analysisMetadataHolder.setBranch(null); - - String hash = computeSourceHash(1); - addFileSourceInDb("henry", DATE_1, "rev-1", hash); - - DbScmInfo scmInfo = underTest.getScmInfo(FILE).get(); - assertThat(scmInfo.getAllChangesets()).hasSize(1); - assertThat(scmInfo.fileHash()).isEqualTo(hash); - - assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'"); - } - - @Test - public void read_from_reference_branch_if_no_base() { - analysisMetadataHolder.setBaseAnalysis(null); - analysisMetadataHolder.setBranch(branch); - - String referenceFileUuid = "referenceFileUuid"; - String hash = computeSourceHash(1); - - when(referenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(referenceFileUuid); - addFileSourceInDb("henry", DATE_1, "rev-1", hash, referenceFileUuid); - - DbScmInfo scmInfo = underTest.getScmInfo(FILE).get(); - assertThat(scmInfo.getAllChangesets()).hasSize(1); - assertThat(scmInfo.fileHash()).isEqualTo(hash); - assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'referenceFileUuid'"); - } - - @Test - public void read_from_target_if_pullrequest() { - Branch branch = mock(Branch.class); - when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); - analysisMetadataHolder.setBaseAnalysis(null); - analysisMetadataHolder.setBranch(branch); - - String targetBranchFileUuid = "targetBranchFileUuid"; - String hash = computeSourceHash(1); - - when(referenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(targetBranchFileUuid); - addFileSourceInDb("henry", DATE_1, "rev-1", hash, targetBranchFileUuid); - - DbScmInfo scmInfo = underTest.getScmInfo(FILE).get(); - assertThat(scmInfo.getAllChangesets()).hasSize(1); - assertThat(scmInfo.fileHash()).isEqualTo(hash); - assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'targetBranchFileUuid'"); - } - - @Test - public void read_from_target_if_reference_branch() { - periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), null, null)); - - Branch branch = mock(Branch.class); - when(branch.getType()).thenReturn(BranchType.BRANCH); - analysisMetadataHolder.setBaseAnalysis(null); - analysisMetadataHolder.setBranch(branch); - - String targetBranchFileUuid = "targetBranchFileUuid"; - String hash = computeSourceHash(1); - - when(newCodeReferenceBranchComponentUuids.getComponentUuid(FILE.getKey())).thenReturn(targetBranchFileUuid); - addFileSourceInDb("henry", DATE_1, "rev-1", hash, targetBranchFileUuid); - - DbScmInfo scmInfo = underTest.getScmInfo(FILE).get(); - assertThat(scmInfo.getAllChangesets()).hasSize(1); - assertThat(scmInfo.fileHash()).isEqualTo(hash); - assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'targetBranchFileUuid'"); - } - - @Test - public void read_from_db_if_not_exist_in_reference_branch() { - periodHolder.setPeriod(new Period(NewCodePeriodType.REFERENCE_BRANCH.name(), null, null)); - - Branch branch = mock(Branch.class); - when(branch.getType()).thenReturn(BranchType.BRANCH); - analysisMetadataHolder.setBaseAnalysis(null); - analysisMetadataHolder.setBranch(branch); - - String hash = computeSourceHash(1); - - addFileSourceInDb("henry", DATE_1, "rev-1", hash, FILE.getUuid()); - - DbScmInfo scmInfo = underTest.getScmInfo(FILE).get(); - assertThat(scmInfo.getAllChangesets()).hasSize(1); - assertThat(scmInfo.fileHash()).isEqualTo(hash); - assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'"); - } - - @Test - public void return_empty_if_no_dto_available() { - analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); - analysisMetadataHolder.setBranch(null); - - Optional scmInfo = underTest.getScmInfo(FILE); - - assertThat(logTester.logs(TRACE)).containsOnly("Reading SCM info from DB for file 'FILE_UUID'"); - assertThat(scmInfo).isEmpty(); - } - - @Test - public void do_not_read_from_db_on_first_analysis_if_there_is_no_reference_branch() { - Branch branch = mock(Branch.class); - when(branch.getType()).thenReturn(BranchType.PULL_REQUEST); - analysisMetadataHolder.setBaseAnalysis(null); - analysisMetadataHolder.setBranch(branch); - - assertThat(underTest.getScmInfo(FILE)).isEmpty(); - assertThat(logTester.logs(TRACE)).isEmpty(); - } - - private static List generateLines(int lineCount) { - ImmutableList.Builder builder = ImmutableList.builder(); - for (int i = 0; i < lineCount; i++) { - builder.add("line " + i); - } - return builder.build(); - } - - private static String computeSourceHash(int lineCount) { - SourceHashComputer sourceHashComputer = new SourceHashComputer(); - Iterator lines = generateLines(lineCount).iterator(); - while (lines.hasNext()) { - sourceHashComputer.addLine(lines.next(), lines.hasNext()); - } - return sourceHashComputer.getHash(); - } - - private void addFileSourceInDb(@Nullable String author, @Nullable Long date, @Nullable String revision, String srcHash) { - addFileSourceInDb(author, date, revision, srcHash, FILE.getUuid()); - } - - private void addFileSourceInDb(@Nullable String author, @Nullable Long date, @Nullable String revision, String srcHash, String fileUuid) { - DbFileSources.Data.Builder fileDataBuilder = DbFileSources.Data.newBuilder(); - DbFileSources.Line.Builder builder = fileDataBuilder.addLinesBuilder() - .setLine(1); - if (author != null) { - builder.setScmAuthor(author); - } - if (date != null) { - builder.setScmDate(date); - } - if (revision != null) { - builder.setScmRevision(revision); - } - dbTester.getDbClient().fileSourceDao().insert(dbTester.getSession(), new FileSourceDto() - .setUuid(Uuids.createFast()) - .setLineHashes(Collections.singletonList("lineHash")) - .setFileUuid(fileUuid) - .setProjectUuid("PROJECT_UUID") - .setSourceData(fileDataBuilder.build()) - .setSrcHash(srcHash)); - dbTester.commit(); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java deleted file mode 100644 index 6d7970d8293..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java +++ /dev/null @@ -1,586 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.step; - -import com.tngtech.java.junit.dataprovider.DataProvider; -import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import com.tngtech.java.junit.dataprovider.UseDataProvider; -import java.util.HashMap; -import java.util.Map; -import java.util.Optional; -import javax.annotation.Nullable; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.sonar.api.utils.System2; -import org.sonar.ce.task.projectanalysis.analysis.Branch; -import org.sonar.ce.task.projectanalysis.analysis.MutableAnalysisMetadataHolderRule; -import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; -import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl; -import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule; -import org.sonar.ce.task.step.TestComputationStepContext; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.component.BranchType; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.component.SnapshotDto; -import org.sonar.scanner.protocol.output.ScannerReport; -import org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType; -import org.sonar.scanner.protocol.output.ScannerReport.Component.FileStatus; -import org.sonar.server.project.Project; - -import static java.util.Optional.ofNullable; -import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.db.component.BranchDto.DEFAULT_MAIN_BRANCH_NAME; -import static org.sonar.db.component.ComponentTesting.newDirectory; -import static org.sonar.db.component.ComponentTesting.newFileDto; -import static org.sonar.db.component.ComponentTesting.newPrivateProjectDto; -import static org.sonar.db.component.SnapshotTesting.newAnalysis; -import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.FILE; -import static org.sonar.scanner.protocol.output.ScannerReport.Component.ComponentType.PROJECT; - -@RunWith(DataProviderRunner.class) -public class BuildComponentTreeStepTest { - private static final String NO_SCANNER_PROJECT_VERSION = null; - private static final String NO_SCANNER_BUILD_STRING = null; - - private static final int ROOT_REF = 1; - private static final int FILE_1_REF = 4; - private static final int FILE_2_REF = 5; - private static final int FILE_3_REF = 7; - private static final int UNCHANGED_FILE_REF = 10; - - private static final String REPORT_PROJECT_KEY = "REPORT_PROJECT_KEY"; - private static final String REPORT_DIR_PATH_1 = "src/main/java/dir1"; - private static final String REPORT_FILE_PATH_1 = "src/main/java/dir1/File1.java"; - private static final String REPORT_FILE_NAME_1 = "File1.java"; - private static final String REPORT_DIR_PATH_2 = "src/main/java/dir2"; - private static final String REPORT_FILE_PATH_2 = "src/main/java/dir2/File2.java"; - private static final String REPORT_FILE_PATH_3 = "src/main/java/dir2/File3.java"; - private static final String REPORT_UNCHANGED_FILE_PATH = "src/main/File3.java"; - - private static final long ANALYSIS_DATE = 123456789L; - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - @Rule - public BatchReportReaderRule reportReader = new BatchReportReaderRule().setMetadata(createReportMetadata(NO_SCANNER_PROJECT_VERSION, NO_SCANNER_BUILD_STRING)); - @Rule - public MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule(); - @Rule - public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule(); - - private DbClient dbClient = dbTester.getDbClient(); - private BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder); - - @Test - public void fails_if_root_component_does_not_exist_in_reportReader() { - setAnalysisMetadataHolder(); - - assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext())) - .isInstanceOf(NullPointerException.class); - } - - @Test - public void verify_tree_is_correctly_built() { - setAnalysisMetadataHolder(); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF, FILE_3_REF)); - reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); - reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2)); - reportReader.putComponent(componentWithPath(FILE_3_REF, FILE, REPORT_FILE_PATH_3)); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - Component root = treeRootHolder.getRoot(); - assertThat(root).isNotNull(); - verifyComponent(root, Component.Type.PROJECT, ROOT_REF, 1); - - Component dir = root.getChildren().iterator().next(); - verifyComponent(dir, Component.Type.DIRECTORY, null, 2); - - Component dir1 = dir.getChildren().get(0); - verifyComponent(dir1, Component.Type.DIRECTORY, null, 1); - verifyComponent(dir1.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0); - - Component dir2 = dir.getChildren().get(1); - verifyComponent(dir2, Component.Type.DIRECTORY, null, 2); - verifyComponent(dir2.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0); - verifyComponent(dir2.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0); - - context.getStatistics().assertValue("components", 7); - } - - @Test - public void verify_tree_is_correctly_built_in_prs() { - setAnalysisMetadataHolder(true); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF, FILE_3_REF, UNCHANGED_FILE_REF)); - reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); - reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2)); - reportReader.putComponent(componentWithPath(FILE_3_REF, FILE, REPORT_FILE_PATH_3)); - reportReader.putComponent(unchangedComponentWithPath(UNCHANGED_FILE_REF, FILE, REPORT_UNCHANGED_FILE_PATH)); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - // modified root - Component mRoot = treeRootHolder.getRoot(); - verifyComponent(mRoot, Component.Type.PROJECT, ROOT_REF, 1); - - Component mDir = mRoot.getChildren().get(0); - assertThat(mDir.getName()).isEqualTo("src/main/java"); - verifyComponent(mDir, Component.Type.DIRECTORY, null, 2); - - Component mDir1 = mDir.getChildren().get(0); - assertThat(mDir1.getName()).isEqualTo("src/main/java/dir1"); - verifyComponent(mDir1, Component.Type.DIRECTORY, null, 1); - verifyComponent(mDir1.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0); - - Component mDir2 = mDir.getChildren().get(1); - assertThat(mDir2.getName()).isEqualTo("src/main/java/dir2"); - verifyComponent(mDir2, Component.Type.DIRECTORY, null, 2); - verifyComponent(mDir2.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0); - verifyComponent(mDir2.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0); - - // root - Component root = treeRootHolder.getReportTreeRoot(); - verifyComponent(root, Component.Type.PROJECT, ROOT_REF, 1); - - Component dir = root.getChildren().get(0); - assertThat(dir.getName()).isEqualTo("src/main"); - verifyComponent(dir, Component.Type.DIRECTORY, null, 2); - - Component dir1 = dir.getChildren().get(0); - assertThat(dir1.getName()).isEqualTo("src/main/java"); - verifyComponent(dir1, Component.Type.DIRECTORY, null, 2); - verifyComponent(dir1.getChildren().get(0), Component.Type.DIRECTORY, null, 1); - verifyComponent(dir1.getChildren().get(1), Component.Type.DIRECTORY, null, 2); - - Component dir2 = dir1.getChildren().get(0); - assertThat(dir2.getName()).isEqualTo("src/main/java/dir1"); - verifyComponent(dir2, Component.Type.DIRECTORY, null, 1); - verifyComponent(dir2.getChildren().get(0), Component.Type.FILE, FILE_1_REF, 0); - - Component dir3 = dir1.getChildren().get(1); - assertThat(dir3.getName()).isEqualTo("src/main/java/dir2"); - verifyComponent(dir3, Component.Type.DIRECTORY, null, 2); - verifyComponent(dir3.getChildren().get(0), Component.Type.FILE, FILE_2_REF, 0); - verifyComponent(dir3.getChildren().get(1), Component.Type.FILE, FILE_3_REF, 0); - - context.getStatistics().assertValue("components", 7); - } - - /** - * SONAR-13262 - */ - @Test - public void verify_tree_is_correctly_built_in_prs_with_repeated_names() { - setAnalysisMetadataHolder(true); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); - reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_PROJECT_KEY + "/file.js")); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - // modified root - Component mRoot = treeRootHolder.getRoot(); - verifyComponent(mRoot, Component.Type.PROJECT, ROOT_REF, 1); - - Component dir = mRoot.getChildren().get(0); - assertThat(dir.getName()).isEqualTo(REPORT_PROJECT_KEY); - assertThat(dir.getShortName()).isEqualTo(REPORT_PROJECT_KEY); - - verifyComponent(dir, Component.Type.DIRECTORY, null, 1); - } - - @Test - public void compute_keys_and_uuids() { - setAnalysisMetadataHolder(); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); - reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); - - underTest.execute(new TestComputationStepContext()); - - verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName()); - verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1); - verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1); - } - - @Test - public void return_existing_uuids() { - setAnalysisMetadataHolder(); - ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY)); - ComponentDto directory = newDirectory(project, "CDEF", REPORT_DIR_PATH_1); - insertComponent(directory.setKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1)); - insertComponent(newFileDto(project, directory, "DEFG") - .setKey(REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1) - .setPath(REPORT_FILE_PATH_1)); - - // new structure, without modules - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); - reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); - - underTest.execute(new TestComputationStepContext()); - - verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName(), "ABCD"); - verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1, "CDEF"); - verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, "DEFG"); - } - - @Test - public void generate_keys_when_using_new_branch() { - Branch branch = mock(Branch.class); - when(branch.getName()).thenReturn("origin/feature"); - when(branch.isMain()).thenReturn(false); - when(branch.generateKey(any(), any())).thenReturn("generated"); - analysisMetadataHolder.setRootComponentRef(ROOT_REF) - .setAnalysisDate(ANALYSIS_DATE) - .setProject(Project.from(newPrivateProjectDto().setKey(REPORT_PROJECT_KEY))) - .setBranch(branch); - BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); - reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); - - underTest.execute(new TestComputationStepContext()); - - verifyComponentByRef(ROOT_REF, "generated", analysisMetadataHolder.getProject().getName(), null); - verifyComponentByRef(FILE_1_REF, "generated", REPORT_FILE_NAME_1, null); - } - - @Test - public void generate_keys_when_using_existing_branch() { - ComponentDto projectDto = dbTester.components().insertPublicProject(); - String branchName = randomAlphanumeric(248); - ComponentDto componentDto = dbTester.components().insertProjectBranch(projectDto, b -> b.setKey(branchName)); - Branch branch = mock(Branch.class); - when(branch.getName()).thenReturn(branchName); - when(branch.isMain()).thenReturn(false); - when(branch.generateKey(any(), any())).thenReturn(componentDto.getKey()); - analysisMetadataHolder.setRootComponentRef(ROOT_REF) - .setAnalysisDate(ANALYSIS_DATE) - .setProject(Project.from(projectDto)) - .setBranch(branch); - BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder); - reportReader.putComponent(component(ROOT_REF, PROJECT, componentDto.getKey())); - - underTest.execute(new TestComputationStepContext()); - - verifyComponentByRef(ROOT_REF, componentDto.getKey(), analysisMetadataHolder.getProject().getName(), componentDto.uuid()); - } - - @Test - public void generate_keys_when_using_main_branch() { - setAnalysisMetadataHolder(); - BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); - reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); - - underTest.execute(new TestComputationStepContext()); - - verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName(), null); - verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1); - verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1, null); - } - - @Test - public void compute_keys_and_uuids_on_project_having_module_and_directory() { - setAnalysisMetadataHolder(); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF, FILE_2_REF)); - reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); - reportReader.putComponent(componentWithPath(FILE_2_REF, FILE, REPORT_FILE_PATH_2)); - - underTest.execute(new TestComputationStepContext()); - - verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName()); - verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, "dir1"); - verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1); - verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_2, "dir2"); - verifyComponentByRef(FILE_2_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_2, "File2.java"); - } - - @Test - public void compute_keys_and_uuids_on_multi_modules() { - setAnalysisMetadataHolder(); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, FILE_1_REF)); - reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, REPORT_FILE_PATH_1)); - - underTest.execute(new TestComputationStepContext()); - - verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, analysisMetadataHolder.getProject().getName()); - verifyComponentByKey(REPORT_PROJECT_KEY + ":" + REPORT_DIR_PATH_1, REPORT_DIR_PATH_1); - verifyComponentByRef(FILE_1_REF, REPORT_PROJECT_KEY + ":" + REPORT_FILE_PATH_1, REPORT_FILE_NAME_1); - } - - @Test - public void set_no_base_project_snapshot_when_no_snapshot() { - setAnalysisMetadataHolder(); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); - underTest.execute(new TestComputationStepContext()); - - assertThat(analysisMetadataHolder.isFirstAnalysis()).isTrue(); - } - - @Test - public void set_no_base_project_snapshot_when_no_last_snapshot() { - setAnalysisMetadataHolder(); - ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY)); - insertSnapshot(newAnalysis(project).setLast(false)); - - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); - underTest.execute(new TestComputationStepContext()); - - assertThat(analysisMetadataHolder.isFirstAnalysis()).isTrue(); - } - - @Test - public void set_base_project_snapshot_when_last_snapshot_exist() { - setAnalysisMetadataHolder(); - ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY)); - insertSnapshot(newAnalysis(project).setLast(true)); - - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); - underTest.execute(new TestComputationStepContext()); - - assertThat(analysisMetadataHolder.isFirstAnalysis()).isFalse(); - } - - @Test - public void set_projectVersion_to_not_provided_when_not_set_on_first_analysis() { - setAnalysisMetadataHolder(); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); - - underTest.execute(new TestComputationStepContext()); - - assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion()).isEqualTo("not provided"); - } - - @Test - @UseDataProvider("oneParameterNullNonNullCombinations") - public void set_projectVersion_to_previous_analysis_when_not_set(@Nullable String previousAnalysisProjectVersion) { - setAnalysisMetadataHolder(); - ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY)); - insertSnapshot(newAnalysis(project).setProjectVersion(previousAnalysisProjectVersion).setLast(true)); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); - - underTest.execute(new TestComputationStepContext()); - - String projectVersion = treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion(); - if (previousAnalysisProjectVersion == null) { - assertThat(projectVersion).isEqualTo("not provided"); - } else { - assertThat(projectVersion).isEqualTo(previousAnalysisProjectVersion); - } - } - - @Test - public void set_projectVersion_when_it_is_set_on_first_analysis() { - String scannerProjectVersion = randomAlphabetic(12); - setAnalysisMetadataHolder(); - reportReader.setMetadata(createReportMetadata(scannerProjectVersion, NO_SCANNER_BUILD_STRING)); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); - - underTest.execute(new TestComputationStepContext()); - - assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion()) - .isEqualTo(scannerProjectVersion); - } - - @Test - @UseDataProvider("oneParameterNullNonNullCombinations") - public void set_projectVersion_when_it_is_set_on_later_analysis(@Nullable String previousAnalysisProjectVersion) { - String scannerProjectVersion = randomAlphabetic(12); - setAnalysisMetadataHolder(); - reportReader.setMetadata(createReportMetadata(scannerProjectVersion, NO_SCANNER_BUILD_STRING)); - ComponentDto project = insertComponent(newPrivateProjectDto("ABCD").setKey(REPORT_PROJECT_KEY)); - insertSnapshot(newAnalysis(project).setProjectVersion(previousAnalysisProjectVersion).setLast(true)); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); - - underTest.execute(new TestComputationStepContext()); - - assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getProjectVersion()) - .isEqualTo(scannerProjectVersion); - } - - @Test - @UseDataProvider("oneParameterNullNonNullCombinations") - public void set_buildString(@Nullable String buildString) { - String projectVersion = randomAlphabetic(7); - setAnalysisMetadataHolder(); - reportReader.setMetadata(createReportMetadata(projectVersion, buildString)); - reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY)); - - underTest.execute(new TestComputationStepContext()); - - assertThat(treeRootHolder.getReportTreeRoot().getProjectAttributes().getBuildString()).isEqualTo(Optional.ofNullable(buildString)); - } - - @DataProvider - public static Object[][] oneParameterNullNonNullCombinations() { - return new Object[][] { - {null}, - {randomAlphabetic(7)} - }; - } - - private void verifyComponent(Component component, Component.Type type, @Nullable Integer componentRef, int size) { - assertThat(component.getType()).isEqualTo(type); - assertThat(component.getReportAttributes().getRef()).isEqualTo(componentRef); - assertThat(component.getChildren()).hasSize(size); - } - - private void verifyComponentByRef(int ref, String key, String shortName) { - verifyComponentByRef(ref, key, shortName, null); - } - - private void verifyComponentByKey(String key, String shortName) { - verifyComponentByKey(key, shortName, null); - } - - private void verifyComponentByKey(String key, String shortName, @Nullable String uuid) { - Map componentsByKey = indexAllComponentsInTreeByKey(treeRootHolder.getRoot()); - Component component = componentsByKey.get(key); - assertThat(component.getKey()).isEqualTo(key); - assertThat(component.getReportAttributes().getRef()).isNull(); - assertThat(component.getShortName()).isEqualTo(shortName); - if (uuid != null) { - assertThat(component.getUuid()).isEqualTo(uuid); - } else { - assertThat(component.getUuid()).isNotNull(); - } - } - - private void verifyComponentByRef(int ref, String key, String shortName, @Nullable String uuid) { - Map componentsByRef = indexAllComponentsInTreeByRef(treeRootHolder.getRoot()); - Component component = componentsByRef.get(ref); - assertThat(component.getKey()).isEqualTo(key); - assertThat(component.getShortName()).isEqualTo(shortName); - if (uuid != null) { - assertThat(component.getUuid()).isEqualTo(uuid); - } else { - assertThat(component.getUuid()).isNotNull(); - } - } - - private static ScannerReport.Component component(int componentRef, ComponentType componentType, String key, int... children) { - return component(componentRef, componentType, key, FileStatus.CHANGED, null, children); - } - - private static ScannerReport.Component unchangedComponentWithPath(int componentRef, ComponentType componentType, String path, int... children) { - return component(componentRef, componentType, REPORT_PROJECT_KEY + ":" + path, FileStatus.SAME, path, children); - } - - private static ScannerReport.Component componentWithPath(int componentRef, ComponentType componentType, String path, int... children) { - return component(componentRef, componentType, REPORT_PROJECT_KEY + ":" + path, FileStatus.CHANGED, path, children); - } - - private static ScannerReport.Component component(int componentRef, ComponentType componentType, String key, FileStatus status, @Nullable String path, int... children) { - ScannerReport.Component.Builder builder = ScannerReport.Component.newBuilder() - .setType(componentType) - .setRef(componentRef) - .setName(key) - .setStatus(status) - .setLines(1) - .setKey(key); - if (path != null) { - builder.setProjectRelativePath(path); - } - for (int child : children) { - builder.addChildRef(child); - } - return builder.build(); - } - - private static Map indexAllComponentsInTreeByRef(Component root) { - Map componentsByRef = new HashMap<>(); - feedComponentByRef(root, componentsByRef); - return componentsByRef; - } - - private static Map indexAllComponentsInTreeByKey(Component root) { - Map componentsByKey = new HashMap<>(); - feedComponentByKey(root, componentsByKey); - return componentsByKey; - } - - private static void feedComponentByKey(Component component, Map map) { - map.put(component.getKey(), component); - for (Component child : component.getChildren()) { - feedComponentByKey(child, map); - } - } - - private static void feedComponentByRef(Component component, Map map) { - if (component.getReportAttributes().getRef() != null) { - map.put(component.getReportAttributes().getRef(), component); - } - for (Component child : component.getChildren()) { - feedComponentByRef(child, map); - } - } - - private ComponentDto insertComponent(ComponentDto component) { - return dbTester.components().insertComponent(component); - } - - private SnapshotDto insertSnapshot(SnapshotDto snapshot) { - dbClient.snapshotDao().insert(dbTester.getSession(), snapshot); - dbTester.getSession().commit(); - return snapshot; - } - - private void setAnalysisMetadataHolder() { - setAnalysisMetadataHolder(false); - } - - private void setAnalysisMetadataHolder(boolean isPr) { - Branch branch = isPr ? new PrBranch(DEFAULT_MAIN_BRANCH_NAME) : new DefaultBranchImpl(DEFAULT_MAIN_BRANCH_NAME); - analysisMetadataHolder.setRootComponentRef(ROOT_REF) - .setAnalysisDate(ANALYSIS_DATE) - .setBranch(branch) - .setProject(Project.from(newPrivateProjectDto().setKey(REPORT_PROJECT_KEY).setName(REPORT_PROJECT_KEY))); - } - - public static ScannerReport.Metadata createReportMetadata(@Nullable String projectVersion, @Nullable String buildString) { - ScannerReport.Metadata.Builder builder = ScannerReport.Metadata.newBuilder() - .setProjectKey(REPORT_PROJECT_KEY) - .setRootComponentRef(ROOT_REF); - ofNullable(projectVersion).ifPresent(builder::setProjectVersion); - ofNullable(buildString).ifPresent(builder::setBuildString); - return builder.build(); - } - - private static class PrBranch extends DefaultBranchImpl { - public PrBranch(String branch) { - super(branch); - } - - @Override - public BranchType getType() { - return BranchType.PULL_REQUEST; - } - } -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepTest.java deleted file mode 100644 index 339e122a292..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadCrossProjectDuplicationsRepositoryStepTest.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.step; - -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.mockito.ArgumentCaptor; -import org.sonar.api.utils.System2; -import org.sonar.ce.task.projectanalysis.analysis.Analysis; -import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; -import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; -import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.FileAttributes; -import org.sonar.ce.task.projectanalysis.component.ReportComponent; -import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; -import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolder; -import org.sonar.ce.task.projectanalysis.duplication.IntegrateCrossProjectDuplications; -import org.sonar.ce.task.step.ComputationStep; -import org.sonar.ce.task.step.TestComputationStepContext; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.component.ComponentTesting; -import org.sonar.db.component.SnapshotDto; -import org.sonar.db.component.SnapshotTesting; -import org.sonar.db.duplication.DuplicationUnitDto; -import org.sonar.duplications.block.Block; -import org.sonar.duplications.block.ByteArray; -import org.sonar.scanner.protocol.output.ScannerReport; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; -import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE; -import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT; - -public class LoadCrossProjectDuplicationsRepositoryStepTest { - - - private static final String XOO_LANGUAGE = "xoo"; - private static final int PROJECT_REF = 1; - private static final int FILE_REF = 2; - private static final String CURRENT_FILE_KEY = "FILE_KEY"; - - private static final Component CURRENT_FILE = ReportComponent.builder(FILE, FILE_REF) - .setKey(CURRENT_FILE_KEY) - .setFileAttributes(new FileAttributes(false, XOO_LANGUAGE, 1)) - .build(); - - @Rule - public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot( - ReportComponent.builder(PROJECT, PROJECT_REF) - .addChildren(CURRENT_FILE).build()); - - @Rule - public BatchReportReaderRule batchReportReader = new BatchReportReaderRule(); - - @Rule - public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); - - private CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder = mock(CrossProjectDuplicationStatusHolder.class); - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private DbClient dbClient = dbTester.getDbClient(); - private DbSession dbSession = dbTester.getSession(); - private IntegrateCrossProjectDuplications integrateCrossProjectDuplications = mock(IntegrateCrossProjectDuplications.class); - private Analysis baseProjectAnalysis; - - private ComputationStep underTest = new LoadCrossProjectDuplicationsRepositoryStep(treeRootHolder, batchReportReader, analysisMetadataHolder, crossProjectDuplicationStatusHolder, - integrateCrossProjectDuplications, dbClient); - - @Before - public void setUp() { - ComponentDto project = ComponentTesting.newPrivateProjectDto(); - dbTester.components().insertComponent(project); - SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(project); - dbClient.snapshotDao().insert(dbSession, projectSnapshot); - dbSession.commit(); - - baseProjectAnalysis = new Analysis.Builder() - .setUuid(projectSnapshot.getUuid()) - .setCreatedAt(projectSnapshot.getCreatedAt()) - .build(); - } - - @Test - public void call_compute_cpd_on_one_duplication() { - when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); - analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); - - ComponentDto otherProject = createProject("OTHER_PROJECT_KEY"); - SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject); - - ComponentDto otherFile = createFile("OTHER_FILE_KEY", otherProject); - - String hash = "a8998353e96320ec"; - DuplicationUnitDto duplicate = new DuplicationUnitDto() - .setHash(hash) - .setStartLine(40) - .setEndLine(55) - .setIndexInFile(0) - .setAnalysisUuid(otherProjectSnapshot.getUuid()) - .setComponentUuid(otherFile.uuid()); - dbClient.duplicationDao().insert(dbSession, duplicate); - dbSession.commit(); - - ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder() - .setHash(hash) - .setStartLine(30) - .setEndLine(45) - .setStartTokenIndex(0) - .setEndTokenIndex(10) - .build(); - batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock)); - - underTest.execute(new TestComputationStepContext()); - - verify(integrateCrossProjectDuplications).computeCpd(CURRENT_FILE, - singletonList( - new Block.Builder() - .setResourceId(CURRENT_FILE_KEY) - .setBlockHash(new ByteArray(hash)) - .setIndexInFile(0) - .setLines(originBlock.getStartLine(), originBlock.getEndLine()) - .setUnit(originBlock.getStartTokenIndex(), originBlock.getEndTokenIndex()) - .build()), - singletonList( - new Block.Builder() - .setResourceId(otherFile.getKey()) - .setBlockHash(new ByteArray(hash)) - .setIndexInFile(duplicate.getIndexInFile()) - .setLines(duplicate.getStartLine(), duplicate.getEndLine()) - .build())); - } - - @Test - public void call_compute_cpd_on_many_duplication() { - when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); - analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); - - ComponentDto otherProject = createProject("OTHER_PROJECT_KEY"); - SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject); - - ComponentDto otherFile = createFile("OTHER_FILE_KEY", otherProject); - - ScannerReport.CpdTextBlock originBlock1 = ScannerReport.CpdTextBlock.newBuilder() - .setHash("a8998353e96320ec") - .setStartLine(30) - .setEndLine(45) - .setStartTokenIndex(0) - .setEndTokenIndex(10) - .build(); - ScannerReport.CpdTextBlock originBlock2 = ScannerReport.CpdTextBlock.newBuilder() - .setHash("b1234353e96320ff") - .setStartLine(10) - .setEndLine(25) - .setStartTokenIndex(5) - .setEndTokenIndex(15) - .build(); - batchReportReader.putDuplicationBlocks(FILE_REF, asList(originBlock1, originBlock2)); - - DuplicationUnitDto duplicate1 = new DuplicationUnitDto() - .setHash(originBlock1.getHash()) - .setStartLine(40) - .setEndLine(55) - .setIndexInFile(0) - .setAnalysisUuid(otherProjectSnapshot.getUuid()) - .setComponentUuid(otherFile.uuid()); - - DuplicationUnitDto duplicate2 = new DuplicationUnitDto() - .setHash(originBlock2.getHash()) - .setStartLine(20) - .setEndLine(35) - .setIndexInFile(1) - .setAnalysisUuid(otherProjectSnapshot.getUuid()) - .setComponentUuid(otherFile.uuid()); - dbClient.duplicationDao().insert(dbSession, duplicate1); - dbClient.duplicationDao().insert(dbSession, duplicate2); - dbSession.commit(); - - underTest.execute(new TestComputationStepContext()); - - ArgumentCaptor> originBlocks = ArgumentCaptor.forClass(List.class); - ArgumentCaptor> duplicationBlocks = ArgumentCaptor.forClass(List.class); - - verify(integrateCrossProjectDuplications).computeCpd(eq(CURRENT_FILE), originBlocks.capture(), duplicationBlocks.capture()); - - Map originBlocksByIndex = blocksByIndexInFile(originBlocks.getValue()); - assertThat(originBlocksByIndex).containsExactly( - Map.entry(0, new Block.Builder() - .setResourceId(CURRENT_FILE_KEY) - .setBlockHash(new ByteArray(originBlock1.getHash())) - .setIndexInFile(0) - .setLines(originBlock1.getStartLine(), originBlock1.getEndLine()) - .setUnit(originBlock1.getStartTokenIndex(), originBlock1.getEndTokenIndex()) - .build()), - Map.entry(1, new Block.Builder() - .setResourceId(CURRENT_FILE_KEY) - .setBlockHash(new ByteArray(originBlock2.getHash())) - .setIndexInFile(1) - .setLines(originBlock2.getStartLine(), originBlock2.getEndLine()) - .setUnit(originBlock2.getStartTokenIndex(), originBlock2.getEndTokenIndex()) - .build())); - - Map duplicationBlocksByIndex = blocksByIndexInFile(duplicationBlocks.getValue()); - assertThat(duplicationBlocksByIndex).containsExactly( - Map.entry(0, new Block.Builder() - .setResourceId(otherFile.getKey()) - .setBlockHash(new ByteArray(originBlock1.getHash())) - .setIndexInFile(duplicate1.getIndexInFile()) - .setLines(duplicate1.getStartLine(), duplicate1.getEndLine()) - .build()), - Map.entry(1, new Block.Builder() - .setResourceId(otherFile.getKey()) - .setBlockHash(new ByteArray(originBlock2.getHash())) - .setIndexInFile(duplicate2.getIndexInFile()) - .setLines(duplicate2.getStartLine(), duplicate2.getEndLine()) - .build())); - } - - @Test - public void nothing_to_do_when_cross_project_duplication_is_disabled() { - when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(false); - analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); - - ComponentDto otherProject = createProject("OTHER_PROJECT_KEY"); - SnapshotDto otherProjectSnapshot = createProjectSnapshot(otherProject); - - ComponentDto otherFIle = createFile("OTHER_FILE_KEY", otherProject); - - String hash = "a8998353e96320ec"; - DuplicationUnitDto duplicate = new DuplicationUnitDto() - .setHash(hash) - .setStartLine(40) - .setEndLine(55) - .setIndexInFile(0) - .setAnalysisUuid(otherProjectSnapshot.getUuid()) - .setComponentUuid(otherFIle.uuid()); - dbClient.duplicationDao().insert(dbSession, duplicate); - dbSession.commit(); - - ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder() - .setHash(hash) - .setStartLine(30) - .setEndLine(45) - .setStartTokenIndex(0) - .setEndTokenIndex(10) - .build(); - batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock)); - - underTest.execute(new TestComputationStepContext()); - - verifyNoInteractions(integrateCrossProjectDuplications); - } - - @Test - public void nothing_to_do_when_no_cpd_text_blocks_found() { - when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); - analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); - - batchReportReader.putDuplicationBlocks(FILE_REF, Collections.emptyList()); - - underTest.execute(new TestComputationStepContext()); - - verifyNoInteractions(integrateCrossProjectDuplications); - } - - @Test - public void nothing_to_do_when_cpd_text_blocks_exists_but_no_duplicated_found() { - when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); - analysisMetadataHolder.setBaseAnalysis(baseProjectAnalysis); - - ScannerReport.CpdTextBlock originBlock = ScannerReport.CpdTextBlock.newBuilder() - .setHash("a8998353e96320ec") - .setStartLine(30) - .setEndLine(45) - .setStartTokenIndex(0) - .setEndTokenIndex(10) - .build(); - batchReportReader.putDuplicationBlocks(FILE_REF, singletonList(originBlock)); - - underTest.execute(new TestComputationStepContext()); - - verifyNoInteractions(integrateCrossProjectDuplications); - } - - private ComponentDto createProject(String projectKey) { - ComponentDto project = ComponentTesting.newPrivateProjectDto().setKey(projectKey); - return dbTester.components().insertComponent(project); - } - - private SnapshotDto createProjectSnapshot(ComponentDto project) { - SnapshotDto projectSnapshot = SnapshotTesting.newAnalysis(project); - dbClient.snapshotDao().insert(dbSession, projectSnapshot); - dbSession.commit(); - return projectSnapshot; - } - - private ComponentDto createFile(String fileKey, ComponentDto project) { - ComponentDto file = ComponentTesting.newFileDto(project, null) - .setKey(fileKey) - .setLanguage(XOO_LANGUAGE); - dbClient.componentDao().insert(dbSession, file); - dbSession.commit(); - return file; - } - - private static Map blocksByIndexInFile(List blocks) { - Map blocksByIndexInFile = new HashMap<>(); - for (Block block : blocks) { - blocksByIndexInFile.put(block.getIndexInFile(), block); - } - return blocksByIndexInFile; - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepTest.java deleted file mode 100644 index 6344846577a..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistAnalysisPropertiesStepTest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.step; - -import com.google.common.collect.ImmutableList; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; -import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; -import org.sonar.ce.task.step.TestComputationStepContext; -import org.sonar.core.util.CloseableIterator; -import org.sonar.core.util.UuidFactoryFast; -import org.sonar.db.DbTester; -import org.sonar.db.component.AnalysisPropertyDto; -import org.sonar.scanner.protocol.output.ScannerReport; - -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.tuple; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class PersistAnalysisPropertiesStepTest { - private static final String SNAPSHOT_UUID = randomAlphanumeric(40); - private static final String SMALL_VALUE1 = randomAlphanumeric(50); - private static final String SMALL_VALUE2 = randomAlphanumeric(50); - private static final String SMALL_VALUE3 = randomAlphanumeric(50); - private static final String BIG_VALUE = randomAlphanumeric(5000); - private static final String VALUE_PREFIX_FOR_PR_PROPERTIES = "pr_"; - private static final List PROPERTIES = Arrays.asList( - newContextProperty("key1", "value1"), - newContextProperty("key2", "value1"), - newContextProperty("sonar.analysis", SMALL_VALUE1), - newContextProperty("sonar.analysis.branch", SMALL_VALUE2), - newContextProperty("sonar.analysis.empty_string", ""), - newContextProperty("sonar.analysis.big_value", BIG_VALUE), - newContextProperty("sonar.analysis.", SMALL_VALUE3), - newContextProperty("sonar.pullrequest", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE1), - newContextProperty("sonar.pullrequest.branch", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE2), - newContextProperty("sonar.pullrequest.empty_string", ""), - newContextProperty("sonar.pullrequest.big_value", VALUE_PREFIX_FOR_PR_PROPERTIES + BIG_VALUE), - newContextProperty("sonar.pullrequest.", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE3)); - private static final String SCM_REV_ID = "sha1"; - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private BatchReportReader batchReportReader = mock(BatchReportReader.class); - private AnalysisMetadataHolder analysisMetadataHolder = mock(AnalysisMetadataHolder.class); - private PersistAnalysisPropertiesStep underTest = new PersistAnalysisPropertiesStep(dbTester.getDbClient(), analysisMetadataHolder, batchReportReader, - UuidFactoryFast.getInstance()); - - @Test - public void persist_should_stores_sonarDotAnalysisDot_and_sonarDotPullRequestDot_properties() { - when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.from(PROPERTIES.iterator())); - when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID); - when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID)); - - underTest.execute(new TestComputationStepContext()); - - assertThat(dbTester.countRowsOfTable("analysis_properties")).isEqualTo(8); - List propertyDtos = dbTester.getDbClient() - .analysisPropertiesDao().selectByAnalysisUuid(dbTester.getSession(), SNAPSHOT_UUID); - - assertThat(propertyDtos) - .extracting(AnalysisPropertyDto::getAnalysisUuid, AnalysisPropertyDto::getKey, AnalysisPropertyDto::getValue) - .containsExactlyInAnyOrder( - tuple(SNAPSHOT_UUID, "sonar.analysis.branch", SMALL_VALUE2), - tuple(SNAPSHOT_UUID, "sonar.analysis.empty_string", ""), - tuple(SNAPSHOT_UUID, "sonar.analysis.big_value", BIG_VALUE), - tuple(SNAPSHOT_UUID, "sonar.analysis.", SMALL_VALUE3), - tuple(SNAPSHOT_UUID, "sonar.pullrequest.branch", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE2), - tuple(SNAPSHOT_UUID, "sonar.pullrequest.empty_string", ""), - tuple(SNAPSHOT_UUID, "sonar.pullrequest.big_value", VALUE_PREFIX_FOR_PR_PROPERTIES + BIG_VALUE), - tuple(SNAPSHOT_UUID, "sonar.pullrequest.", VALUE_PREFIX_FOR_PR_PROPERTIES + SMALL_VALUE3)); - } - - @Test - public void persist_filtering_of_properties_is_case_sensitive() { - when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID)); - when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.from(ImmutableList.of( - newContextProperty("sonar.ANALYSIS.foo", "foo"), - newContextProperty("sonar.anaLysis.bar", "bar"), - newContextProperty("sonar.anaLYSIS.doo", "doh"), - newContextProperty("sonar.PULLREQUEST.foo", "foo"), - newContextProperty("sonar.pullRequest.bar", "bar"), - newContextProperty("sonar.pullREQUEST.doo", "doh")).iterator())); - when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID); - - underTest.execute(new TestComputationStepContext()); - - assertThat(dbTester.countRowsOfTable("analysis_properties")).isZero(); - } - - @Test - public void persist_should_store_nothing_if_there_are_no_context_properties() { - when(analysisMetadataHolder.getScmRevision()).thenReturn(Optional.of(SCM_REV_ID)); - when(batchReportReader.readContextProperties()).thenReturn(CloseableIterator.emptyCloseableIterator()); - when(analysisMetadataHolder.getUuid()).thenReturn(SNAPSHOT_UUID); - - underTest.execute(new TestComputationStepContext()); - - assertThat(dbTester.countRowsOfTable("analysis_properties")).isZero(); - } - - @Test - public void verify_description_value() { - assertThat(underTest.getDescription()).isEqualTo("Persist analysis properties"); - } - - private static ScannerReport.ContextProperty newContextProperty(String key, String value) { - return ScannerReport.ContextProperty.newBuilder() - .setKey(key) - .setValue(value) - .build(); - } -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepTest.java deleted file mode 100644 index ac428f4a710..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistCrossProjectDuplicationIndexStepTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.step; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.ce.task.projectanalysis.analysis.Analysis; -import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; -import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule; -import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.ReportComponent; -import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; -import org.sonar.ce.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolder; -import org.sonar.ce.task.step.ComputationStep; -import org.sonar.ce.task.step.TestComputationStepContext; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.scanner.protocol.output.ScannerReport; - -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class PersistCrossProjectDuplicationIndexStepTest { - - private static final int FILE_1_REF = 2; - private static final int FILE_2_REF = 3; - private static final String FILE_2_UUID = "file2"; - - private static final Component FILE_1 = ReportComponent.builder(Component.Type.FILE, FILE_1_REF).build(); - private static final Component FILE_2 = ReportComponent.builder(Component.Type.FILE, FILE_2_REF) - .setStatus(Component.Status.SAME).setUuid(FILE_2_UUID).build(); - - private static final Component PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1) - .addChildren(FILE_1) - .addChildren(FILE_2) - .build(); - - private static final ScannerReport.CpdTextBlock CPD_TEXT_BLOCK = ScannerReport.CpdTextBlock.newBuilder() - .setHash("a8998353e96320ec") - .setStartLine(30) - .setEndLine(45) - .build(); - private static final String ANALYSIS_UUID = "analysis uuid"; - private static final String BASE_ANALYSIS_UUID = "base analysis uuid"; - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - @Rule - public BatchReportReaderRule reportReader = new BatchReportReaderRule(); - @Rule - public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT); - @Rule - public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); - - private CrossProjectDuplicationStatusHolder crossProjectDuplicationStatusHolder = mock(CrossProjectDuplicationStatusHolder.class); - private Analysis baseAnalysis = mock(Analysis.class); - private DbClient dbClient = dbTester.getDbClient(); - - private ComputationStep underTest; - - @Before - public void setUp() { - when(baseAnalysis.getUuid()).thenReturn(BASE_ANALYSIS_UUID); - analysisMetadataHolder.setUuid(ANALYSIS_UUID); - analysisMetadataHolder.setBaseAnalysis(baseAnalysis); - underTest = new PersistCrossProjectDuplicationIndexStep(crossProjectDuplicationStatusHolder, dbClient, treeRootHolder, analysisMetadataHolder, reportReader); - } - - @Test - public void persist_cpd_text_block() { - when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); - reportReader.putDuplicationBlocks(FILE_1_REF, singletonList(CPD_TEXT_BLOCK)); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - Map dto = dbTester.selectFirst("select HASH, START_LINE, END_LINE, INDEX_IN_FILE, COMPONENT_UUID, ANALYSIS_UUID from duplications_index"); - assertThat(dto) - .containsEntry("HASH", CPD_TEXT_BLOCK.getHash()) - .containsEntry("START_LINE", 30L) - .containsEntry("END_LINE", 45L) - .containsEntry("INDEX_IN_FILE", 0L) - .containsEntry("COMPONENT_UUID", FILE_1.getUuid()) - .containsEntry("ANALYSIS_UUID", ANALYSIS_UUID); - context.getStatistics().assertValue("inserts", 1); - } - - @Test - public void persist_many_cpd_text_blocks() { - when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); - reportReader.putDuplicationBlocks(FILE_1_REF, Arrays.asList( - CPD_TEXT_BLOCK, - ScannerReport.CpdTextBlock.newBuilder() - .setHash("b1234353e96320ff") - .setStartLine(20) - .setEndLine(15) - .build())); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - List> dtos = dbTester.select("select HASH, START_LINE, END_LINE, INDEX_IN_FILE, COMPONENT_UUID, ANALYSIS_UUID from duplications_index"); - assertThat(dtos).extracting("HASH").containsOnly(CPD_TEXT_BLOCK.getHash(), "b1234353e96320ff"); - assertThat(dtos).extracting("START_LINE").containsOnly(30L, 20L); - assertThat(dtos).extracting("END_LINE").containsOnly(45L, 15L); - assertThat(dtos).extracting("INDEX_IN_FILE").containsOnly(0L, 1L); - assertThat(dtos).extracting("COMPONENT_UUID").containsOnly(FILE_1.getUuid()); - assertThat(dtos).extracting("ANALYSIS_UUID").containsOnly(ANALYSIS_UUID); - context.getStatistics().assertValue("inserts", 2); - } - - @Test - public void nothing_to_persist_when_no_cpd_text_blocks_in_report() { - when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(true); - reportReader.putDuplicationBlocks(FILE_1_REF, Collections.emptyList()); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(dbTester.countRowsOfTable("duplications_index")).isZero(); - context.getStatistics().assertValue("inserts", 0); - } - - @Test - public void nothing_to_do_when_cross_project_duplication_is_disabled() { - when(crossProjectDuplicationStatusHolder.isEnabled()).thenReturn(false); - reportReader.putDuplicationBlocks(FILE_1_REF, singletonList(CPD_TEXT_BLOCK)); - - TestComputationStepContext context = new TestComputationStepContext(); - underTest.execute(context); - - assertThat(dbTester.countRowsOfTable("duplications_index")).isZero(); - context.getStatistics().assertValue("inserts", null); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepTest.java deleted file mode 100644 index 4629dcdd842..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/PersistEventsStepTest.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.step; - -import com.google.common.collect.ImmutableList; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolderRule; -import org.sonar.ce.task.projectanalysis.component.Component; -import org.sonar.ce.task.projectanalysis.component.ReportComponent; -import org.sonar.ce.task.projectanalysis.component.TreeRootHolderRule; -import org.sonar.ce.task.projectanalysis.event.Event; -import org.sonar.ce.task.projectanalysis.event.EventRepository; -import org.sonar.ce.task.step.ComputationStep; -import org.sonar.ce.task.step.TestComputationStepContext; -import org.sonar.core.util.UuidFactory; -import org.sonar.core.util.UuidFactoryImpl; -import org.sonar.db.DbTester; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.event.EventDto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.ce.task.projectanalysis.component.Component.Type.DIRECTORY; -import static org.sonar.ce.task.projectanalysis.component.Component.Type.FILE; -import static org.sonar.ce.task.projectanalysis.component.Component.Type.PROJECT; -import static org.sonar.ce.task.projectanalysis.component.ReportComponent.builder; -import static org.sonar.db.event.EventDto.CATEGORY_ALERT; -import static org.sonar.db.event.EventDto.CATEGORY_PROFILE; -import static org.sonar.db.event.EventDto.CATEGORY_VERSION; - -public class PersistEventsStepTest extends BaseStepTest { - - private static final long NOW = 1225630680000L; - private static final ReportComponent ROOT = builder(PROJECT, 1) - .setUuid("ABCD") - .setProjectVersion("version_1") - .addChildren( - builder(DIRECTORY, 2) - .setUuid("BCDE") - .addChildren( - builder(DIRECTORY, 3) - .setUuid("Q") - .addChildren( - builder(FILE, 4) - .setUuid("Z") - .build()) - .build()) - .build()) - .build(); - private static final String ANALYSIS_UUID = "uuid_1"; - - System2 system2 = mock(System2.class); - - @Rule - public DbTester dbTester = DbTester.create(system2); - @Rule - public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule(); - @Rule - public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule(); - - private Date someDate = new Date(150000000L); - - private EventRepository eventRepository = mock(EventRepository.class); - private UuidFactory uuidFactory = UuidFactoryImpl.INSTANCE; - - private PersistEventsStep underTest; - - @Before - public void setup() { - analysisMetadataHolder.setAnalysisDate(someDate.getTime()).setUuid(ANALYSIS_UUID); - underTest = new PersistEventsStep(dbTester.getDbClient(), system2, treeRootHolder, analysisMetadataHolder, eventRepository, uuidFactory); - when(eventRepository.getEvents(any(Component.class))).thenReturn(Collections.emptyList()); - } - - @Override - protected ComputationStep step() { - return underTest; - } - - @Test - public void create_version_event() { - when(system2.now()).thenReturn(NOW); - Component project = builder(PROJECT, 1) - .setUuid("ABCD") - .setProjectVersion("1.0") - .addChildren( - builder(DIRECTORY, 2) - .setUuid("BCDE") - .addChildren( - builder(DIRECTORY, 3) - .setUuid("Q") - .addChildren( - builder(FILE, 4) - .setUuid("Z") - .build()) - .build()) - .build()) - .build(); - treeRootHolder.setRoot(project); - - underTest.execute(new TestComputationStepContext()); - - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isOne(); - List eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid()); - assertThat(eventDtos).hasSize(1); - EventDto eventDto = eventDtos.iterator().next(); - assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid()); - assertThat(eventDto.getName()).isEqualTo("1.0"); - assertThat(eventDto.getDescription()).isNull(); - assertThat(eventDto.getCategory()).isEqualTo(CATEGORY_VERSION); - assertThat(eventDto.getData()).isNull(); - assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate()); - assertThat(eventDto.getCreatedAt()).isEqualTo(NOW); - } - - @Test - public void persist_alert_events_on_root() { - when(system2.now()).thenReturn(NOW); - treeRootHolder.setRoot(ROOT); - Event alert = Event.createAlert("Failed", null, "Open issues > 0"); - when(eventRepository.getEvents(ROOT)).thenReturn(ImmutableList.of(alert)); - - underTest.execute(new TestComputationStepContext()); - - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(2); - List eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid()); - assertThat(eventDtos) - .extracting(EventDto::getCategory) - .containsOnly(CATEGORY_ALERT, CATEGORY_VERSION); - EventDto eventDto = eventDtos.stream().filter(t -> CATEGORY_ALERT.equals(t.getCategory())).findAny().get(); - assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid()); - assertThat(eventDto.getName()).isEqualTo(alert.getName()); - assertThat(eventDto.getDescription()).isEqualTo(alert.getDescription()); - assertThat(eventDto.getCategory()).isEqualTo(CATEGORY_ALERT); - assertThat(eventDto.getData()).isNull(); - assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate()); - assertThat(eventDto.getCreatedAt()).isEqualTo(NOW); - } - - @Test - public void persist_profile_events_on_root() { - when(system2.now()).thenReturn(NOW); - treeRootHolder.setRoot(ROOT); - Event profile = Event.createProfile("foo", null, "bar"); - when(eventRepository.getEvents(ROOT)).thenReturn(ImmutableList.of(profile)); - - underTest.execute(new TestComputationStepContext()); - - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(2); - List eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), ROOT.getUuid()); - assertThat(eventDtos) - .extracting(EventDto::getCategory) - .containsOnly(CATEGORY_PROFILE, CATEGORY_VERSION); - EventDto eventDto = eventDtos.stream().filter(t -> CATEGORY_PROFILE.equals(t.getCategory())).findAny().get(); - assertThat(eventDto.getComponentUuid()).isEqualTo(ROOT.getUuid()); - assertThat(eventDto.getName()).isEqualTo(profile.getName()); - assertThat(eventDto.getDescription()).isEqualTo(profile.getDescription()); - assertThat(eventDto.getCategory()).isEqualTo(EventDto.CATEGORY_PROFILE); - assertThat(eventDto.getData()).isNull(); - assertThat(eventDto.getDate()).isEqualTo(analysisMetadataHolder.getAnalysisDate()); - assertThat(eventDto.getCreatedAt()).isEqualTo(NOW); - } - - @Test - public void keep_one_event_by_version() { - ComponentDto projectDto = dbTester.components().insertPublicProject(); - EventDto[] existingEvents = new EventDto[] { - dbTester.events().insertEvent(newVersionEventDto(projectDto, 120_000_000L, "1.3-SNAPSHOT")), - dbTester.events().insertEvent(newVersionEventDto(projectDto, 130_000_000L, "1.4")), - dbTester.events().insertEvent(newVersionEventDto(projectDto, 140_000_000L, "1.5-SNAPSHOT")) - }; - - Component project = builder(PROJECT, 1) - .setUuid(projectDto.uuid()) - .setProjectVersion("1.5-SNAPSHOT") - .addChildren( - builder(DIRECTORY, 2) - .setUuid("BCDE") - .addChildren( - builder(DIRECTORY, 3) - .setUuid("Q") - .addChildren( - builder(FILE, 4) - .setUuid("Z") - .build()) - .build()) - .build()) - .build(); - treeRootHolder.setRoot(project); - - underTest.execute(new TestComputationStepContext()); - - assertThat(dbTester.countRowsOfTable(dbTester.getSession(), "events")).isEqualTo(3); - List eventDtos = dbTester.getDbClient().eventDao().selectByComponentUuid(dbTester.getSession(), projectDto.uuid()); - assertThat(eventDtos).hasSize(3); - assertThat(eventDtos) - .extracting(EventDto::getName) - .containsOnly("1.3-SNAPSHOT", "1.4", "1.5-SNAPSHOT"); - assertThat(eventDtos) - .extracting(EventDto::getUuid) - .contains(existingEvents[0].getUuid(), existingEvents[1].getUuid()) - .doesNotContain(existingEvents[2].getUuid()); - } - - private EventDto newVersionEventDto(ComponentDto project, long date, String name) { - return new EventDto().setUuid(uuidFactory.create()).setComponentUuid(project.uuid()) - .setAnalysisUuid("analysis_uuid") - .setCategory(CATEGORY_VERSION) - .setName(name).setDate(date).setCreatedAt(date); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepTest.java deleted file mode 100644 index a63c27ce096..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/taskprocessor/IgnoreOrphanBranchStepTest.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.taskprocessor; - -import java.util.Optional; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.ce.task.CeTask; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.component.BranchDto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.sonar.db.component.BranchType.BRANCH; - -public class IgnoreOrphanBranchStepTest { - - private String BRANCH_UUID = "branch_uuid"; - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private CeTask.Component component = new CeTask.Component(BRANCH_UUID, "component key", "component name"); - private CeTask ceTask = new CeTask.Builder() - .setType("type") - .setUuid("uuid") - .setComponent(component) - .setMainComponent(component) - .build(); - - private DbClient dbClient = dbTester.getDbClient(); - private IgnoreOrphanBranchStep underTest = new IgnoreOrphanBranchStep(ceTask, dbClient); - - @Test - public void execute() { - BranchDto branchDto = new BranchDto() - .setBranchType(BRANCH) - .setKey("branchName") - .setUuid(BRANCH_UUID) - .setProjectUuid("project_uuid") - .setNeedIssueSync(true); - dbClient.branchDao().insert(dbTester.getSession(), branchDto); - dbTester.commit(); - - underTest.execute(() -> null); - - Optional branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), BRANCH_UUID); - assertThat(branch.get().isNeedIssueSync()).isFalse(); - assertThat(branch.get().isExcludeFromPurge()).isFalse(); - } - - @Test - public void execute_on_already_indexed_branch() { - BranchDto branchDto = new BranchDto() - .setBranchType(BRANCH) - .setKey("branchName") - .setUuid(BRANCH_UUID) - .setProjectUuid("project_uuid") - .setNeedIssueSync(false); - dbClient.branchDao().insert(dbTester.getSession(), branchDto); - dbTester.commit(); - - underTest.execute(() -> null); - - Optional branch = dbClient.branchDao().selectByUuid(dbTester.getSession(), BRANCH_UUID); - assertThat(branch.get().isNeedIssueSync()).isFalse(); - assertThat(branch.get().isExcludeFromPurge()).isFalse(); - } - - @Test - public void fail_if_missing_main_component_in_task() { - CeTask ceTask = new CeTask.Builder() - .setType("type") - .setUuid("uuid") - .setComponent(null) - .setMainComponent(null) - .build(); - IgnoreOrphanBranchStep underTest = new IgnoreOrphanBranchStep(ceTask, dbClient); - - assertThatThrownBy(() -> underTest.execute(() -> null)) - .isInstanceOf(UnsupportedOperationException.class) - .hasMessage("main component not found in task"); - } - - @Test - public void verify_step_description() { - assertThat(underTest.getDescription()).isEqualTo("Ignore orphan component"); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepTest.java deleted file mode 100644 index 52bb309160c..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/component/ExportComponentsStepTest.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectexport.component; - -import com.google.common.collect.ImmutableSet; -import com.sonarsource.governance.projectdump.protobuf.ProjectDump; -import java.util.Date; -import java.util.List; -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.resources.Qualifiers; -import org.sonar.api.resources.Scopes; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.log.LogTester; -import org.sonar.api.utils.log.LoggerLevel; -import org.sonar.ce.task.projectexport.steps.DumpElement; -import org.sonar.ce.task.projectexport.steps.FakeDumpWriter; -import org.sonar.ce.task.projectexport.steps.ProjectHolder; -import org.sonar.ce.task.step.TestComputationStepContext; -import org.sonar.db.DbTester; -import org.sonar.db.component.ComponentDto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.tuple; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT; -import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR; - -public class ExportComponentsStepTest { - - private static final String PROJECT_UUID = "PROJECT_UUID"; - private static final ComponentDto PROJECT = new ComponentDto() - // no id yet - .setScope(Scopes.PROJECT) - .setQualifier(Qualifiers.PROJECT) - .setKey("the_project") - .setName("The Project") - .setDescription("The project description") - .setEnabled(true) - .setUuid(PROJECT_UUID) - .setUuidPath(UUID_PATH_OF_ROOT) - .setCreatedAt(new Date(1596749115856L)) - .setBranchUuid(PROJECT_UUID); - - private static final String FILE_UUID = "FILE_UUID"; - private static final String FILE_UUID_PATH = PROJECT_UUID + FILE_UUID + UUID_PATH_SEPARATOR; - private static final ComponentDto FILE = new ComponentDto() - // no id yet - .setScope(Scopes.FILE) - .setQualifier(Qualifiers.FILE) - .setKey("the_file") - .setName("The File") - .setUuid(FILE_UUID) - .setUuidPath(FILE_UUID_PATH) - .setEnabled(true) - .setCreatedAt(new Date(1596749148406L)) - .setBranchUuid(PROJECT_UUID); - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - @Rule - public LogTester logTester = new LogTester(); - - private final FakeDumpWriter dumpWriter = new FakeDumpWriter(); - private final ProjectHolder projectHolder = mock(ProjectHolder.class); - private final MutableComponentRepository componentRepository = new ComponentRepositoryImpl(); - private final ExportComponentsStep underTest = new ExportComponentsStep(dbTester.getDbClient(), projectHolder, componentRepository, dumpWriter); - - @After - public void tearDown() { - dbTester.getSession().close(); - } - - @Test - public void export_components_including_project() { - dbTester.components().insertPublicProject(PROJECT); - dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE); - dbTester.commit(); - when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT)); - - underTest.execute(new TestComputationStepContext()); - - assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 components exported"); - List components = dumpWriter.getWrittenMessagesOf(DumpElement.COMPONENTS); - assertThat(components).extracting(ProjectDump.Component::getQualifier, ProjectDump.Component::getUuid, ProjectDump.Component::getUuidPath) - .containsExactlyInAnyOrder( - tuple(Qualifiers.FILE, FILE_UUID, FILE_UUID_PATH), - tuple(Qualifiers.PROJECT, PROJECT_UUID, UUID_PATH_OF_ROOT)); - } - - @Test - public void execute_register_all_components_uuids_as_their_id_in_ComponentRepository() { - dbTester.components().insertPublicProject(PROJECT); - dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE); - dbTester.commit(); - when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT)); - - underTest.execute(new TestComputationStepContext()); - - assertThat(ImmutableSet.of( - componentRepository.getRef(PROJECT.uuid()), - componentRepository.getRef(FILE.uuid()))).containsExactlyInAnyOrder(1L, 2L); - } - - @Test - public void throws_ISE_if_error() { - dbTester.components().insertPublicProject(PROJECT); - dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE); - dbTester.commit(); - when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT)); - dumpWriter.failIfMoreThan(1, DumpElement.COMPONENTS); - - assertThatThrownBy(() -> underTest.execute(new TestComputationStepContext())) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Component Export failed after processing 1 components successfully"); - } - - @Test - public void getDescription_is_defined() { - assertThat(underTest.getDescription()).isEqualTo("Export components"); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepTest.java deleted file mode 100644 index 5ad64414c2c..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectexport/steps/ExportMeasuresStepTest.java +++ /dev/null @@ -1,259 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectexport.steps; - -import com.sonarsource.governance.projectdump.protobuf.ProjectDump; -import java.util.List; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.log.LogTester; -import org.sonar.api.utils.log.LoggerLevel; -import org.sonar.ce.task.projectexport.component.ComponentRepositoryImpl; -import org.sonar.ce.task.step.TestComputationStepContext; -import org.sonar.db.DbTester; -import org.sonar.db.component.BranchDto; -import org.sonar.db.component.BranchType; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.component.SnapshotDto; -import org.sonar.db.measure.MeasureDto; -import org.sonar.db.metric.MetricDto; - -import static com.google.common.collect.Lists.newArrayList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.db.component.ComponentDto.UUID_PATH_OF_ROOT; -import static org.sonar.db.component.ComponentDto.UUID_PATH_SEPARATOR; -import static org.sonar.db.component.SnapshotDto.STATUS_PROCESSED; -import static org.sonar.db.component.SnapshotDto.STATUS_UNPROCESSED; - -public class ExportMeasuresStepTest { - - private static final ComponentDto PROJECT = new ComponentDto() - .setKey("project_key") - .setUuid("project_uuid") - .setBranchUuid("project_uuid") - .setUuidPath(UUID_PATH_OF_ROOT) - .setEnabled(true); - private static final ComponentDto FILE = new ComponentDto() - .setKey("file_key") - .setUuid("file_uuid") - .setBranchUuid("project_uuid") - .setUuidPath(UUID_PATH_OF_ROOT + PROJECT.uuid() + UUID_PATH_SEPARATOR) - .setEnabled(true); - private static final ComponentDto ANOTHER_PROJECT = new ComponentDto() - .setKey("another_project_key") - .setUuid("another_project_uuid") - .setBranchUuid("another_project_uuid") - .setUuidPath(UUID_PATH_OF_ROOT) - .setEnabled(true); - - private static final MetricDto NCLOC = new MetricDto() - .setUuid("3") - .setKey("ncloc") - .setShortName("Lines of code") - .setEnabled(true); - - private static final MetricDto DISABLED_METRIC = new MetricDto() - .setUuid("4") - .setKey("coverage") - .setShortName("Coverage") - .setEnabled(false); - - private static final MetricDto NEW_NCLOC = new MetricDto() - .setUuid("5") - .setKey("new_ncloc") - .setShortName("New Lines of code") - .setEnabled(true); - - private static final List BRANCHES = newArrayList( - new BranchDto() - .setBranchType(BranchType.BRANCH) - .setKey("master") - .setUuid(PROJECT.uuid()) - .setProjectUuid(PROJECT.uuid())); - - - @Rule - public LogTester logTester = new LogTester(); - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private ComponentRepositoryImpl componentRepository = new ComponentRepositoryImpl(); - private MutableMetricRepository metricRepository = new MutableMetricRepositoryImpl(); - private ProjectHolder projectHolder = mock(ProjectHolder.class); - private FakeDumpWriter dumpWriter = new FakeDumpWriter(); - private ExportMeasuresStep underTest = new ExportMeasuresStep(dbTester.getDbClient(), projectHolder, componentRepository, metricRepository, dumpWriter); - - @Before - public void setUp() { - String projectUuid = dbTester.components().insertPublicProject(PROJECT).uuid(); - componentRepository.register(1, projectUuid, false); - dbTester.getDbClient().componentDao().insert(dbTester.getSession(), FILE, ANOTHER_PROJECT); - dbTester.getDbClient().metricDao().insert(dbTester.getSession(), NCLOC, DISABLED_METRIC, NEW_NCLOC); - dbTester.commit(); - when(projectHolder.projectDto()).thenReturn(dbTester.components().getProjectDto(PROJECT)); - when(projectHolder.branches()).thenReturn(BRANCHES); - } - - @Test - public void export_zero_measures() { - underTest.execute(new TestComputationStepContext()); - - assertThat(dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES)).isEmpty(); - assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("0 measures exported"); - assertThat(metricRepository.getRefByUuid()).isEmpty(); - } - - @Test - public void export_measures() { - SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED); - insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(NCLOC.getUuid())); - SnapshotDto secondAnalysis = insertSnapshot("U_2", PROJECT, STATUS_PROCESSED); - insertMeasure(secondAnalysis, PROJECT, new MeasureDto().setValue(110.0).setMetricUuid(NCLOC.getUuid())); - SnapshotDto anotherProjectAnalysis = insertSnapshot("U_3", ANOTHER_PROJECT, STATUS_PROCESSED); - insertMeasure(anotherProjectAnalysis, ANOTHER_PROJECT, new MeasureDto().setValue(500.0).setMetricUuid(NCLOC.getUuid())); - dbTester.commit(); - - underTest.execute(new TestComputationStepContext()); - - List exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES); - assertThat(exportedMeasures).hasSize(2); - assertThat(exportedMeasures).extracting(ProjectDump.Measure::getAnalysisUuid).containsOnly(firstAnalysis.getUuid(), secondAnalysis.getUuid()); - assertThat(exportedMeasures).extracting(ProjectDump.Measure::getMetricRef).containsOnly(0); - assertThat(logTester.logs(LoggerLevel.DEBUG)).contains("2 measures exported"); - assertThat(metricRepository.getRefByUuid()).containsOnlyKeys(NCLOC.getUuid()); - } - - @Test - public void do_not_export_measures_on_unprocessed_snapshots() { - SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_UNPROCESSED); - insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(NCLOC.getUuid())); - dbTester.commit(); - - underTest.execute(new TestComputationStepContext()); - - List exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES); - assertThat(exportedMeasures).isEmpty(); - } - - @Test - public void do_not_export_measures_on_disabled_metrics() { - SnapshotDto firstAnalysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED); - insertMeasure(firstAnalysis, PROJECT, new MeasureDto().setValue(100.0).setMetricUuid(DISABLED_METRIC.getUuid())); - dbTester.commit(); - - underTest.execute(new TestComputationStepContext()); - - List exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES); - assertThat(exportedMeasures).isEmpty(); - } - - @Test - public void test_exported_fields() { - SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED); - MeasureDto dto = new MeasureDto() - .setMetricUuid(NCLOC.getUuid()) - .setValue(100.0) - .setData("data") - .setAlertStatus("OK") - .setAlertText("alert text"); - insertMeasure(analysis, PROJECT, dto); - dbTester.commit(); - - underTest.execute(new TestComputationStepContext()); - - List exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES); - ProjectDump.Measure measure = exportedMeasures.get(0); - assertThat(measure.getAlertStatus()).isEqualTo(dto.getAlertStatus()); - assertThat(measure.getAlertText()).isEqualTo(dto.getAlertText()); - assertThat(measure.getDoubleValue().getValue()).isEqualTo(dto.getValue()); - assertThat(measure.getTextValue()).isEqualTo(dto.getData()); - assertThat(measure.getMetricRef()).isZero(); - assertThat(measure.getAnalysisUuid()).isEqualTo(analysis.getUuid()); - assertThat(measure.getVariation1().getValue()).isZero(); - } - - @Test - public void test_exported_fields_new_metric() { - SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED); - MeasureDto dto = new MeasureDto() - .setMetricUuid(NEW_NCLOC.getUuid()) - .setValue(100.0) - .setData("data") - .setAlertStatus("OK") - .setAlertText("alert text"); - insertMeasure(analysis, PROJECT, dto); - dbTester.commit(); - - underTest.execute(new TestComputationStepContext()); - - List exportedMeasures = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES); - ProjectDump.Measure measure = exportedMeasures.get(0); - assertThat(measure.getAlertStatus()).isEqualTo(dto.getAlertStatus()); - assertThat(measure.getAlertText()).isEqualTo(dto.getAlertText()); - assertThat(measure.getDoubleValue().getValue()).isZero(); - assertThat(measure.getTextValue()).isEqualTo(dto.getData()); - assertThat(measure.getMetricRef()).isZero(); - assertThat(measure.getAnalysisUuid()).isEqualTo(analysis.getUuid()); - assertThat(measure.getVariation1().getValue()).isEqualTo(dto.getValue()); - } - - @Test - public void test_null_exported_fields() { - SnapshotDto analysis = insertSnapshot("U_1", PROJECT, STATUS_PROCESSED); - insertMeasure(analysis, PROJECT, new MeasureDto().setMetricUuid(NCLOC.getUuid())); - dbTester.commit(); - - underTest.execute(new TestComputationStepContext()); - - ProjectDump.Measure measure = dumpWriter.getWrittenMessagesOf(DumpElement.MEASURES).get(0); - assertThat(measure.getAlertStatus()).isEmpty(); - assertThat(measure.getAlertText()).isEmpty(); - assertThat(measure.hasDoubleValue()).isFalse(); - assertThat(measure.getTextValue()).isEmpty(); - assertThat(measure.hasVariation1()).isFalse(); - } - - @Test - public void test_getDescription() { - assertThat(underTest.getDescription()).isEqualTo("Export measures"); - } - - private SnapshotDto insertSnapshot(String snapshotUuid, ComponentDto project, String status) { - SnapshotDto snapshot = new SnapshotDto() - .setUuid(snapshotUuid) - .setComponentUuid(project.uuid()) - .setStatus(status) - .setLast(true); - dbTester.getDbClient().snapshotDao().insert(dbTester.getSession(), snapshot); - return snapshot; - } - - private void insertMeasure(SnapshotDto analysisDto, ComponentDto componentDto, MeasureDto measureDto) { - measureDto - .setAnalysisUuid(analysisDto.getUuid()) - .setComponentUuid(componentDto.uuid()); - dbTester.getDbClient().measureDao().insert(dbTester.getSession(), measureDto); - } -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1235_add_component_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1235_add_component_uuid_to_duplications_index.rb deleted file mode 100644 index 34dfbce8de4..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1235_add_component_uuid_to_duplications_index.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class AddComponentUuidToDuplicationsIndex < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.AddComponentUuidColumnToDuplicationsIndex') - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1236_populate_component_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1236_populate_component_uuid_of_duplications_index.rb deleted file mode 100644 index 6f9d4ddea8d..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1236_populate_component_uuid_of_duplications_index.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class PopulateComponentUuidOfDuplicationsIndex < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.PopulateComponentUuidOfDuplicationsIndex') - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1237_delete_orphan_duplications_index_rows_without_component.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1237_delete_orphan_duplications_index_rows_without_component.rb deleted file mode 100644 index f4f67a968f3..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1237_delete_orphan_duplications_index_rows_without_component.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class DeleteOrphanDuplicationsIndexRowsWithoutComponent < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutComponent') - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1238_make_component_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1238_make_component_uuid_not_null_on_duplications_index.rb deleted file mode 100644 index eece62dbab3..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1238_make_component_uuid_not_null_on_duplications_index.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class MakeComponentUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.MakeComponentUuidNotNullOnDuplicationsIndex') - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1239_add_analysis_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1239_add_analysis_uuid_to_duplications_index.rb deleted file mode 100644 index bb5011b7f9a..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1239_add_analysis_uuid_to_duplications_index.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class AddAnalysisUuidToDuplicationsIndex < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.AddAnalysisUuidColumnToDuplicationsIndex') - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1240_populate_analysis_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1240_populate_analysis_uuid_of_duplications_index.rb deleted file mode 100644 index 6451732adad..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1240_populate_analysis_uuid_of_duplications_index.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class PopulateAnalysisUuidOfDuplicationsIndex < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.PopulateAnalysisUuidOfDuplicationsIndex') - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb deleted file mode 100644 index 5afedbe9ebd..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1241_delete_orphan_duplications_index_rows_without_analysis.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class DeleteOrphanDuplicationsIndexRowsWithoutAnalysis < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutAnalysis') - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb deleted file mode 100644 index 961cd6e902f..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/1242_make_analysis_uuid_not_null_on_duplications_index.rb +++ /dev/null @@ -1,31 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class MakeAnalysisUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.MakeAnalysisUuidNotNullOnDuplicationsIndex') - - add_index :duplications_index, [:analysis_uuid, :component_uuid], :name => 'duplication_analysis_component' - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddAnalysisUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddAnalysisUuidColumnToDuplicationsIndex.java deleted file mode 100644 index f641c800d7f..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddAnalysisUuidColumnToDuplicationsIndex.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1; - -import java.sql.SQLException; -import org.sonar.db.Database; -import org.sonar.db.version.AddColumnsBuilder; - -import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE; -import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder; - -public class AddAnalysisUuidColumnToDuplicationsIndex extends DdlChange { - - private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index"; - - public AddAnalysisUuidColumnToDuplicationsIndex(Database db) { - super(db); - } - - @Override - public void execute(Context context) throws SQLException { - context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX) - .addColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build()) - .build()); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddComponentUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddComponentUuidColumnToDuplicationsIndex.java deleted file mode 100644 index b6f6763cb3c..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/AddComponentUuidColumnToDuplicationsIndex.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1; - -import java.sql.SQLException; -import org.sonar.db.Database; -import org.sonar.db.version.AddColumnsBuilder; - -import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE; -import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder; - -public class AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex extends DdlChange { - - private static final String TABLE_PUBLICATIONS_INDEX = "duplications_index"; - - public AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex(Database db) { - super(db); - } - - @Override - public void execute(Context context) throws SQLException { - context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_PUBLICATIONS_INDEX) - .addColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build()) - .addColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build()) - .build()); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java deleted file mode 100644 index eb7ed0b79aa..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/DeleteOrphanDuplicationsIndexRowsWithoutComponent.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1; - -import java.sql.SQLException; -import org.sonar.db.Database; -import org.sonar.server.platform.db.migration.step.MassUpdate; - -public class DeleteOrphanDuplicationsIndexRowsWithoutComponent extends BaseDataChange { - - public DeleteOrphanDuplicationsIndexRowsWithoutComponent(Database db) { - super(db); - } - - @Override - public void execute(Context context) throws SQLException { - MassUpdate massUpdate = context.prepareMassUpdate(); - massUpdate.select("SELECT id from duplications_index where component_uuid is null"); - massUpdate.update("DELETE from duplications_index WHERE id=?"); - massUpdate.rowPluralName("resources_index entries"); - massUpdate.execute((row, update) -> { - update.setLong(1, row.getLong(1)); - return true; - }); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java deleted file mode 100644 index e4d1f35dfa6..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v1/MakeComponentUuidNotNullOnDuplicationsIndex.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v1; - -import java.sql.SQLException; -import org.sonar.db.Database; -import org.sonar.db.version.AlterColumnsBuilder; - -import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE; -import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder; - -public class MakeComponentUuidNotNullOnDuplicationsIndex extends DdlChange { - - private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index"; - - public MakeComponentUuidNotNullOnDuplicationsIndex(Database db) { - super(db); - } - - @Override - public void execute(Context context) throws SQLException { - context.execute(new AlterColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX) - .updateColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build()) - .build()); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb deleted file mode 100644 index c7ecc23e1ad..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1235_add_component_uuid_and_analysis_uuid_to_duplications_index.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class AddComponentUuidAndAnalysisUuidToDuplicationsIndex < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex') - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb deleted file mode 100644 index 024aa7f5ca1..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1236_populate_component_uuid_and_analysis_uuid_of_duplications_index.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class PopulateComponentUuidAndAnalysisUuidOfDuplicationsIndex < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.PopulateComponentUuidAndAnalysisUuidOfDuplicationsIndex') - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb deleted file mode 100644 index caa887bffe4..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1237_delete_orphan_duplications_index_rows_without_component_or_analysis.rb +++ /dev/null @@ -1,29 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class DeleteOrphanDuplicationsIndexRowsWithoutComponentOrAnalysis < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.DeleteOrphanDuplicationsIndexRowsWithoutComponentOrAnalysis') - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb deleted file mode 100644 index 72e090fe509..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/1238_make_component_uuid_and_analysis_uuid_not_null_on_duplications_index.rb +++ /dev/null @@ -1,31 +0,0 @@ -# -# SonarQube, open source software quality management tool. -# Copyright (C) 2008-2016 SonarSource -# mailto:contact AT sonarsource DOT com -# -# SonarQube is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License as published by the Free Software Foundation; either -# version 3 of the License, or (at your option) any later version. -# -# SonarQube is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - -# -# SonarQube 6.0 -# -class MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex < ActiveRecord::Migration - - def self.up - execute_java_migration('org.sonar.db.version.v60.MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex') - - add_index :duplications_index, [:analysis_uuid, :component_uuid], :name => 'duplication_analysis_component' - end -end diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java deleted file mode 100644 index d3df009edd7..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/AddComponentUuidAndAnalysisUuidColumnToDuplicationsIndex.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v2; - -import java.sql.SQLException; -import org.sonar.db.Database; -import org.sonar.db.version.AddColumnsBuilder; - -import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE; -import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder; - -public class AddComponentUuidColumnToDuplicationsIndex extends DdlChange { - - private static final String TABLE_PUBLICATIONS_INDEX = "duplications_index"; - - public AddComponentUuidColumnToDuplicationsIndex(Database db) { - super(db); - } - - @Override - public void execute(Context context) throws SQLException { - context.execute(new AddColumnsBuilder(getDatabase().getDialect(), TABLE_PUBLICATIONS_INDEX) - .addColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(true).build()) - .build()); - } - -} diff --git a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java b/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java deleted file mode 100644 index 01f56100fa9..00000000000 --- a/server/sonar-ce-task-projectanalysis/src/test/resources/org/sonar/ce/task/projectanalysis/filemove/FileMoveDetectionStepTest/v2/MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.ce.task.projectanalysis.filemove.FileMoveDetectionStepTest.v2; - -import java.sql.SQLException; -import org.sonar.db.Database; -import org.sonar.db.version.AlterColumnsBuilder; - -import static org.sonar.db.version.VarcharColumnDef.UUID_VARCHAR_SIZE; -import static org.sonar.db.version.VarcharColumnDef.newVarcharColumnDefBuilder; - -public class MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex extends DdlChange { - - private static final String TABLE_DUPLICATIONS_INDEX = "duplications_index"; - - public MakeComponentUuidAndAnalysisUuidNotNullOnDuplicationsIndex(Database db) { - super(db); - } - - @Override - public void execute(Context context) throws SQLException { - context.execute(new AlterColumnsBuilder(getDatabase().getDialect(), TABLE_DUPLICATIONS_INDEX) - .updateColumn(newVarcharColumnDefBuilder().setColumnName("component_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build()) - .updateColumn(newVarcharColumnDefBuilder().setColumnName("analysis_uuid").setLimit(UUID_VARCHAR_SIZE).setIsNullable(false).build()) - .build()); - } - -} diff --git a/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java b/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java new file mode 100644 index 00000000000..412565ae931 --- /dev/null +++ b/server/sonar-webserver-api/src/it/java/org/sonar/server/plugins/DetectPluginChangeIT.java @@ -0,0 +1,126 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.Plugin; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.plugin.PluginType; +import org.sonar.db.DbTester; +import org.sonar.db.plugin.PluginDto; +import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DetectPluginChangeIT { + @Rule + public DbTester dbTester = DbTester.create(); + + private final ServerPluginRepository pluginRepository = new ServerPluginRepository(); + private final DetectPluginChange detectPluginChange = new DetectPluginChange(pluginRepository, dbTester.getDbClient()); + + @Test + public void detect_changed_plugin() { + addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); + addPluginToFs("plugin1", "hash2", PluginType.BUNDLED); + + detectPluginChange.start(); + assertThat(detectPluginChange.anyPluginChanged()).isTrue(); + } + + @Test + public void detect_changed_type() { + addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); + addPluginToFs("plugin1", "hash1", PluginType.EXTERNAL); + + detectPluginChange.start(); + assertThat(detectPluginChange.anyPluginChanged()).isTrue(); + } + + @Test + public void detect_new_plugin() { + addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); + addPluginToFs("plugin2", "hash1", PluginType.BUNDLED); + + detectPluginChange.start(); + assertThat(detectPluginChange.anyPluginChanged()).isTrue(); + } + + @Test + public void detect_removed_plugin() { + addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED, true); + addPluginToFs("plugin1", "hash1", PluginType.BUNDLED); + + detectPluginChange.start(); + assertThat(detectPluginChange.anyPluginChanged()).isTrue(); + } + + @Test + public void detect_missing_plugin() { + addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); + + detectPluginChange.start(); + assertThat(detectPluginChange.anyPluginChanged()).isTrue(); + } + + @Test + public void detect_no_changes() { + addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); + addPluginToFs("plugin1", "hash1", PluginType.BUNDLED); + addPluginToDb("plugin2", "hash2", PluginDto.Type.EXTERNAL); + addPluginToFs("plugin2", "hash2", PluginType.EXTERNAL); + + detectPluginChange.start(); + assertThat(detectPluginChange.anyPluginChanged()).isFalse(); + } + + @Test + public void fail_if_start_twice() { + detectPluginChange.start(); + assertThrows(IllegalStateException.class, detectPluginChange::start); + } + + @Test + public void fail_if_not_started() { + assertThrows(NullPointerException.class, detectPluginChange::anyPluginChanged); + } + + private void addPluginToDb(String key, String hash, PluginDto.Type type) { + addPluginToDb(key, hash, type, false); + } + + private void addPluginToDb(String key, String hash, PluginDto.Type type, boolean removed) { + dbTester.pluginDbTester().insertPlugin(p -> p.setKee(key).setFileHash(hash).setType(type).setRemoved(removed)); + } + + private void addPluginToFs(String key, String hash, PluginType type) { + PluginInfo pluginInfo = new PluginInfo(key); + Plugin plugin = mock(Plugin.class); + FileAndMd5 fileAndMd5 = mock(FileAndMd5.class); + when(fileAndMd5.getMd5()).thenReturn(hash); + ServerPlugin serverPlugin = new ServerPlugin(pluginInfo, type, plugin, fileAndMd5, null); + pluginRepository.addPlugin(serverPlugin); + } + +} diff --git a/server/sonar-webserver-api/src/it/java/org/sonar/server/rule/CachingRuleFinderIT.java b/server/sonar-webserver-api/src/it/java/org/sonar/server/rule/CachingRuleFinderIT.java new file mode 100644 index 00000000000..5d0134b6f29 --- /dev/null +++ b/server/sonar-webserver-api/src/it/java/org/sonar/server/rule/CachingRuleFinderIT.java @@ -0,0 +1,437 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.rule; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import org.junit.Before; +import org.junit.Test; +import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.Rule; +import org.sonar.api.rules.RuleQuery; +import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.rule.RuleDao; +import org.sonar.db.rule.RuleDto; +import org.sonar.db.rule.RuleParamDto; +import org.sonar.db.rule.RuleTesting; + +import static java.util.stream.Collectors.toList; +import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +public class CachingRuleFinderIT { + @org.junit.Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final DbClient dbClient = dbTester.getDbClient(); + private final AlwaysIncreasingSystem2 system2 = new AlwaysIncreasingSystem2(); + private RuleDto[] ruleDtos; + private RuleParamDto[] ruleParams; + private CachingRuleFinder underTest; + private RuleDescriptionFormatter ruleDescriptionFormatter = new RuleDescriptionFormatter(); + + @Before() + public void setUp() { + Consumer setUpdatedAt = rule -> rule.setUpdatedAt(system2.now()); + this.ruleDtos = new RuleDto[] { + dbTester.rules().insert(setUpdatedAt), + dbTester.rules().insert(setUpdatedAt), + dbTester.rules().insert(setUpdatedAt), + dbTester.rules().insert(setUpdatedAt), + dbTester.rules().insert(setUpdatedAt), + dbTester.rules().insert(setUpdatedAt) + }; + this.ruleParams = Arrays.stream(ruleDtos) + .map(rule -> dbTester.rules().insertRuleParam(rule)) + .toArray(RuleParamDto[]::new); + + underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + // delete all data from DB to ensure tests rely on cache exclusively + dbTester.executeUpdateSql("delete from rules"); + dbTester.executeUpdateSql("delete from rules_parameters"); + assertThat(dbTester.countRowsOfTable("rules")).isZero(); + assertThat(dbTester.countRowsOfTable("rules_parameters")).isZero(); + } + + @Test + public void constructor_reads_rules_from_DB() { + DbClient dbClient = mock(DbClient.class); + DbSession dbSession = mock(DbSession.class); + RuleDao ruleDao = mock(RuleDao.class); + when(dbClient.openSession(anyBoolean())).thenReturn(dbSession); + when(dbClient.ruleDao()).thenReturn(ruleDao); + + new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + verify(dbClient).openSession(anyBoolean()); + verify(ruleDao).selectAll(dbSession); + verify(ruleDao).selectAllRuleParams(dbSession); + verifyNoMoreInteractions(ruleDao); + } + + @Test + public void constructor_reads_parameters_from_DB() { + DbClient dbClient = mock(DbClient.class); + DbSession dbSession = mock(DbSession.class); + RuleDao ruleDao = mock(RuleDao.class); + when(dbClient.openSession(anyBoolean())).thenReturn(dbSession); + when(dbClient.ruleDao()).thenReturn(ruleDao); + List ruleKeys = Arrays.asList(RuleKey.of("A", "B"), RuleKey.of("C", "D"), RuleKey.of("E", "F")); + when(ruleDao.selectAll(dbSession)).thenReturn(ruleKeys.stream().map(RuleTesting::newRule).collect(toList())); + + new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + verify(ruleDao).selectAllRuleParams(dbSession); + } + + @Test + public void findByKey_returns_all_loaded_rules() { + for (int i = 0; i < ruleDtos.length; i++) { + RuleDto ruleDto = ruleDtos[i]; + RuleParamDto ruleParam = ruleParams[i]; + + org.sonar.api.rules.Rule rule = underTest.findByKey(ruleDto.getKey()); + verifyRule(rule, ruleDto, ruleParam); + assertThat(underTest.findByKey(ruleDto.getRepositoryKey(), ruleDto.getRuleKey())) + .isSameAs(rule); + } + } + + @Test + public void findByKey_returns_null_when_RuleKey_is_null() { + assertThat(underTest.findByKey(null)).isNull(); + } + + @Test + public void findByKey_returns_null_when_repository_key_is_null() { + assertThat(underTest.findByKey(null, randomAlphabetic(2))).isNull(); + } + + @Test + public void findByKey_returns_null_when_key_is_null() { + assertThat(underTest.findByKey(randomAlphabetic(2), null)).isNull(); + } + + @Test + public void findByKey_returns_null_when_both_repository_key_and_key_are_null() { + assertThat(underTest.findByKey(null, null)).isNull(); + } + + @Test + public void find_returns_null_when_RuleQuery_is_empty() { + assertThat(underTest.find(null)).isNull(); + } + + @Test + public void find_returns_most_recent_rule_when_RuleQuery_has_no_non_null_field() { + Rule rule = underTest.find(RuleQuery.create()); + + assertThat(toRuleKey(rule)).isEqualTo(ruleDtos[5].getKey()); + } + + @Test + public void find_searches_by_exact_match_of_repository_key_and_returns_most_recent_rule() { + String repoKey = "ABCD"; + RuleDto[] sameRepoKey = { + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())) + }; + RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(repoKey)))) + .isEqualTo(sameRepoKey[1].getKey()); + assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey())))) + .isEqualTo(otherRule.getKey()); + assertThat(underTest.find(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase()))) + .isNull(); + assertThat(underTest.find(RuleQuery.create().withRepositoryKey(randomAlphabetic(3)))) + .isNull(); + } + + @Test + public void find_searches_by_exact_match_of_ruleKey_and_returns_most_recent_rule() { + String ruleKey = "ABCD"; + RuleDto[] sameRuleKey = { + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())) + }; + RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(ruleKey)))) + .isEqualTo(sameRuleKey[1].getKey()); + assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(otherRule.getRuleKey())))) + .isEqualTo(otherRule.getKey()); + assertThat(underTest.find(RuleQuery.create().withKey(ruleKey.toLowerCase()))) + .isNull(); + assertThat(underTest.find(RuleQuery.create().withKey(randomAlphabetic(3)))) + .isNull(); + } + + @Test + public void find_searches_by_exact_match_of_configKey_and_returns_most_recent_rule() { + String configKey = "ABCD"; + RuleDto[] sameConfigKey = { + dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())) + }; + RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(configKey)))) + .isEqualTo(sameConfigKey[1].getKey()); + assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(otherRule.getConfigKey())))) + .isEqualTo(otherRule.getKey()); + assertThat(underTest.find(RuleQuery.create().withConfigKey(configKey.toLowerCase()))) + .isNull(); + assertThat(underTest.find(RuleQuery.create().withConfigKey(randomAlphabetic(3)))) + .isNull(); + } + + @Test + public void find_searches_by_exact_match_and_match_on_all_criterias_and_returns_most_recent_match() { + String repoKey = "ABCD"; + String ruleKey = "EFGH"; + String configKey = "IJKL"; + RuleDto[] rules = { + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())) + }; + RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey); + RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey); + RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey); + RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey); + RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey); + RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey); + RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + assertThat(toRuleKey(underTest.find(allQuery))).isEqualTo(rules[0].getKey()); + assertThat(toRuleKey(underTest.find(ruleAndConfigKeyQuery))).isEqualTo(rules[1].getKey()); + assertThat(toRuleKey(underTest.find(repoAndConfigKeyQuery))).isEqualTo(rules[2].getKey()); + assertThat(toRuleKey(underTest.find(repoAndKeyQuery))).isEqualTo(rules[0].getKey()); + assertThat(toRuleKey(underTest.find(repoKeyQuery))).isEqualTo(rules[2].getKey()); + assertThat(toRuleKey(underTest.find(ruleKeyQuery))).isEqualTo(rules[1].getKey()); + assertThat(toRuleKey(underTest.find(configKeyQuery))).isEqualTo(rules[2].getKey()); + } + + @Test + public void findAll_returns_empty_when_RuleQuery_is_empty() { + assertThat(underTest.findAll(null)).isEmpty(); + } + + @Test + public void findAll_returns_all_rules_when_RuleQuery_has_no_non_null_field() { + assertThat(underTest.findAll(RuleQuery.create())) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsOnly(Arrays.stream(ruleDtos).map(RuleDto::getKey).toArray(RuleKey[]::new)); + } + + @Test + public void findAll_returns_all_rules_with_exact_same_repository_key_and_order_them_most_recent_first() { + String repoKey = "ABCD"; + long currentTimeMillis = System.currentTimeMillis(); + RuleDto[] sameRepoKey = { + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(currentTimeMillis + system2.now())), + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(currentTimeMillis + system2.now())) + }; + RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(currentTimeMillis + system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey))) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(sameRepoKey[1].getKey(), sameRepoKey[0].getKey()); + assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey()))) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(otherRule.getKey()); + assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase()))) + .isEmpty(); + assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(randomAlphabetic(3)))) + .isEmpty(); + } + + @Test + public void findAll_returns_all_rules_with_exact_same_rulekey_and_order_them_most_recent_first() { + String ruleKey = "ABCD"; + RuleDto[] sameRuleKey = { + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())) + }; + RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey))) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(sameRuleKey[1].getKey(), sameRuleKey[0].getKey()); + assertThat(underTest.findAll(RuleQuery.create().withKey(otherRule.getRuleKey()))) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(otherRule.getKey()); + assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey.toLowerCase()))) + .isEmpty(); + assertThat(underTest.findAll(RuleQuery.create().withKey(randomAlphabetic(3)))) + .isEmpty(); + } + + @Test + public void findAll_returns_all_rules_with_exact_same_configkey_and_order_them_most_recent_first() { + String configKey = "ABCD"; + RuleDto[] sameConfigKey = { + dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())) + }; + RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey))) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(sameConfigKey[1].getKey(), sameConfigKey[0].getKey()); + assertThat(underTest.findAll(RuleQuery.create().withConfigKey(otherRule.getConfigKey()))) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(otherRule.getKey()); + assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey.toLowerCase()))) + .isEmpty(); + assertThat(underTest.findAll(RuleQuery.create().withConfigKey(randomAlphabetic(3)))) + .isEmpty(); + } + + @Test + public void findAll_returns_all_rules_which_match_exactly_all_criteria_and_order_then_by_most_recent_first() { + String repoKey = "ABCD"; + String ruleKey = "EFGH"; + String configKey = "IJKL"; + RuleDto[] rules = { + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())), + dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())) + }; + RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey); + RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey); + RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey); + RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey); + RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey); + RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey); + RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey); + + CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); + + assertThat(underTest.findAll(allQuery)) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(rules[0].getKey()); + assertThat(underTest.findAll(ruleAndConfigKeyQuery)) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(rules[1].getKey(), rules[0].getKey()); + assertThat(underTest.findAll(repoAndConfigKeyQuery)) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(rules[2].getKey(), rules[0].getKey()); + assertThat(underTest.findAll(repoAndKeyQuery)) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(rules[0].getKey()); + assertThat(underTest.findAll(repoKeyQuery)) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(rules[2].getKey(), rules[0].getKey()); + assertThat(underTest.findAll(ruleKeyQuery)) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(rules[1].getKey(), rules[0].getKey()); + assertThat(underTest.findAll(configKeyQuery)) + .extracting(CachingRuleFinderIT::toRuleKey) + .containsExactly(rules[2].getKey(), rules[1].getKey(), rules[0].getKey()); + } + + @Test + public void findDtoByKey_finds_rules() { + for(RuleDto dto : ruleDtos) { + assertThat(underTest.findDtoByKey(dto.getKey())).contains(dto); + } + } + + @Test + public void findDtoByUuid_finds_rules() { + for(RuleDto dto : ruleDtos) { + assertThat(underTest.findDtoByUuid(dto.getUuid())).contains(dto); + } + } + + @Test + public void findDtoByKey_returns_empty_if_rule_not_found() { + assertThat(underTest.findDtoByKey(RuleKey.of("unknown", "unknown"))).isEmpty(); + } + + @Test + public void findDtoByUuid_returns_empty_if_rule_not_found() { + assertThat(underTest.findDtoByUuid("unknown")).isEmpty(); + } + + @Test + public void findAll_returns_all_rules() { + assertThat(underTest.findAll()).containsOnly(ruleDtos); + } + + private static RuleKey toRuleKey(Rule rule) { + return RuleKey.of(rule.getRepositoryKey(), rule.getKey()); + } + + private void verifyRule(@Nullable Rule rule, RuleDto ruleDto, RuleParamDto ruleParam) { + assertThat(rule).isNotNull(); + + assertThat(rule.getName()).isEqualTo(ruleDto.getName()); + assertThat(rule.getLanguage()).isEqualTo(ruleDto.getLanguage()); + assertThat(rule.getKey()).isEqualTo(ruleDto.getRuleKey()); + assertThat(rule.getConfigKey()).isEqualTo(ruleDto.getConfigKey()); + assertThat(rule.isTemplate()).isEqualTo(ruleDto.isTemplate()); + assertThat(rule.getCreatedAt().getTime()).isEqualTo(ruleDto.getCreatedAt()); + assertThat(rule.getUpdatedAt().getTime()).isEqualTo(ruleDto.getUpdatedAt()); + assertThat(rule.getRepositoryKey()).isEqualTo(ruleDto.getRepositoryKey()); + assertThat(rule.getSeverity().name()).isEqualTo(ruleDto.getSeverityString()); + assertThat(rule.getSystemTags()).isEqualTo(ruleDto.getSystemTags().toArray(new String[0])); + assertThat(rule.getTags()).isEmpty(); + assertThat(rule.getDescription()).isEqualTo(ruleDto.getDefaultRuleDescriptionSection().getContent()); + + assertThat(rule.getParams()).hasSize(1); + org.sonar.api.rules.RuleParam param = rule.getParams().iterator().next(); + assertThat(param.getRule()).isSameAs(rule); + assertThat(param.getKey()).isEqualTo(ruleParam.getName()); + assertThat(param.getDescription()).isEqualTo(ruleParam.getDescription()); + assertThat(param.getType()).isEqualTo(ruleParam.getType()); + assertThat(param.getDefaultValue()).isEqualTo(ruleParam.getDefaultValue()); + } +} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/DetectPluginChangeTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/DetectPluginChangeTest.java deleted file mode 100644 index a0451f70138..00000000000 --- a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/DetectPluginChangeTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.plugins; - -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.Plugin; -import org.sonar.core.platform.PluginInfo; -import org.sonar.core.plugin.PluginType; -import org.sonar.db.DbTester; -import org.sonar.db.plugin.PluginDto; -import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.assertThrows; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class DetectPluginChangeTest { - @Rule - public DbTester dbTester = DbTester.create(); - - private final ServerPluginRepository pluginRepository = new ServerPluginRepository(); - private final DetectPluginChange detectPluginChange = new DetectPluginChange(pluginRepository, dbTester.getDbClient()); - - @Test - public void detect_changed_plugin() { - addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); - addPluginToFs("plugin1", "hash2", PluginType.BUNDLED); - - detectPluginChange.start(); - assertThat(detectPluginChange.anyPluginChanged()).isTrue(); - } - - @Test - public void detect_changed_type() { - addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); - addPluginToFs("plugin1", "hash1", PluginType.EXTERNAL); - - detectPluginChange.start(); - assertThat(detectPluginChange.anyPluginChanged()).isTrue(); - } - - @Test - public void detect_new_plugin() { - addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); - addPluginToFs("plugin2", "hash1", PluginType.BUNDLED); - - detectPluginChange.start(); - assertThat(detectPluginChange.anyPluginChanged()).isTrue(); - } - - @Test - public void detect_removed_plugin() { - addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED, true); - addPluginToFs("plugin1", "hash1", PluginType.BUNDLED); - - detectPluginChange.start(); - assertThat(detectPluginChange.anyPluginChanged()).isTrue(); - } - - @Test - public void detect_missing_plugin() { - addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); - - detectPluginChange.start(); - assertThat(detectPluginChange.anyPluginChanged()).isTrue(); - } - - @Test - public void detect_no_changes() { - addPluginToDb("plugin1", "hash1", PluginDto.Type.BUNDLED); - addPluginToFs("plugin1", "hash1", PluginType.BUNDLED); - addPluginToDb("plugin2", "hash2", PluginDto.Type.EXTERNAL); - addPluginToFs("plugin2", "hash2", PluginType.EXTERNAL); - - detectPluginChange.start(); - assertThat(detectPluginChange.anyPluginChanged()).isFalse(); - } - - @Test - public void fail_if_start_twice() { - detectPluginChange.start(); - assertThrows(IllegalStateException.class, detectPluginChange::start); - } - - @Test - public void fail_if_not_started() { - assertThrows(NullPointerException.class, detectPluginChange::anyPluginChanged); - } - - private void addPluginToDb(String key, String hash, PluginDto.Type type) { - addPluginToDb(key, hash, type, false); - } - - private void addPluginToDb(String key, String hash, PluginDto.Type type, boolean removed) { - dbTester.pluginDbTester().insertPlugin(p -> p.setKee(key).setFileHash(hash).setType(type).setRemoved(removed)); - } - - private void addPluginToFs(String key, String hash, PluginType type) { - PluginInfo pluginInfo = new PluginInfo(key); - Plugin plugin = mock(Plugin.class); - FileAndMd5 fileAndMd5 = mock(FileAndMd5.class); - when(fileAndMd5.getMd5()).thenReturn(hash); - ServerPlugin serverPlugin = new ServerPlugin(pluginInfo, type, plugin, fileAndMd5, null); - pluginRepository.addPlugin(serverPlugin); - } - -} diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java deleted file mode 100644 index 4cd4c1c0cd1..00000000000 --- a/server/sonar-webserver-api/src/test/java/org/sonar/server/rule/CachingRuleFinderTest.java +++ /dev/null @@ -1,437 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.rule; - -import java.util.Arrays; -import java.util.List; -import java.util.function.Consumer; -import javax.annotation.Nullable; -import org.junit.Before; -import org.junit.Test; -import org.sonar.api.impl.utils.AlwaysIncreasingSystem2; -import org.sonar.api.rule.RuleKey; -import org.sonar.api.rules.Rule; -import org.sonar.api.rules.RuleQuery; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.rule.RuleDao; -import org.sonar.db.rule.RuleDto; -import org.sonar.db.rule.RuleParamDto; -import org.sonar.db.rule.RuleTesting; - -import static java.util.stream.Collectors.toList; -import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoMoreInteractions; -import static org.mockito.Mockito.when; - -public class CachingRuleFinderTest { - @org.junit.Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private final DbClient dbClient = dbTester.getDbClient(); - private final AlwaysIncreasingSystem2 system2 = new AlwaysIncreasingSystem2(); - private RuleDto[] ruleDtos; - private RuleParamDto[] ruleParams; - private CachingRuleFinder underTest; - private RuleDescriptionFormatter ruleDescriptionFormatter = new RuleDescriptionFormatter(); - - @Before() - public void setUp() { - Consumer setUpdatedAt = rule -> rule.setUpdatedAt(system2.now()); - this.ruleDtos = new RuleDto[] { - dbTester.rules().insert(setUpdatedAt), - dbTester.rules().insert(setUpdatedAt), - dbTester.rules().insert(setUpdatedAt), - dbTester.rules().insert(setUpdatedAt), - dbTester.rules().insert(setUpdatedAt), - dbTester.rules().insert(setUpdatedAt) - }; - this.ruleParams = Arrays.stream(ruleDtos) - .map(rule -> dbTester.rules().insertRuleParam(rule)) - .toArray(RuleParamDto[]::new); - - underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - // delete all data from DB to ensure tests rely on cache exclusively - dbTester.executeUpdateSql("delete from rules"); - dbTester.executeUpdateSql("delete from rules_parameters"); - assertThat(dbTester.countRowsOfTable("rules")).isZero(); - assertThat(dbTester.countRowsOfTable("rules_parameters")).isZero(); - } - - @Test - public void constructor_reads_rules_from_DB() { - DbClient dbClient = mock(DbClient.class); - DbSession dbSession = mock(DbSession.class); - RuleDao ruleDao = mock(RuleDao.class); - when(dbClient.openSession(anyBoolean())).thenReturn(dbSession); - when(dbClient.ruleDao()).thenReturn(ruleDao); - - new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - verify(dbClient).openSession(anyBoolean()); - verify(ruleDao).selectAll(dbSession); - verify(ruleDao).selectAllRuleParams(dbSession); - verifyNoMoreInteractions(ruleDao); - } - - @Test - public void constructor_reads_parameters_from_DB() { - DbClient dbClient = mock(DbClient.class); - DbSession dbSession = mock(DbSession.class); - RuleDao ruleDao = mock(RuleDao.class); - when(dbClient.openSession(anyBoolean())).thenReturn(dbSession); - when(dbClient.ruleDao()).thenReturn(ruleDao); - List ruleKeys = Arrays.asList(RuleKey.of("A", "B"), RuleKey.of("C", "D"), RuleKey.of("E", "F")); - when(ruleDao.selectAll(dbSession)).thenReturn(ruleKeys.stream().map(RuleTesting::newRule).collect(toList())); - - new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - verify(ruleDao).selectAllRuleParams(dbSession); - } - - @Test - public void findByKey_returns_all_loaded_rules() { - for (int i = 0; i < ruleDtos.length; i++) { - RuleDto ruleDto = ruleDtos[i]; - RuleParamDto ruleParam = ruleParams[i]; - - org.sonar.api.rules.Rule rule = underTest.findByKey(ruleDto.getKey()); - verifyRule(rule, ruleDto, ruleParam); - assertThat(underTest.findByKey(ruleDto.getRepositoryKey(), ruleDto.getRuleKey())) - .isSameAs(rule); - } - } - - @Test - public void findByKey_returns_null_when_RuleKey_is_null() { - assertThat(underTest.findByKey(null)).isNull(); - } - - @Test - public void findByKey_returns_null_when_repository_key_is_null() { - assertThat(underTest.findByKey(null, randomAlphabetic(2))).isNull(); - } - - @Test - public void findByKey_returns_null_when_key_is_null() { - assertThat(underTest.findByKey(randomAlphabetic(2), null)).isNull(); - } - - @Test - public void findByKey_returns_null_when_both_repository_key_and_key_are_null() { - assertThat(underTest.findByKey(null, null)).isNull(); - } - - @Test - public void find_returns_null_when_RuleQuery_is_empty() { - assertThat(underTest.find(null)).isNull(); - } - - @Test - public void find_returns_most_recent_rule_when_RuleQuery_has_no_non_null_field() { - Rule rule = underTest.find(RuleQuery.create()); - - assertThat(toRuleKey(rule)).isEqualTo(ruleDtos[5].getKey()); - } - - @Test - public void find_searches_by_exact_match_of_repository_key_and_returns_most_recent_rule() { - String repoKey = "ABCD"; - RuleDto[] sameRepoKey = { - dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(system2.now())) - }; - RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); - - CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(repoKey)))) - .isEqualTo(sameRepoKey[1].getKey()); - assertThat(toRuleKey(underTest.find(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey())))) - .isEqualTo(otherRule.getKey()); - assertThat(underTest.find(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase()))) - .isNull(); - assertThat(underTest.find(RuleQuery.create().withRepositoryKey(randomAlphabetic(3)))) - .isNull(); - } - - @Test - public void find_searches_by_exact_match_of_ruleKey_and_returns_most_recent_rule() { - String ruleKey = "ABCD"; - RuleDto[] sameRuleKey = { - dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())) - }; - RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); - - CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(ruleKey)))) - .isEqualTo(sameRuleKey[1].getKey()); - assertThat(toRuleKey(underTest.find(RuleQuery.create().withKey(otherRule.getRuleKey())))) - .isEqualTo(otherRule.getKey()); - assertThat(underTest.find(RuleQuery.create().withKey(ruleKey.toLowerCase()))) - .isNull(); - assertThat(underTest.find(RuleQuery.create().withKey(randomAlphabetic(3)))) - .isNull(); - } - - @Test - public void find_searches_by_exact_match_of_configKey_and_returns_most_recent_rule() { - String configKey = "ABCD"; - RuleDto[] sameConfigKey = { - dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())) - }; - RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); - - CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(configKey)))) - .isEqualTo(sameConfigKey[1].getKey()); - assertThat(toRuleKey(underTest.find(RuleQuery.create().withConfigKey(otherRule.getConfigKey())))) - .isEqualTo(otherRule.getKey()); - assertThat(underTest.find(RuleQuery.create().withConfigKey(configKey.toLowerCase()))) - .isNull(); - assertThat(underTest.find(RuleQuery.create().withConfigKey(randomAlphabetic(3)))) - .isNull(); - } - - @Test - public void find_searches_by_exact_match_and_match_on_all_criterias_and_returns_most_recent_match() { - String repoKey = "ABCD"; - String ruleKey = "EFGH"; - String configKey = "IJKL"; - RuleDto[] rules = { - dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())) - }; - RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey); - RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey); - RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey); - RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey); - RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey); - RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey); - RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey); - - CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - assertThat(toRuleKey(underTest.find(allQuery))).isEqualTo(rules[0].getKey()); - assertThat(toRuleKey(underTest.find(ruleAndConfigKeyQuery))).isEqualTo(rules[1].getKey()); - assertThat(toRuleKey(underTest.find(repoAndConfigKeyQuery))).isEqualTo(rules[2].getKey()); - assertThat(toRuleKey(underTest.find(repoAndKeyQuery))).isEqualTo(rules[0].getKey()); - assertThat(toRuleKey(underTest.find(repoKeyQuery))).isEqualTo(rules[2].getKey()); - assertThat(toRuleKey(underTest.find(ruleKeyQuery))).isEqualTo(rules[1].getKey()); - assertThat(toRuleKey(underTest.find(configKeyQuery))).isEqualTo(rules[2].getKey()); - } - - @Test - public void findAll_returns_empty_when_RuleQuery_is_empty() { - assertThat(underTest.findAll(null)).isEmpty(); - } - - @Test - public void findAll_returns_all_rules_when_RuleQuery_has_no_non_null_field() { - assertThat(underTest.findAll(RuleQuery.create())) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsOnly(Arrays.stream(ruleDtos).map(RuleDto::getKey).toArray(RuleKey[]::new)); - } - - @Test - public void findAll_returns_all_rules_with_exact_same_repository_key_and_order_them_most_recent_first() { - String repoKey = "ABCD"; - long currentTimeMillis = System.currentTimeMillis(); - RuleDto[] sameRepoKey = { - dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(currentTimeMillis + system2.now())), - dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setUpdatedAt(currentTimeMillis + system2.now())) - }; - RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(currentTimeMillis + system2.now())); - - CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey))) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(sameRepoKey[1].getKey(), sameRepoKey[0].getKey()); - assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(otherRule.getRepositoryKey()))) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(otherRule.getKey()); - assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(repoKey.toLowerCase()))) - .isEmpty(); - assertThat(underTest.findAll(RuleQuery.create().withRepositoryKey(randomAlphabetic(3)))) - .isEmpty(); - } - - @Test - public void findAll_returns_all_rules_with_exact_same_rulekey_and_order_them_most_recent_first() { - String ruleKey = "ABCD"; - RuleDto[] sameRuleKey = { - dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setUpdatedAt(system2.now())) - }; - RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); - - CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey))) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(sameRuleKey[1].getKey(), sameRuleKey[0].getKey()); - assertThat(underTest.findAll(RuleQuery.create().withKey(otherRule.getRuleKey()))) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(otherRule.getKey()); - assertThat(underTest.findAll(RuleQuery.create().withKey(ruleKey.toLowerCase()))) - .isEmpty(); - assertThat(underTest.findAll(RuleQuery.create().withKey(randomAlphabetic(3)))) - .isEmpty(); - } - - @Test - public void findAll_returns_all_rules_with_exact_same_configkey_and_order_them_most_recent_first() { - String configKey = "ABCD"; - RuleDto[] sameConfigKey = { - dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setConfigKey(configKey).setUpdatedAt(system2.now())) - }; - RuleDto otherRule = dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())); - - CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey))) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(sameConfigKey[1].getKey(), sameConfigKey[0].getKey()); - assertThat(underTest.findAll(RuleQuery.create().withConfigKey(otherRule.getConfigKey()))) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(otherRule.getKey()); - assertThat(underTest.findAll(RuleQuery.create().withConfigKey(configKey.toLowerCase()))) - .isEmpty(); - assertThat(underTest.findAll(RuleQuery.create().withConfigKey(randomAlphabetic(3)))) - .isEmpty(); - } - - @Test - public void findAll_returns_all_rules_which_match_exactly_all_criteria_and_order_then_by_most_recent_first() { - String repoKey = "ABCD"; - String ruleKey = "EFGH"; - String configKey = "IJKL"; - RuleDto[] rules = { - dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setRuleKey(ruleKey).setConfigKey(configKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setRepositoryKey(repoKey).setConfigKey(configKey).setUpdatedAt(system2.now())), - dbTester.rules().insert(rule -> rule.setUpdatedAt(system2.now())) - }; - RuleQuery allQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey).withConfigKey(configKey); - RuleQuery ruleAndConfigKeyQuery = RuleQuery.create().withKey(ruleKey).withConfigKey(configKey); - RuleQuery repoAndConfigKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withConfigKey(configKey); - RuleQuery repoAndKeyQuery = RuleQuery.create().withRepositoryKey(repoKey).withKey(ruleKey); - RuleQuery configKeyQuery = RuleQuery.create().withConfigKey(configKey); - RuleQuery ruleKeyQuery = RuleQuery.create().withKey(ruleKey); - RuleQuery repoKeyQuery = RuleQuery.create().withRepositoryKey(repoKey); - - CachingRuleFinder underTest = new CachingRuleFinder(dbClient, ruleDescriptionFormatter); - - assertThat(underTest.findAll(allQuery)) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(rules[0].getKey()); - assertThat(underTest.findAll(ruleAndConfigKeyQuery)) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(rules[1].getKey(), rules[0].getKey()); - assertThat(underTest.findAll(repoAndConfigKeyQuery)) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(rules[2].getKey(), rules[0].getKey()); - assertThat(underTest.findAll(repoAndKeyQuery)) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(rules[0].getKey()); - assertThat(underTest.findAll(repoKeyQuery)) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(rules[2].getKey(), rules[0].getKey()); - assertThat(underTest.findAll(ruleKeyQuery)) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(rules[1].getKey(), rules[0].getKey()); - assertThat(underTest.findAll(configKeyQuery)) - .extracting(CachingRuleFinderTest::toRuleKey) - .containsExactly(rules[2].getKey(), rules[1].getKey(), rules[0].getKey()); - } - - @Test - public void findDtoByKey_finds_rules() { - for(RuleDto dto : ruleDtos) { - assertThat(underTest.findDtoByKey(dto.getKey())).contains(dto); - } - } - - @Test - public void findDtoByUuid_finds_rules() { - for(RuleDto dto : ruleDtos) { - assertThat(underTest.findDtoByUuid(dto.getUuid())).contains(dto); - } - } - - @Test - public void findDtoByKey_returns_empty_if_rule_not_found() { - assertThat(underTest.findDtoByKey(RuleKey.of("unknown", "unknown"))).isEmpty(); - } - - @Test - public void findDtoByUuid_returns_empty_if_rule_not_found() { - assertThat(underTest.findDtoByUuid("unknown")).isEmpty(); - } - - @Test - public void findAll_returns_all_rules() { - assertThat(underTest.findAll()).containsOnly(ruleDtos); - } - - private static RuleKey toRuleKey(Rule rule) { - return RuleKey.of(rule.getRepositoryKey(), rule.getKey()); - } - - private void verifyRule(@Nullable Rule rule, RuleDto ruleDto, RuleParamDto ruleParam) { - assertThat(rule).isNotNull(); - - assertThat(rule.getName()).isEqualTo(ruleDto.getName()); - assertThat(rule.getLanguage()).isEqualTo(ruleDto.getLanguage()); - assertThat(rule.getKey()).isEqualTo(ruleDto.getRuleKey()); - assertThat(rule.getConfigKey()).isEqualTo(ruleDto.getConfigKey()); - assertThat(rule.isTemplate()).isEqualTo(ruleDto.isTemplate()); - assertThat(rule.getCreatedAt().getTime()).isEqualTo(ruleDto.getCreatedAt()); - assertThat(rule.getUpdatedAt().getTime()).isEqualTo(ruleDto.getUpdatedAt()); - assertThat(rule.getRepositoryKey()).isEqualTo(ruleDto.getRepositoryKey()); - assertThat(rule.getSeverity().name()).isEqualTo(ruleDto.getSeverityString()); - assertThat(rule.getSystemTags()).isEqualTo(ruleDto.getSystemTags().toArray(new String[0])); - assertThat(rule.getTags()).isEmpty(); - assertThat(rule.getDescription()).isEqualTo(ruleDto.getDefaultRuleDescriptionSection().getContent()); - - assertThat(rule.getParams()).hasSize(1); - org.sonar.api.rules.RuleParam param = rule.getParams().iterator().next(); - assertThat(param.getRule()).isSameAs(rule); - assertThat(param.getKey()).isEqualTo(ruleParam.getName()); - assertThat(param.getDescription()).isEqualTo(ruleParam.getDescription()); - assertThat(param.getType()).isEqualTo(ruleParam.getType()); - assertThat(param.getDefaultValue()).isEqualTo(ruleParam.getDefaultValue()); - } -} diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/PersistentSettingsIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/PersistentSettingsIT.java new file mode 100644 index 00000000000..efa71811800 --- /dev/null +++ b/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/PersistentSettingsIT.java @@ -0,0 +1,66 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.config.internal.MapSettings; +import org.sonar.api.config.internal.Settings; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; +import org.sonar.server.setting.SettingsChangeNotifier; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class PersistentSettingsIT { + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + private Settings delegate = new MapSettings(); + private SettingsChangeNotifier changeNotifier = mock(SettingsChangeNotifier.class); + private PersistentSettings underTest = new PersistentSettings(delegate, dbTester.getDbClient(), changeNotifier); + + @Test + public void insert_property_into_database_and_notify_extensions() { + assertThat(underTest.getString("foo")).isNull(); + + underTest.saveProperty("foo", "bar"); + + assertThat(underTest.getString("foo")).isEqualTo("bar"); + assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty("foo").getValue()).isEqualTo("bar"); + verify(changeNotifier).onGlobalPropertyChange("foo", "bar"); + } + + @Test + public void delete_property_from_database_and_notify_extensions() { + underTest.saveProperty("foo", "bar"); + underTest.saveProperty("foo", null); + + assertThat(underTest.getString("foo")).isNull(); + assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty("foo")).isNull(); + verify(changeNotifier).onGlobalPropertyChange("foo", null); + } + + @Test + public void getSettings_returns_delegate() { + assertThat(underTest.getSettings()).isSameAs(delegate); + } +} diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/StartupMetadataPersisterIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/StartupMetadataPersisterIT.java new file mode 100644 index 00000000000..89d7050a583 --- /dev/null +++ b/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/StartupMetadataPersisterIT.java @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.CoreProperties; +import org.sonar.api.utils.DateUtils; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; +import org.sonar.db.property.PropertyDto; + +import static org.assertj.core.api.Assertions.assertThat; + +public class StartupMetadataPersisterIT { + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private StartupMetadata metadata = new StartupMetadata(123_456_789L); + private StartupMetadataPersister underTest = new StartupMetadataPersister(metadata, dbTester.getDbClient()); + + @Test + public void persist_metadata_at_startup() { + underTest.start(); + + assertPersistedProperty(CoreProperties.SERVER_STARTTIME, DateUtils.formatDateTime(metadata.getStartedAt())); + + underTest.stop(); + } + + private void assertPersistedProperty(String propertyKey, String expectedValue) { + PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey); + assertThat(prop.getValue()).isEqualTo(expectedValue); + } +} diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/serverid/ServerIdManagerIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/serverid/ServerIdManagerIT.java new file mode 100644 index 00000000000..85754d2e20e --- /dev/null +++ b/server/sonar-webserver-core/src/it/java/org/sonar/server/platform/serverid/ServerIdManagerIT.java @@ -0,0 +1,292 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.serverid; + +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import org.assertj.core.api.ThrowableAssert.ThrowingCallable; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sonar.api.CoreProperties; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.internal.SonarRuntimeImpl; +import org.sonar.api.utils.System2; +import org.sonar.api.utils.Version; +import org.sonar.core.platform.ServerId; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.property.PropertyDto; +import org.sonar.server.platform.NodeInformation; +import org.sonar.server.property.InternalProperties; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.sonar.api.SonarQubeSide.COMPUTE_ENGINE; +import static org.sonar.api.SonarQubeSide.SERVER; +import static org.sonar.core.platform.ServerId.DATABASE_ID_LENGTH; +import static org.sonar.core.platform.ServerId.NOT_UUID_DATASET_ID_LENGTH; +import static org.sonar.core.platform.ServerId.UUID_DATASET_ID_LENGTH; + +@RunWith(DataProviderRunner.class) +public class ServerIdManagerIT { + + private static final ServerId WITH_DATABASE_ID_SERVER_ID = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(NOT_UUID_DATASET_ID_LENGTH)); + private static final String CHECKSUM_1 = randomAlphanumeric(12); + + @Rule + public final DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final ServerIdChecksum serverIdChecksum = mock(ServerIdChecksum.class); + private final ServerIdFactory serverIdFactory = mock(ServerIdFactory.class); + private final DbClient dbClient = dbTester.getDbClient(); + private final DbSession dbSession = dbTester.getSession(); + private final NodeInformation nodeInformation = mock(NodeInformation.class); + private ServerIdManager underTest; + + @After + public void tearDown() { + if (underTest != null) { + underTest.stop(); + } + } + + @Test + public void web_leader_persists_new_server_id_if_missing() { + mockCreateNewServerId(); + mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); + when(nodeInformation.isStartupLeader()).thenReturn(true); + + test(SERVER); + + verifyDb(CHECKSUM_1); + verifyCreateNewServerIdFromScratch(); + } + + @Test + public void web_leader_persists_new_server_id_if_value_is_empty() { + insertServerId(""); + mockCreateNewServerId(); + mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); + when(nodeInformation.isStartupLeader()).thenReturn(true); + + test(SERVER); + + verifyDb(CHECKSUM_1); + verifyCreateNewServerIdFromScratch(); + } + + @Test + public void web_leader_keeps_existing_server_id_if_valid() { + insertServerId(WITH_DATABASE_ID_SERVER_ID); + insertChecksum(CHECKSUM_1); + mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); + when(nodeInformation.isStartupLeader()).thenReturn(true); + + test(SERVER); + + verifyDb(CHECKSUM_1); + } + + @Test + public void web_leader_creates_server_id_from_current_serverId_with_databaseId_if_checksum_fails() { + ServerId currentServerId = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(UUID_DATASET_ID_LENGTH)); + insertServerId(currentServerId); + insertChecksum("does_not_match_WITH_DATABASE_ID_SERVER_ID"); + mockChecksumOf(currentServerId, "matches_WITH_DATABASE_ID_SERVER_ID"); + mockCreateNewServerIdFrom(currentServerId); + mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); + when(nodeInformation.isStartupLeader()).thenReturn(true); + + test(SERVER); + + verifyDb(CHECKSUM_1); + verifyCreateNewServerIdFrom(currentServerId); + } + + @Test + public void web_leader_generates_missing_checksum_for_current_serverId_with_databaseId() { + insertServerId(WITH_DATABASE_ID_SERVER_ID); + mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); + when(nodeInformation.isStartupLeader()).thenReturn(true); + + test(SERVER); + + verifyDb(CHECKSUM_1); + } + + @Test + public void web_follower_does_not_fail_if_server_id_matches_checksum() { + insertServerId(WITH_DATABASE_ID_SERVER_ID); + insertChecksum(CHECKSUM_1); + mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); + when(nodeInformation.isStartupLeader()).thenReturn(false); + + test(SERVER); + + // no changes + verifyDb(CHECKSUM_1); + } + + @Test + public void web_follower_fails_if_server_id_is_missing() { + when(nodeInformation.isStartupLeader()).thenReturn(false); + + expectMissingServerIdException(() -> test(SERVER)); + } + + @Test + public void web_follower_fails_if_server_id_is_empty() { + insertServerId(""); + when(nodeInformation.isStartupLeader()).thenReturn(false); + + expectEmptyServerIdException(() -> test(SERVER)); + } + + @Test + public void web_follower_fails_if_checksum_does_not_match() { + String dbChecksum = "boom"; + insertServerId(WITH_DATABASE_ID_SERVER_ID); + insertChecksum(dbChecksum); + mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); + when(nodeInformation.isStartupLeader()).thenReturn(false); + + try { + test(SERVER); + fail("An ISE should have been raised"); + } catch (IllegalStateException e) { + assertThat(e.getMessage()).isEqualTo("Server ID is invalid"); + // no changes + verifyDb(dbChecksum); + } + } + + @Test + public void compute_engine_does_not_fail_if_server_id_is_valid() { + insertServerId(WITH_DATABASE_ID_SERVER_ID); + insertChecksum(CHECKSUM_1); + mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); + + test(COMPUTE_ENGINE); + + // no changes + verifyDb(CHECKSUM_1); + } + + @Test + public void compute_engine_fails_if_server_id_is_missing() { + expectMissingServerIdException(() -> test(COMPUTE_ENGINE)); + } + + @Test + public void compute_engine_fails_if_server_id_is_empty() { + insertServerId(""); + + expectEmptyServerIdException(() -> test(COMPUTE_ENGINE)); + } + + @Test + public void compute_engine_fails_if_server_id_is_invalid() { + String dbChecksum = "boom"; + insertServerId(WITH_DATABASE_ID_SERVER_ID); + insertChecksum(dbChecksum); + mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); + + try { + test(SERVER); + fail("An ISE should have been raised"); + } catch (IllegalStateException e) { + assertThat(e.getMessage()).isEqualTo("Server ID is invalid"); + // no changes + verifyDb(dbChecksum); + } + } + + private void expectEmptyServerIdException(ThrowingCallable callback) { + assertThatThrownBy(callback) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Property sonar.core.id is empty in database"); + } + + private void expectMissingServerIdException(ThrowingCallable callback) { + assertThatThrownBy(callback) + .isInstanceOf(IllegalStateException.class) + .hasMessage("Property sonar.core.id is missing in database"); + } + + private void verifyDb(String expectedChecksum) { + assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID)) + .extracting(PropertyDto::getValue) + .isEqualTo(ServerIdManagerIT.WITH_DATABASE_ID_SERVER_ID.toString()); + assertThat(dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.SERVER_ID_CHECKSUM)) + .hasValue(expectedChecksum); + } + + private void mockCreateNewServerId() { + when(serverIdFactory.create()).thenReturn(WITH_DATABASE_ID_SERVER_ID); + when(serverIdFactory.create(any())).thenThrow(new IllegalStateException("new ServerId should not be created from current server id")); + } + + private void mockCreateNewServerIdFrom(ServerId currentServerId) { + when(serverIdFactory.create()).thenThrow(new IllegalStateException("new ServerId should be created from current server id")); + when(serverIdFactory.create(currentServerId)).thenReturn(ServerIdManagerIT.WITH_DATABASE_ID_SERVER_ID); + } + + private void verifyCreateNewServerIdFromScratch() { + verify(serverIdFactory).create(); + } + + private void verifyCreateNewServerIdFrom(ServerId currentServerId) { + verify(serverIdFactory).create(currentServerId); + } + + private void mockChecksumOf(ServerId serverId, String checksum1) { + when(serverIdChecksum.computeFor(serverId.toString())).thenReturn(checksum1); + } + + private void insertServerId(ServerId serverId) { + insertServerId(serverId.toString()); + } + + private void insertServerId(String serverId) { + dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(CoreProperties.SERVER_ID).setValue(serverId), + null, null, null, null); + dbSession.commit(); + } + + private void insertChecksum(String value) { + dbClient.internalPropertiesDao().save(dbSession, InternalProperties.SERVER_ID_CHECKSUM, value); + dbSession.commit(); + } + + private void test(SonarQubeSide side) { + underTest = new ServerIdManager(serverIdChecksum, serverIdFactory, dbClient, SonarRuntimeImpl + .forSonarQube(Version.create(6, 7), side, SonarEdition.COMMUNITY), nodeInformation); + underTest.start(); + } +} diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterMetricsIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterMetricsIT.java new file mode 100644 index 00000000000..1f1b1db9017 --- /dev/null +++ b/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterMetricsIT.java @@ -0,0 +1,198 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.startup; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.stream.IntStream; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.measures.CoreMetrics; +import org.sonar.api.measures.Metric; +import org.sonar.api.measures.Metrics; +import org.sonar.api.utils.System2; +import org.sonar.core.util.SequenceUuidFactory; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.metric.MetricDto; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; + +public class RegisterMetricsIT { + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final UuidFactory uuidFactory = new SequenceUuidFactory(); + private final DbClient dbClient = dbTester.getDbClient(); + private final RegisterMetrics register = new RegisterMetrics(dbClient, uuidFactory); + + /** + * Insert new metrics, including custom metrics + */ + @Test + public void insert_new_metrics() { + Metric m1 = new Metric.Builder("m1", "One", Metric.ValueType.FLOAT) + .setDescription("desc1") + .setDirection(1) + .setQualitative(true) + .setDomain("domain1") + .setUserManaged(false) + .create(); + Metric custom = new Metric.Builder("custom", "Custom", Metric.ValueType.FLOAT) + .setDescription("This is a custom metric") + .setUserManaged(true) + .create(); + + register.register(asList(m1, custom)); + + Map metricsByKey = selectAllMetrics(); + assertThat(metricsByKey).hasSize(2); + assertEquals(m1, metricsByKey.get("m1")); + assertEquals(custom, metricsByKey.get("custom")); + } + + /** + * Update existing metrics + */ + @Test + public void update_metrics() { + dbTester.measures().insertMetric(t -> t.setKey("m1") + .setShortName("name") + .setValueType(Metric.ValueType.INT.name()) + .setDescription("old desc") + .setDomain("old domain") + .setShortName("old short name") + .setQualitative(false) + .setEnabled(true) + .setOptimizedBestValue(false) + .setDirection(1) + .setHidden(false)); + + Metric m1 = new Metric.Builder("m1", "New name", Metric.ValueType.FLOAT) + .setDescription("new description") + .setDirection(-1) + .setQualitative(true) + .setDomain("new domain") + .setUserManaged(false) + .setDecimalScale(3) + .setHidden(true) + .create(); + register.register(asList(m1)); + + Map metricsByKey = selectAllMetrics(); + assertThat(metricsByKey).hasSize(1); + assertEquals(m1, metricsByKey.get("m1")); + } + + @Test + public void disable_undefined_metrics() { + Random random = new Random(); + int count = 1 + random.nextInt(10); + IntStream.range(0, count) + .forEach(t -> dbTester.measures().insertMetric(m -> m.setEnabled(random.nextBoolean()))); + + register.register(Collections.emptyList()); + + assertThat(selectAllMetrics().values().stream()) + .extracting(MetricDto::isEnabled) + .containsOnly(IntStream.range(0, count).mapToObj(t -> false).toArray(Boolean[]::new)); + } + + @Test + public void enable_disabled_metrics() { + MetricDto enabledMetric = dbTester.measures().insertMetric(t -> t.setEnabled(true)); + MetricDto disabledMetric = dbTester.measures().insertMetric(t -> t.setEnabled(false)); + + register.register(asList(builderOf(enabledMetric).create(), builderOf(disabledMetric).create())); + + assertThat(selectAllMetrics().values()) + .extracting(MetricDto::isEnabled) + .containsOnly(true, true); + } + + @Test + public void insert_core_metrics() { + register.start(); + + assertThat(dbTester.countRowsOfTable("metrics")).isEqualTo(CoreMetrics.getMetrics().size()); + } + + @Test + public void fail_if_duplicated_plugin_metrics() { + Metrics plugin1 = new TestMetrics(new Metric.Builder("m1", "In first plugin", Metric.ValueType.FLOAT).create()); + Metrics plugin2 = new TestMetrics(new Metric.Builder("m1", "In second plugin", Metric.ValueType.FLOAT).create()); + + assertThatThrownBy(() -> new RegisterMetrics(dbClient, uuidFactory, new Metrics[] {plugin1, plugin2}).start()) + .isInstanceOf(IllegalStateException.class); + } + + @Test + public void fail_if_plugin_duplicates_core_metric() { + Metrics plugin = new TestMetrics(new Metric.Builder("ncloc", "In plugin", Metric.ValueType.FLOAT).create()); + + assertThatThrownBy(() -> new RegisterMetrics(dbClient, uuidFactory, new Metrics[] {plugin}).start()) + .isInstanceOf(IllegalStateException.class); + } + + private static class TestMetrics implements Metrics { + private final List metrics; + + public TestMetrics(Metric... metrics) { + this.metrics = asList(metrics); + } + + @Override + public List getMetrics() { + return metrics; + } + } + + private Map selectAllMetrics() { + return dbTester.getDbClient().metricDao().selectAll(dbTester.getSession()) + .stream() + .collect(uniqueIndex(MetricDto::getKey)); + } + + private void assertEquals(Metric expected, MetricDto actual) { + assertThat(actual.getKey()).isEqualTo(expected.getKey()); + assertThat(actual.getShortName()).isEqualTo(expected.getName()); + assertThat(actual.getValueType()).isEqualTo(expected.getType().name()); + assertThat(actual.getDescription()).isEqualTo(expected.getDescription()); + assertThat(actual.getDirection()).isEqualTo(expected.getDirection()); + assertThat(actual.isQualitative()).isEqualTo(expected.getQualitative()); + } + + private static Metric.Builder builderOf(MetricDto enabledMetric) { + return new Metric.Builder(enabledMetric.getKey(), enabledMetric.getShortName(), Metric.ValueType.valueOf(enabledMetric.getValueType())) + .setDescription(enabledMetric.getDescription()) + .setDirection(enabledMetric.getDirection()) + .setQualitative(enabledMetric.isQualitative()) + .setQualitative(enabledMetric.isQualitative()) + .setDomain(enabledMetric.getDomain()) + .setHidden(enabledMetric.isHidden()); + } +} diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterPluginsIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterPluginsIT.java new file mode 100644 index 00000000000..52f24f023df --- /dev/null +++ b/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/RegisterPluginsIT.java @@ -0,0 +1,238 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.startup; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import javax.annotation.Nullable; +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.utils.System2; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.util.UuidFactory; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.plugin.PluginDto; +import org.sonar.db.plugin.PluginDto.Type; +import org.sonar.server.plugins.PluginFilesAndMd5; +import org.sonar.core.plugin.PluginType; +import org.sonar.server.plugins.ServerPlugin; +import org.sonar.server.plugins.ServerPluginRepository; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; + +public class RegisterPluginsIT { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final long now = 12345L; + private final DbClient dbClient = dbTester.getDbClient(); + private final ServerPluginRepository serverPluginRepository = new ServerPluginRepository(); + private final UuidFactory uuidFactory = mock(UuidFactory.class); + private final System2 system2 = mock(System2.class); + private final RegisterPlugins register = new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2); + + + @Before + public void setUp() { + when(system2.now()).thenReturn(now); + } + + /** + * Insert new plugins + */ + @Test + public void insert_new_plugins() throws IOException { + addPlugin("java", null); + addPlugin("javacustom", "java"); + when(uuidFactory.create()).thenReturn("a").thenReturn("b").thenThrow(new IllegalStateException("Should be called only twice")); + register.start(); + + Map pluginsByKey = selectAllPlugins(); + assertThat(pluginsByKey).hasSize(2); + verify(pluginsByKey.get("java"), Type.BUNDLED, null, "93f725a07423fe1c889f448b33d21f46", false, now, now); + verify(pluginsByKey.get("javacustom"), Type.BUNDLED, "java", "bf8b3d4cb91efc5f9ef0bcd02f128ebf", false, now, now); + + register.stop(); + } + + @Test + public void update_removed_plugins() throws IOException { + // will be flagged as removed + dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() + .setUuid("a") + .setKee("java") + .setBasePluginKey(null) + .setFileHash("bd451e47a1aa76e73da0359cef63dd63") + .setType(Type.BUNDLED) + .setCreatedAt(1L) + .setUpdatedAt(1L)); + // already flagged as removed + dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() + .setUuid("b") + .setKee("java2") + .setBasePluginKey(null) + .setFileHash("bd451e47a1aa76e73da0359cef63dd63") + .setType(Type.BUNDLED) + .setRemoved(true) + .setCreatedAt(1L) + .setUpdatedAt(1L)); + dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() + .setUuid("c") + .setKee("csharp") + .setBasePluginKey(null) + .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78") + .setType(Type.EXTERNAL) + .setCreatedAt(1L) + .setUpdatedAt(1L)); + dbTester.commit(); + + addPlugin("csharp", PluginType.EXTERNAL, null); + register.start(); + + Map pluginsByKey = selectAllPlugins(); + assertThat(pluginsByKey).hasSize(3); + verify(pluginsByKey.get("java"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, now); + verify(pluginsByKey.get("java2"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, 1L); + verify(pluginsByKey.get("csharp"), Type.EXTERNAL, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, 1L); + } + + @Test + public void re_add_previously_removed_plugin() throws IOException { + dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() + .setUuid("c") + .setKee("csharp") + .setBasePluginKey(null) + .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78") + .setType(Type.EXTERNAL) + .setRemoved(true) + .setCreatedAt(1L) + .setUpdatedAt(1L)); + dbTester.commit(); + + addPlugin("csharp", PluginType.EXTERNAL, null); + register.start(); + + Map pluginsByKey = selectAllPlugins(); + assertThat(pluginsByKey).hasSize(1); + verify(pluginsByKey.get("csharp"), Type.EXTERNAL, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, now); + } + + /** + * Update existing plugins, only when checksum is different and don't remove uninstalled plugins + */ + @Test + public void update_changed_plugins() throws IOException { + dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() + .setUuid("a") + .setKee("java") + .setBasePluginKey(null) + .setFileHash("bd451e47a1aa76e73da0359cef63dd63") + .setType(Type.BUNDLED) + .setCreatedAt(1L) + .setUpdatedAt(1L)); + dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() + .setUuid("b") + .setKee("javacustom") + .setBasePluginKey("java") + .setFileHash("de9b2de3ddc0680904939686c0dba5be") + .setType(Type.BUNDLED) + .setCreatedAt(1L) + .setUpdatedAt(1L)); + dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() + .setUuid("c") + .setKee("csharp") + .setBasePluginKey(null) + .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78") + .setType(Type.EXTERNAL) + .setCreatedAt(1L) + .setUpdatedAt(1L)); + dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() + .setUuid("d") + .setKee("new-measures") + .setBasePluginKey(null) + .setFileHash("6d24712cf701c41ce5eaa948e0bd6d22") + .setType(Type.EXTERNAL) + .setCreatedAt(1L) + .setUpdatedAt(1L)); + + dbTester.commit(); + + addPlugin("javacustom", PluginType.BUNDLED, "java2"); + // csharp plugin type changed + addPlugin("csharp", PluginType.BUNDLED, null); + + register.start(); + + Map pluginsByKey = selectAllPlugins(); + assertThat(pluginsByKey).hasSize(4); + verify(pluginsByKey.get("java"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, now); + verify(pluginsByKey.get("javacustom"), Type.BUNDLED, "java2", "bf8b3d4cb91efc5f9ef0bcd02f128ebf", false, 1L, now); + verify(pluginsByKey.get("csharp"), Type.BUNDLED, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, now); + verify(pluginsByKey.get("new-measures"), Type.EXTERNAL, null, "6d24712cf701c41ce5eaa948e0bd6d22", true, 1L, now); + } + + private ServerPlugin addPlugin(String key, @Nullable String basePlugin) throws IOException { + return addPlugin(key, PluginType.BUNDLED, basePlugin); + } + + private ServerPlugin addPlugin(String key, PluginType type, @Nullable String basePlugin) throws IOException { + File file = createPluginFile(key); + PluginFilesAndMd5.FileAndMd5 jar = new PluginFilesAndMd5.FileAndMd5(file); + PluginInfo info = new PluginInfo(key) + .setBasePlugin(basePlugin) + .setJarFile(file); + ServerPlugin serverPlugin = new ServerPlugin(info, type, null, jar, null); + serverPluginRepository.addPlugin(serverPlugin); + return serverPlugin; + } + + private File createPluginFile(String key) throws IOException { + File pluginJar = temp.newFile(); + FileUtils.write(pluginJar, key, StandardCharsets.UTF_8); + return pluginJar; + } + + private Map selectAllPlugins() { + return dbTester.getDbClient().pluginDao().selectAll(dbTester.getSession()).stream() + .collect(uniqueIndex(PluginDto::getKee)); + } + + private void verify(PluginDto pluginDto, Type type, @Nullable String basePluginKey, String fileHash, boolean removed, @Nullable Long createdAt, long updatedAt) { + assertThat(pluginDto.getBasePluginKey()).isEqualTo(basePluginKey); + assertThat(pluginDto.getType()).isEqualTo(type); + assertThat(pluginDto.getFileHash()).isEqualTo(fileHash); + assertThat(pluginDto.isRemoved()).isEqualTo(removed); + assertThat(pluginDto.getCreatedAt()).isEqualTo(createdAt); + assertThat(pluginDto.getUpdatedAt()).isEqualTo(updatedAt); + } + +} diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/UpgradeSuggestionsCleanerIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/UpgradeSuggestionsCleanerIT.java new file mode 100644 index 00000000000..fdf008ac82a --- /dev/null +++ b/server/sonar-webserver-core/src/it/java/org/sonar/server/startup/UpgradeSuggestionsCleanerIT.java @@ -0,0 +1,148 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.startup; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.sonar.api.SonarEdition; +import org.sonar.api.SonarRuntime; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; +import org.sonar.db.ce.CeTaskMessageDto; +import org.sonar.db.ce.CeTaskMessageType; +import org.sonar.db.user.UserDismissedMessageDto; +import org.sonar.db.user.UserDto; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(DataProviderRunner.class) +public class UpgradeSuggestionsCleanerIT { + private static final String TASK_UUID = "b8d564dd-4ceb-4dba-8a3d-5fafa2d72cdf"; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final SonarRuntime sonarRuntime = mock(SonarRuntime.class); + private final UpgradeSuggestionsCleaner underTest = new UpgradeSuggestionsCleaner(dbTester.getDbClient(), sonarRuntime); + + private UserDto user; + + @Before + public void setup() { + user = dbTester.users().insertUser(); + } + + @DataProvider + public static Object[][] editionsWithCleanup() { + return new Object[][] { + {SonarEdition.DEVELOPER}, + {SonarEdition.ENTERPRISE}, + {SonarEdition.DATACENTER} + }; + } + + @DataProvider + public static Object[][] allEditions() { + return new Object[][] { + {SonarEdition.COMMUNITY}, + {SonarEdition.DEVELOPER}, + {SonarEdition.ENTERPRISE}, + {SonarEdition.DATACENTER} + }; + } + + @Test + @UseDataProvider("editionsWithCleanup") + public void start_cleans_up_obsolete_upgrade_suggestions(SonarEdition edition) { + when(sonarRuntime.getEdition()).thenReturn(edition); + insertCeTaskMessage("ctm1", CeTaskMessageType.GENERIC, "msg1"); + insertCeTaskMessage("ctm2", CeTaskMessageType.GENERIC, "msg2"); + insertCeTaskMessage("ctm3", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE, "upgrade-msg-1"); + insertInUserDismissedMessages("u1", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + insertInUserDismissedMessages("u2", CeTaskMessageType.GENERIC); + + underTest.start(); + underTest.stop(); + + assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)) + .extracting(CeTaskMessageDto::getUuid) + .containsExactly("ctm1", "ctm2"); + assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)) + .extracting(UserDismissedMessageDto::getUuid) + .containsExactly("u2"); + } + + @Test + public void start_does_nothing_in_community_edition() { + when(sonarRuntime.getEdition()).thenReturn(SonarEdition.COMMUNITY); + insertCeTaskMessage("ctm1", CeTaskMessageType.GENERIC, "msg1"); + insertCeTaskMessage("ctm2", CeTaskMessageType.GENERIC, "msg2"); + insertCeTaskMessage("ctm3", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE, "upgrade-msg-1"); + insertInUserDismissedMessages("u1", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); + insertInUserDismissedMessages("u2", CeTaskMessageType.GENERIC); + + underTest.start(); + + assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)) + .extracting(CeTaskMessageDto::getUuid) + .containsExactly("ctm1", "ctm2", "ctm3"); + assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)) + .extracting(UserDismissedMessageDto::getUuid) + .containsExactlyInAnyOrder("u1", "u2"); + } + + @Test + @UseDataProvider("allEditions") + public void start_does_nothing_when_no_suggest_upgrade_messages(SonarEdition edition) { + when(sonarRuntime.getEdition()).thenReturn(edition); + + underTest.start(); + + assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)).isEmpty(); + assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)).isEmpty(); + } + + private void insertCeTaskMessage(String uuid, CeTaskMessageType messageType, String msg) { + CeTaskMessageDto dto = new CeTaskMessageDto() + .setUuid(uuid) + .setMessage(msg) + .setType(messageType) + .setTaskUuid(TASK_UUID); + dbTester.getDbClient().ceTaskMessageDao().insert(dbTester.getSession(), dto); + dbTester.getSession().commit(); + } + + private void insertInUserDismissedMessages(String uuid, CeTaskMessageType messageType) { + UserDismissedMessageDto dto = new UserDismissedMessageDto() + .setUuid(uuid) + .setUserUuid(user.getUuid()) + .setProjectUuid("PROJECT_1") + .setCeMessageType(messageType); + dbTester.getDbClient().userDismissedMessagesDao().insert(dbTester.getSession(), dto); + dbTester.getSession().commit(); + } +} diff --git a/server/sonar-webserver-core/src/it/java/org/sonar/server/webhook/WebhookQGChangeEventListenerIT.java b/server/sonar-webserver-core/src/it/java/org/sonar/server/webhook/WebhookQGChangeEventListenerIT.java new file mode 100644 index 00000000000..31d31456b61 --- /dev/null +++ b/server/sonar-webserver-core/src/it/java/org/sonar/server/webhook/WebhookQGChangeEventListenerIT.java @@ -0,0 +1,329 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.webhook; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.Set; +import java.util.function.Supplier; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mockito; +import org.sonar.api.config.Configuration; +import org.sonar.api.measures.Metric; +import org.sonar.api.utils.System2; +import org.sonar.core.util.UuidFactoryFast; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.component.AnalysisPropertyDto; +import org.sonar.db.component.BranchDto; +import org.sonar.db.component.BranchType; +import org.sonar.db.component.SnapshotDto; +import org.sonar.db.project.ProjectDto; +import org.sonar.server.qualitygate.EvaluatedQualityGate; +import org.sonar.server.qualitygate.changeevent.QGChangeEvent; +import org.sonar.server.qualitygate.changeevent.QGChangeEventListener; + +import static java.util.Arrays.stream; +import static java.util.Collections.emptySet; +import static java.util.stream.Stream.concat; +import static java.util.stream.Stream.of; +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; +import static org.sonar.core.util.stream.MoreCollectors.toArrayList; +import static org.sonar.db.component.BranchType.BRANCH; + +@RunWith(DataProviderRunner.class) +public class WebhookQGChangeEventListenerIT { + + private static final Set CHANGED_ISSUES_ARE_IGNORED = emptySet(); + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private DbClient dbClient = dbTester.getDbClient(); + + private EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class); + private WebHooks webHooks = mock(WebHooks.class); + private WebhookPayloadFactory webhookPayloadFactory = mock(WebhookPayloadFactory.class); + private DbClient spiedOnDbClient = Mockito.spy(dbClient); + private WebhookQGChangeEventListener underTest = new WebhookQGChangeEventListener(webHooks, webhookPayloadFactory, spiedOnDbClient); + private DbClient mockedDbClient = mock(DbClient.class); + private WebhookQGChangeEventListener mockedUnderTest = new WebhookQGChangeEventListener(webHooks, webhookPayloadFactory, mockedDbClient); + + @Test + @UseDataProvider("allCombinationsOfStatuses") + public void onIssueChanges_has_no_effect_if_no_webhook_is_configured(Metric.Level previousStatus, Metric.Level newStatus) { + Configuration configuration1 = mock(Configuration.class); + when(newQualityGate.getStatus()).thenReturn(newStatus); + QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration1, previousStatus, newQualityGate); + mockWebhookDisabled(qualityGateEvent.getProject()); + + mockedUnderTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); + + verify(webHooks).isEnabled(qualityGateEvent.getProject()); + verifyNoInteractions(webhookPayloadFactory, mockedDbClient); + } + + @DataProvider + public static Object[][] allCombinationsOfStatuses() { + Metric.Level[] levelsAndNull = concat(of((Metric.Level) null), stream(Metric.Level.values())) + .toArray(Metric.Level[]::new); + Object[][] res = new Object[levelsAndNull.length * levelsAndNull.length][2]; + int i = 0; + for (Metric.Level previousStatus : levelsAndNull) { + for (Metric.Level newStatus : levelsAndNull) { + res[i][0] = previousStatus; + res[i][1] = newStatus; + i++; + } + } + return res; + } + + @Test + public void onIssueChanges_has_no_effect_if_event_has_neither_previousQGStatus_nor_qualityGate() { + Configuration configuration = mock(Configuration.class); + QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration, null, null); + mockWebhookEnabled(qualityGateEvent.getProject()); + + underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); + + verifyNoInteractions(webhookPayloadFactory, mockedDbClient); + } + + @Test + public void onIssueChanges_has_no_effect_if_event_has_same_status_in_previous_and_new_QG() { + Configuration configuration = mock(Configuration.class); + Metric.Level previousStatus = randomLevel(); + when(newQualityGate.getStatus()).thenReturn(previousStatus); + QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration, previousStatus, newQualityGate); + mockWebhookEnabled(qualityGateEvent.getProject()); + + underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); + + verifyNoInteractions(webhookPayloadFactory, mockedDbClient); + } + + @Test + @UseDataProvider("newQGorNot") + public void onIssueChanges_calls_webhook_for_changeEvent_with_webhook_enabled(@Nullable EvaluatedQualityGate newQualityGate) { + ProjectAndBranch projectBranch = insertBranch(BRANCH, "foo"); + SnapshotDto analysis = insertAnalysisTask(projectBranch); + Configuration configuration = mock(Configuration.class); + mockPayloadSupplierConsumedByWebhooks(); + Map properties = new HashMap<>(); + properties.put("sonar.analysis.test1", randomAlphanumeric(50)); + properties.put("sonar.analysis.test2", randomAlphanumeric(5000)); + insertPropertiesFor(analysis.getUuid(), properties); + QGChangeEvent qualityGateEvent = newQGChangeEvent(projectBranch, analysis, configuration, newQualityGate); + mockWebhookEnabled(qualityGateEvent.getProject()); + + underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); + + ProjectAnalysis projectAnalysis = verifyWebhookCalledAndExtractPayloadFactoryArgument(projectBranch, analysis, qualityGateEvent.getProject()); + assertThat(projectAnalysis).isEqualTo( + new ProjectAnalysis( + new Project(projectBranch.project.getUuid(), projectBranch.project.getKey(), projectBranch.project.getName()), + null, + new Analysis(analysis.getUuid(), analysis.getCreatedAt(), analysis.getRevision()), + new Branch(false, "foo", Branch.Type.BRANCH), + newQualityGate, + null, + properties)); + } + + @Test + @UseDataProvider("newQGorNot") + public void onIssueChanges_calls_webhook_on_main_branch(@Nullable EvaluatedQualityGate newQualityGate) { + ProjectAndBranch mainBranch = insertMainBranch(); + SnapshotDto analysis = insertAnalysisTask(mainBranch); + Configuration configuration = mock(Configuration.class); + QGChangeEvent qualityGateEvent = newQGChangeEvent(mainBranch, analysis, configuration, newQualityGate); + mockWebhookEnabled(qualityGateEvent.getProject()); + + underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); + + verifyWebhookCalled(mainBranch, analysis, qualityGateEvent.getProject()); + } + + @Test + public void onIssueChanges_calls_webhook_on_branch() { + onIssueChangesCallsWebhookOnBranch(BRANCH); + } + + @Test + public void onIssueChanges_calls_webhook_on_pr() { + onIssueChangesCallsWebhookOnBranch(BranchType.PULL_REQUEST); + } + + public void onIssueChangesCallsWebhookOnBranch(BranchType branchType) { + ProjectAndBranch nonMainBranch = insertBranch(branchType, "foo"); + SnapshotDto analysis = insertAnalysisTask(nonMainBranch); + Configuration configuration = mock(Configuration.class); + QGChangeEvent qualityGateEvent = newQGChangeEvent(nonMainBranch, analysis, configuration, null); + mockWebhookEnabled(qualityGateEvent.getProject()); + + underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); + + verifyWebhookCalled(nonMainBranch, analysis, qualityGateEvent.getProject()); + } + + @DataProvider + public static Object[][] newQGorNot() { + EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class); + return new Object[][] { + {null}, + {newQualityGate} + }; + } + + private void mockWebhookEnabled(ProjectDto... projects) { + for (ProjectDto dto : projects) { + when(webHooks.isEnabled(dto)).thenReturn(true); + } + } + + private void mockWebhookDisabled(ProjectDto... projects) { + for (ProjectDto dto : projects) { + when(webHooks.isEnabled(dto)).thenReturn(false); + } + } + + private void mockPayloadSupplierConsumedByWebhooks() { + Mockito.doAnswer(invocationOnMock -> { + Supplier supplier = (Supplier) invocationOnMock.getArguments()[1]; + supplier.get(); + return null; + }).when(webHooks) + .sendProjectAnalysisUpdate(any(), any()); + } + + private void insertPropertiesFor(String snapshotUuid, Map properties) { + List analysisProperties = properties.entrySet().stream() + .map(entry -> new AnalysisPropertyDto() + .setUuid(UuidFactoryFast.getInstance().create()) + .setAnalysisUuid(snapshotUuid) + .setKey(entry.getKey()) + .setValue(entry.getValue())) + .collect(toArrayList(properties.size())); + dbTester.getDbClient().analysisPropertiesDao().insert(dbTester.getSession(), analysisProperties); + dbTester.getSession().commit(); + } + + private SnapshotDto insertAnalysisTask(ProjectAndBranch projectAndBranch) { + return dbTester.components().insertSnapshot(projectAndBranch.getBranch()); + } + + private ProjectAnalysis verifyWebhookCalledAndExtractPayloadFactoryArgument(ProjectAndBranch projectAndBranch, SnapshotDto analysis, ProjectDto project) { + verifyWebhookCalled(projectAndBranch, analysis, project); + + return extractPayloadFactoryArguments(1).iterator().next(); + } + + private void verifyWebhookCalled(ProjectAndBranch projectAndBranch, SnapshotDto analysis, ProjectDto project) { + verify(webHooks).isEnabled(project); + verify(webHooks).sendProjectAnalysisUpdate( + eq(new WebHooks.Analysis(projectAndBranch.uuid(), analysis.getUuid(), null)), + any()); + } + + private List extractPayloadFactoryArguments(int time) { + ArgumentCaptor projectAnalysisCaptor = ArgumentCaptor.forClass(ProjectAnalysis.class); + verify(webhookPayloadFactory, Mockito.times(time)).create(projectAnalysisCaptor.capture()); + return projectAnalysisCaptor.getAllValues(); + } + + public ProjectAndBranch insertMainBranch() { + ProjectDto project = dbTester.components().insertPrivateProjectDto(); + BranchDto branch = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), project.getUuid()).get(); + dbTester.commit(); + return new ProjectAndBranch(project, branch); + } + + public ProjectAndBranch insertBranch(BranchType type, String branchKey) { + ProjectDto project = dbTester.components().insertPrivateProjectDto(); + BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey(branchKey).setBranchType(type)); + return new ProjectAndBranch(project, branch); + } + + public ProjectAndBranch insertBranch(ProjectDto project, BranchType type, String branchKey) { + BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey(branchKey).setBranchType(type)); + return new ProjectAndBranch(project, branch); + } + + private static class ProjectAndBranch { + private final ProjectDto project; + private final BranchDto branch; + + private ProjectAndBranch(ProjectDto project, BranchDto branch) { + this.project = project; + this.branch = branch; + } + + public ProjectDto getProject() { + return project; + } + + public BranchDto getBranch() { + return branch; + } + + public String uuid() { + return project.getUuid(); + } + + } + + private static QGChangeEvent newQGChangeEvent(Configuration configuration, @Nullable Metric.Level previousQQStatus, @Nullable EvaluatedQualityGate evaluatedQualityGate) { + return new QGChangeEvent(new ProjectDto(), new BranchDto(), new SnapshotDto(), configuration, previousQQStatus, () -> Optional.ofNullable(evaluatedQualityGate)); + } + + private static QGChangeEvent newQGChangeEvent(ProjectAndBranch branch, SnapshotDto analysis, Configuration configuration, @Nullable EvaluatedQualityGate evaluatedQualityGate) { + Metric.Level previousStatus = randomLevel(); + if (evaluatedQualityGate != null) { + Metric.Level otherLevel = stream(Metric.Level.values()) + .filter(s -> s != previousStatus) + .toArray(Metric.Level[]::new)[new Random().nextInt(Metric.Level.values().length - 1)]; + when(evaluatedQualityGate.getStatus()).thenReturn(otherLevel); + } + return new QGChangeEvent(branch.project, branch.branch, analysis, configuration, previousStatus, () -> Optional.ofNullable(evaluatedQualityGate)); + } + + private static Metric.Level randomLevel() { + return Metric.Level.values()[new Random().nextInt(Metric.Level.values().length)]; + } + +} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/PersistentSettingsTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/PersistentSettingsTest.java deleted file mode 100644 index e59874d6fcc..00000000000 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/PersistentSettingsTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform; - -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.config.internal.MapSettings; -import org.sonar.api.config.internal.Settings; -import org.sonar.api.utils.System2; -import org.sonar.db.DbTester; -import org.sonar.server.setting.SettingsChangeNotifier; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -public class PersistentSettingsTest { - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - private Settings delegate = new MapSettings(); - private SettingsChangeNotifier changeNotifier = mock(SettingsChangeNotifier.class); - private PersistentSettings underTest = new PersistentSettings(delegate, dbTester.getDbClient(), changeNotifier); - - @Test - public void insert_property_into_database_and_notify_extensions() { - assertThat(underTest.getString("foo")).isNull(); - - underTest.saveProperty("foo", "bar"); - - assertThat(underTest.getString("foo")).isEqualTo("bar"); - assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty("foo").getValue()).isEqualTo("bar"); - verify(changeNotifier).onGlobalPropertyChange("foo", "bar"); - } - - @Test - public void delete_property_from_database_and_notify_extensions() { - underTest.saveProperty("foo", "bar"); - underTest.saveProperty("foo", null); - - assertThat(underTest.getString("foo")).isNull(); - assertThat(dbTester.getDbClient().propertiesDao().selectGlobalProperty("foo")).isNull(); - verify(changeNotifier).onGlobalPropertyChange("foo", null); - } - - @Test - public void getSettings_returns_delegate() { - assertThat(underTest.getSettings()).isSameAs(delegate); - } -} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java deleted file mode 100644 index 23071070463..00000000000 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/StartupMetadataPersisterTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform; - -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.CoreProperties; -import org.sonar.api.utils.DateUtils; -import org.sonar.api.utils.System2; -import org.sonar.db.DbTester; -import org.sonar.db.property.PropertyDto; - -import static org.assertj.core.api.Assertions.assertThat; - -public class StartupMetadataPersisterTest { - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private StartupMetadata metadata = new StartupMetadata(123_456_789L); - private StartupMetadataPersister underTest = new StartupMetadataPersister(metadata, dbTester.getDbClient()); - - @Test - public void persist_metadata_at_startup() { - underTest.start(); - - assertPersistedProperty(CoreProperties.SERVER_STARTTIME, DateUtils.formatDateTime(metadata.getStartedAt())); - - underTest.stop(); - } - - private void assertPersistedProperty(String propertyKey, String expectedValue) { - PropertyDto prop = dbTester.getDbClient().propertiesDao().selectGlobalProperty(dbTester.getSession(), propertyKey); - assertThat(prop.getValue()).isEqualTo(expectedValue); - } -} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionIT.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionIT.java new file mode 100644 index 00000000000..f5f0e5241d8 --- /dev/null +++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionIT.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.monitoring; + +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.SonarQubeSide; +import org.sonar.api.SonarRuntime; +import org.sonar.api.utils.System2; +import org.sonar.db.DbTester; +import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; +import org.sonar.server.platform.db.migration.version.DatabaseVersion; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.process.systeminfo.SystemInfoUtils.attribute; + +public class DbConnectionSectionIT { + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final DatabaseVersion databaseVersion = mock(DatabaseVersion.class); + private final SonarRuntime runtime = mock(SonarRuntime.class); + private final DbConnectionSection underTest = new DbConnectionSection(databaseVersion, dbTester.getDbClient(), runtime); + + @Test + public void jmx_name_is_not_empty() { + assertThat(underTest.name()).isEqualTo("Database"); + } + + @Test + public void pool_info() { + ProtobufSystemInfo.Section section = underTest.toProtobuf(); + assertThat(attribute(section, "Pool Total Connections").getLongValue()).isNotNegative(); + assertThat(attribute(section, "Pool Active Connections").getLongValue()).isNotNegative(); + assertThat(attribute(section, "Pool Idle Connections").getLongValue()).isNotNegative(); + assertThat(attribute(section, "Pool Max Connections").getLongValue()).isNotNegative(); + assertThat(attribute(section, "Pool Min Idle Connections")).isNotNull(); + assertThat(attribute(section, "Pool Max Lifetime (ms)")).isNotNull(); + } + + @Test + public void section_name_depends_on_runtime_side() { + when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.COMPUTE_ENGINE); + assertThat(underTest.toProtobuf().getName()).isEqualTo("Compute Engine Database Connection"); + + when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.SERVER); + assertThat(underTest.toProtobuf().getName()).isEqualTo("Web Database Connection"); + } +} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java deleted file mode 100644 index acc359b423e..00000000000 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/monitoring/DbConnectionSectionTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform.monitoring; - -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.SonarQubeSide; -import org.sonar.api.SonarRuntime; -import org.sonar.api.utils.System2; -import org.sonar.db.DbTester; -import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo; -import org.sonar.server.platform.db.migration.version.DatabaseVersion; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.process.systeminfo.SystemInfoUtils.attribute; - -public class DbConnectionSectionTest { - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private final DatabaseVersion databaseVersion = mock(DatabaseVersion.class); - private final SonarRuntime runtime = mock(SonarRuntime.class); - private final DbConnectionSection underTest = new DbConnectionSection(databaseVersion, dbTester.getDbClient(), runtime); - - @Test - public void jmx_name_is_not_empty() { - assertThat(underTest.name()).isEqualTo("Database"); - } - - @Test - public void pool_info() { - ProtobufSystemInfo.Section section = underTest.toProtobuf(); - assertThat(attribute(section, "Pool Total Connections").getLongValue()).isNotNegative(); - assertThat(attribute(section, "Pool Active Connections").getLongValue()).isNotNegative(); - assertThat(attribute(section, "Pool Idle Connections").getLongValue()).isNotNegative(); - assertThat(attribute(section, "Pool Max Connections").getLongValue()).isNotNegative(); - assertThat(attribute(section, "Pool Min Idle Connections")).isNotNull(); - assertThat(attribute(section, "Pool Max Lifetime (ms)")).isNotNull(); - } - - @Test - public void section_name_depends_on_runtime_side() { - when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.COMPUTE_ENGINE); - assertThat(underTest.toProtobuf().getName()).isEqualTo("Compute Engine Database Connection"); - - when(runtime.getSonarQubeSide()).thenReturn(SonarQubeSide.SERVER); - assertThat(underTest.toProtobuf().getName()).isEqualTo("Web Database Connection"); - } -} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java deleted file mode 100644 index 616f5131772..00000000000 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/platform/serverid/ServerIdManagerTest.java +++ /dev/null @@ -1,292 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform.serverid; - -import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import org.assertj.core.api.ThrowableAssert.ThrowingCallable; -import org.junit.After; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.sonar.api.CoreProperties; -import org.sonar.api.SonarEdition; -import org.sonar.api.SonarQubeSide; -import org.sonar.api.internal.SonarRuntimeImpl; -import org.sonar.api.utils.System2; -import org.sonar.api.utils.Version; -import org.sonar.core.platform.ServerId; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.property.PropertyDto; -import org.sonar.server.platform.NodeInformation; -import org.sonar.server.property.InternalProperties; - -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.assertj.core.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.sonar.api.SonarQubeSide.COMPUTE_ENGINE; -import static org.sonar.api.SonarQubeSide.SERVER; -import static org.sonar.core.platform.ServerId.DATABASE_ID_LENGTH; -import static org.sonar.core.platform.ServerId.NOT_UUID_DATASET_ID_LENGTH; -import static org.sonar.core.platform.ServerId.UUID_DATASET_ID_LENGTH; - -@RunWith(DataProviderRunner.class) -public class ServerIdManagerTest { - - private static final ServerId WITH_DATABASE_ID_SERVER_ID = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(NOT_UUID_DATASET_ID_LENGTH)); - private static final String CHECKSUM_1 = randomAlphanumeric(12); - - @Rule - public final DbTester dbTester = DbTester.create(System2.INSTANCE); - - private final ServerIdChecksum serverIdChecksum = mock(ServerIdChecksum.class); - private final ServerIdFactory serverIdFactory = mock(ServerIdFactory.class); - private final DbClient dbClient = dbTester.getDbClient(); - private final DbSession dbSession = dbTester.getSession(); - private final NodeInformation nodeInformation = mock(NodeInformation.class); - private ServerIdManager underTest; - - @After - public void tearDown() { - if (underTest != null) { - underTest.stop(); - } - } - - @Test - public void web_leader_persists_new_server_id_if_missing() { - mockCreateNewServerId(); - mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); - when(nodeInformation.isStartupLeader()).thenReturn(true); - - test(SERVER); - - verifyDb(CHECKSUM_1); - verifyCreateNewServerIdFromScratch(); - } - - @Test - public void web_leader_persists_new_server_id_if_value_is_empty() { - insertServerId(""); - mockCreateNewServerId(); - mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); - when(nodeInformation.isStartupLeader()).thenReturn(true); - - test(SERVER); - - verifyDb(CHECKSUM_1); - verifyCreateNewServerIdFromScratch(); - } - - @Test - public void web_leader_keeps_existing_server_id_if_valid() { - insertServerId(WITH_DATABASE_ID_SERVER_ID); - insertChecksum(CHECKSUM_1); - mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); - when(nodeInformation.isStartupLeader()).thenReturn(true); - - test(SERVER); - - verifyDb(CHECKSUM_1); - } - - @Test - public void web_leader_creates_server_id_from_current_serverId_with_databaseId_if_checksum_fails() { - ServerId currentServerId = ServerId.of(randomAlphanumeric(DATABASE_ID_LENGTH), randomAlphanumeric(UUID_DATASET_ID_LENGTH)); - insertServerId(currentServerId); - insertChecksum("does_not_match_WITH_DATABASE_ID_SERVER_ID"); - mockChecksumOf(currentServerId, "matches_WITH_DATABASE_ID_SERVER_ID"); - mockCreateNewServerIdFrom(currentServerId); - mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); - when(nodeInformation.isStartupLeader()).thenReturn(true); - - test(SERVER); - - verifyDb(CHECKSUM_1); - verifyCreateNewServerIdFrom(currentServerId); - } - - @Test - public void web_leader_generates_missing_checksum_for_current_serverId_with_databaseId() { - insertServerId(WITH_DATABASE_ID_SERVER_ID); - mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); - when(nodeInformation.isStartupLeader()).thenReturn(true); - - test(SERVER); - - verifyDb(CHECKSUM_1); - } - - @Test - public void web_follower_does_not_fail_if_server_id_matches_checksum() { - insertServerId(WITH_DATABASE_ID_SERVER_ID); - insertChecksum(CHECKSUM_1); - mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); - when(nodeInformation.isStartupLeader()).thenReturn(false); - - test(SERVER); - - // no changes - verifyDb(CHECKSUM_1); - } - - @Test - public void web_follower_fails_if_server_id_is_missing() { - when(nodeInformation.isStartupLeader()).thenReturn(false); - - expectMissingServerIdException(() -> test(SERVER)); - } - - @Test - public void web_follower_fails_if_server_id_is_empty() { - insertServerId(""); - when(nodeInformation.isStartupLeader()).thenReturn(false); - - expectEmptyServerIdException(() -> test(SERVER)); - } - - @Test - public void web_follower_fails_if_checksum_does_not_match() { - String dbChecksum = "boom"; - insertServerId(WITH_DATABASE_ID_SERVER_ID); - insertChecksum(dbChecksum); - mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); - when(nodeInformation.isStartupLeader()).thenReturn(false); - - try { - test(SERVER); - fail("An ISE should have been raised"); - } catch (IllegalStateException e) { - assertThat(e.getMessage()).isEqualTo("Server ID is invalid"); - // no changes - verifyDb(dbChecksum); - } - } - - @Test - public void compute_engine_does_not_fail_if_server_id_is_valid() { - insertServerId(WITH_DATABASE_ID_SERVER_ID); - insertChecksum(CHECKSUM_1); - mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); - - test(COMPUTE_ENGINE); - - // no changes - verifyDb(CHECKSUM_1); - } - - @Test - public void compute_engine_fails_if_server_id_is_missing() { - expectMissingServerIdException(() -> test(COMPUTE_ENGINE)); - } - - @Test - public void compute_engine_fails_if_server_id_is_empty() { - insertServerId(""); - - expectEmptyServerIdException(() -> test(COMPUTE_ENGINE)); - } - - @Test - public void compute_engine_fails_if_server_id_is_invalid() { - String dbChecksum = "boom"; - insertServerId(WITH_DATABASE_ID_SERVER_ID); - insertChecksum(dbChecksum); - mockChecksumOf(WITH_DATABASE_ID_SERVER_ID, CHECKSUM_1); - - try { - test(SERVER); - fail("An ISE should have been raised"); - } catch (IllegalStateException e) { - assertThat(e.getMessage()).isEqualTo("Server ID is invalid"); - // no changes - verifyDb(dbChecksum); - } - } - - private void expectEmptyServerIdException(ThrowingCallable callback) { - assertThatThrownBy(callback) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Property sonar.core.id is empty in database"); - } - - private void expectMissingServerIdException(ThrowingCallable callback) { - assertThatThrownBy(callback) - .isInstanceOf(IllegalStateException.class) - .hasMessage("Property sonar.core.id is missing in database"); - } - - private void verifyDb(String expectedChecksum) { - assertThat(dbClient.propertiesDao().selectGlobalProperty(dbSession, CoreProperties.SERVER_ID)) - .extracting(PropertyDto::getValue) - .isEqualTo(ServerIdManagerTest.WITH_DATABASE_ID_SERVER_ID.toString()); - assertThat(dbClient.internalPropertiesDao().selectByKey(dbSession, InternalProperties.SERVER_ID_CHECKSUM)) - .hasValue(expectedChecksum); - } - - private void mockCreateNewServerId() { - when(serverIdFactory.create()).thenReturn(WITH_DATABASE_ID_SERVER_ID); - when(serverIdFactory.create(any())).thenThrow(new IllegalStateException("new ServerId should not be created from current server id")); - } - - private void mockCreateNewServerIdFrom(ServerId currentServerId) { - when(serverIdFactory.create()).thenThrow(new IllegalStateException("new ServerId should be created from current server id")); - when(serverIdFactory.create(currentServerId)).thenReturn(ServerIdManagerTest.WITH_DATABASE_ID_SERVER_ID); - } - - private void verifyCreateNewServerIdFromScratch() { - verify(serverIdFactory).create(); - } - - private void verifyCreateNewServerIdFrom(ServerId currentServerId) { - verify(serverIdFactory).create(currentServerId); - } - - private void mockChecksumOf(ServerId serverId, String checksum1) { - when(serverIdChecksum.computeFor(serverId.toString())).thenReturn(checksum1); - } - - private void insertServerId(ServerId serverId) { - insertServerId(serverId.toString()); - } - - private void insertServerId(String serverId) { - dbClient.propertiesDao().saveProperty(dbSession, new PropertyDto().setKey(CoreProperties.SERVER_ID).setValue(serverId), - null, null, null, null); - dbSession.commit(); - } - - private void insertChecksum(String value) { - dbClient.internalPropertiesDao().save(dbSession, InternalProperties.SERVER_ID_CHECKSUM, value); - dbSession.commit(); - } - - private void test(SonarQubeSide side) { - underTest = new ServerIdManager(serverIdChecksum, serverIdFactory, dbClient, SonarRuntimeImpl - .forSonarQube(Version.create(6, 7), side, SonarEdition.COMMUNITY), nodeInformation); - underTest.start(); - } -} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java deleted file mode 100644 index c66e2a6d42a..00000000000 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterMetricsTest.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.startup; - -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Random; -import java.util.stream.IntStream; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.measures.CoreMetrics; -import org.sonar.api.measures.Metric; -import org.sonar.api.measures.Metrics; -import org.sonar.api.utils.System2; -import org.sonar.core.util.SequenceUuidFactory; -import org.sonar.core.util.UuidFactory; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.metric.MetricDto; - -import static java.util.Arrays.asList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; - -public class RegisterMetricsTest { - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private final UuidFactory uuidFactory = new SequenceUuidFactory(); - private final DbClient dbClient = dbTester.getDbClient(); - private final RegisterMetrics register = new RegisterMetrics(dbClient, uuidFactory); - - /** - * Insert new metrics, including custom metrics - */ - @Test - public void insert_new_metrics() { - Metric m1 = new Metric.Builder("m1", "One", Metric.ValueType.FLOAT) - .setDescription("desc1") - .setDirection(1) - .setQualitative(true) - .setDomain("domain1") - .setUserManaged(false) - .create(); - Metric custom = new Metric.Builder("custom", "Custom", Metric.ValueType.FLOAT) - .setDescription("This is a custom metric") - .setUserManaged(true) - .create(); - - register.register(asList(m1, custom)); - - Map metricsByKey = selectAllMetrics(); - assertThat(metricsByKey).hasSize(2); - assertEquals(m1, metricsByKey.get("m1")); - assertEquals(custom, metricsByKey.get("custom")); - } - - /** - * Update existing metrics - */ - @Test - public void update_metrics() { - dbTester.measures().insertMetric(t -> t.setKey("m1") - .setShortName("name") - .setValueType(Metric.ValueType.INT.name()) - .setDescription("old desc") - .setDomain("old domain") - .setShortName("old short name") - .setQualitative(false) - .setEnabled(true) - .setOptimizedBestValue(false) - .setDirection(1) - .setHidden(false)); - - Metric m1 = new Metric.Builder("m1", "New name", Metric.ValueType.FLOAT) - .setDescription("new description") - .setDirection(-1) - .setQualitative(true) - .setDomain("new domain") - .setUserManaged(false) - .setDecimalScale(3) - .setHidden(true) - .create(); - register.register(asList(m1)); - - Map metricsByKey = selectAllMetrics(); - assertThat(metricsByKey).hasSize(1); - assertEquals(m1, metricsByKey.get("m1")); - } - - @Test - public void disable_undefined_metrics() { - Random random = new Random(); - int count = 1 + random.nextInt(10); - IntStream.range(0, count) - .forEach(t -> dbTester.measures().insertMetric(m -> m.setEnabled(random.nextBoolean()))); - - register.register(Collections.emptyList()); - - assertThat(selectAllMetrics().values().stream()) - .extracting(MetricDto::isEnabled) - .containsOnly(IntStream.range(0, count).mapToObj(t -> false).toArray(Boolean[]::new)); - } - - @Test - public void enable_disabled_metrics() { - MetricDto enabledMetric = dbTester.measures().insertMetric(t -> t.setEnabled(true)); - MetricDto disabledMetric = dbTester.measures().insertMetric(t -> t.setEnabled(false)); - - register.register(asList(builderOf(enabledMetric).create(), builderOf(disabledMetric).create())); - - assertThat(selectAllMetrics().values()) - .extracting(MetricDto::isEnabled) - .containsOnly(true, true); - } - - @Test - public void insert_core_metrics() { - register.start(); - - assertThat(dbTester.countRowsOfTable("metrics")).isEqualTo(CoreMetrics.getMetrics().size()); - } - - @Test - public void fail_if_duplicated_plugin_metrics() { - Metrics plugin1 = new TestMetrics(new Metric.Builder("m1", "In first plugin", Metric.ValueType.FLOAT).create()); - Metrics plugin2 = new TestMetrics(new Metric.Builder("m1", "In second plugin", Metric.ValueType.FLOAT).create()); - - assertThatThrownBy(() -> new RegisterMetrics(dbClient, uuidFactory, new Metrics[] {plugin1, plugin2}).start()) - .isInstanceOf(IllegalStateException.class); - } - - @Test - public void fail_if_plugin_duplicates_core_metric() { - Metrics plugin = new TestMetrics(new Metric.Builder("ncloc", "In plugin", Metric.ValueType.FLOAT).create()); - - assertThatThrownBy(() -> new RegisterMetrics(dbClient, uuidFactory, new Metrics[] {plugin}).start()) - .isInstanceOf(IllegalStateException.class); - } - - private static class TestMetrics implements Metrics { - private final List metrics; - - public TestMetrics(Metric... metrics) { - this.metrics = asList(metrics); - } - - @Override - public List getMetrics() { - return metrics; - } - } - - private Map selectAllMetrics() { - return dbTester.getDbClient().metricDao().selectAll(dbTester.getSession()) - .stream() - .collect(uniqueIndex(MetricDto::getKey)); - } - - private void assertEquals(Metric expected, MetricDto actual) { - assertThat(actual.getKey()).isEqualTo(expected.getKey()); - assertThat(actual.getShortName()).isEqualTo(expected.getName()); - assertThat(actual.getValueType()).isEqualTo(expected.getType().name()); - assertThat(actual.getDescription()).isEqualTo(expected.getDescription()); - assertThat(actual.getDirection()).isEqualTo(expected.getDirection()); - assertThat(actual.isQualitative()).isEqualTo(expected.getQualitative()); - } - - private static Metric.Builder builderOf(MetricDto enabledMetric) { - return new Metric.Builder(enabledMetric.getKey(), enabledMetric.getShortName(), Metric.ValueType.valueOf(enabledMetric.getValueType())) - .setDescription(enabledMetric.getDescription()) - .setDirection(enabledMetric.getDirection()) - .setQualitative(enabledMetric.isQualitative()) - .setQualitative(enabledMetric.isQualitative()) - .setDomain(enabledMetric.getDomain()) - .setHidden(enabledMetric.isHidden()); - } -} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java deleted file mode 100644 index c47aaefc8c5..00000000000 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.startup; - -import java.io.File; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.Map; -import javax.annotation.Nullable; -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.sonar.api.utils.System2; -import org.sonar.core.platform.PluginInfo; -import org.sonar.core.util.UuidFactory; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.plugin.PluginDto; -import org.sonar.db.plugin.PluginDto.Type; -import org.sonar.server.plugins.PluginFilesAndMd5; -import org.sonar.core.plugin.PluginType; -import org.sonar.server.plugins.ServerPlugin; -import org.sonar.server.plugins.ServerPluginRepository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; - -public class RegisterPluginsTest { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private final long now = 12345L; - private final DbClient dbClient = dbTester.getDbClient(); - private final ServerPluginRepository serverPluginRepository = new ServerPluginRepository(); - private final UuidFactory uuidFactory = mock(UuidFactory.class); - private final System2 system2 = mock(System2.class); - private final RegisterPlugins register = new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2); - - - @Before - public void setUp() { - when(system2.now()).thenReturn(now); - } - - /** - * Insert new plugins - */ - @Test - public void insert_new_plugins() throws IOException { - addPlugin("java", null); - addPlugin("javacustom", "java"); - when(uuidFactory.create()).thenReturn("a").thenReturn("b").thenThrow(new IllegalStateException("Should be called only twice")); - register.start(); - - Map pluginsByKey = selectAllPlugins(); - assertThat(pluginsByKey).hasSize(2); - verify(pluginsByKey.get("java"), Type.BUNDLED, null, "93f725a07423fe1c889f448b33d21f46", false, now, now); - verify(pluginsByKey.get("javacustom"), Type.BUNDLED, "java", "bf8b3d4cb91efc5f9ef0bcd02f128ebf", false, now, now); - - register.stop(); - } - - @Test - public void update_removed_plugins() throws IOException { - // will be flagged as removed - dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() - .setUuid("a") - .setKee("java") - .setBasePluginKey(null) - .setFileHash("bd451e47a1aa76e73da0359cef63dd63") - .setType(Type.BUNDLED) - .setCreatedAt(1L) - .setUpdatedAt(1L)); - // already flagged as removed - dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() - .setUuid("b") - .setKee("java2") - .setBasePluginKey(null) - .setFileHash("bd451e47a1aa76e73da0359cef63dd63") - .setType(Type.BUNDLED) - .setRemoved(true) - .setCreatedAt(1L) - .setUpdatedAt(1L)); - dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() - .setUuid("c") - .setKee("csharp") - .setBasePluginKey(null) - .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78") - .setType(Type.EXTERNAL) - .setCreatedAt(1L) - .setUpdatedAt(1L)); - dbTester.commit(); - - addPlugin("csharp", PluginType.EXTERNAL, null); - register.start(); - - Map pluginsByKey = selectAllPlugins(); - assertThat(pluginsByKey).hasSize(3); - verify(pluginsByKey.get("java"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, now); - verify(pluginsByKey.get("java2"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, 1L); - verify(pluginsByKey.get("csharp"), Type.EXTERNAL, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, 1L); - } - - @Test - public void re_add_previously_removed_plugin() throws IOException { - dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() - .setUuid("c") - .setKee("csharp") - .setBasePluginKey(null) - .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78") - .setType(Type.EXTERNAL) - .setRemoved(true) - .setCreatedAt(1L) - .setUpdatedAt(1L)); - dbTester.commit(); - - addPlugin("csharp", PluginType.EXTERNAL, null); - register.start(); - - Map pluginsByKey = selectAllPlugins(); - assertThat(pluginsByKey).hasSize(1); - verify(pluginsByKey.get("csharp"), Type.EXTERNAL, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, now); - } - - /** - * Update existing plugins, only when checksum is different and don't remove uninstalled plugins - */ - @Test - public void update_changed_plugins() throws IOException { - dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() - .setUuid("a") - .setKee("java") - .setBasePluginKey(null) - .setFileHash("bd451e47a1aa76e73da0359cef63dd63") - .setType(Type.BUNDLED) - .setCreatedAt(1L) - .setUpdatedAt(1L)); - dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() - .setUuid("b") - .setKee("javacustom") - .setBasePluginKey("java") - .setFileHash("de9b2de3ddc0680904939686c0dba5be") - .setType(Type.BUNDLED) - .setCreatedAt(1L) - .setUpdatedAt(1L)); - dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() - .setUuid("c") - .setKee("csharp") - .setBasePluginKey(null) - .setFileHash("a20d785dbacb8f41a3b7392aa7d03b78") - .setType(Type.EXTERNAL) - .setCreatedAt(1L) - .setUpdatedAt(1L)); - dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto() - .setUuid("d") - .setKee("new-measures") - .setBasePluginKey(null) - .setFileHash("6d24712cf701c41ce5eaa948e0bd6d22") - .setType(Type.EXTERNAL) - .setCreatedAt(1L) - .setUpdatedAt(1L)); - - dbTester.commit(); - - addPlugin("javacustom", PluginType.BUNDLED, "java2"); - // csharp plugin type changed - addPlugin("csharp", PluginType.BUNDLED, null); - - register.start(); - - Map pluginsByKey = selectAllPlugins(); - assertThat(pluginsByKey).hasSize(4); - verify(pluginsByKey.get("java"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", true, 1L, now); - verify(pluginsByKey.get("javacustom"), Type.BUNDLED, "java2", "bf8b3d4cb91efc5f9ef0bcd02f128ebf", false, 1L, now); - verify(pluginsByKey.get("csharp"), Type.BUNDLED, null, "a20d785dbacb8f41a3b7392aa7d03b78", false, 1L, now); - verify(pluginsByKey.get("new-measures"), Type.EXTERNAL, null, "6d24712cf701c41ce5eaa948e0bd6d22", true, 1L, now); - } - - private ServerPlugin addPlugin(String key, @Nullable String basePlugin) throws IOException { - return addPlugin(key, PluginType.BUNDLED, basePlugin); - } - - private ServerPlugin addPlugin(String key, PluginType type, @Nullable String basePlugin) throws IOException { - File file = createPluginFile(key); - PluginFilesAndMd5.FileAndMd5 jar = new PluginFilesAndMd5.FileAndMd5(file); - PluginInfo info = new PluginInfo(key) - .setBasePlugin(basePlugin) - .setJarFile(file); - ServerPlugin serverPlugin = new ServerPlugin(info, type, null, jar, null); - serverPluginRepository.addPlugin(serverPlugin); - return serverPlugin; - } - - private File createPluginFile(String key) throws IOException { - File pluginJar = temp.newFile(); - FileUtils.write(pluginJar, key, StandardCharsets.UTF_8); - return pluginJar; - } - - private Map selectAllPlugins() { - return dbTester.getDbClient().pluginDao().selectAll(dbTester.getSession()).stream() - .collect(uniqueIndex(PluginDto::getKee)); - } - - private void verify(PluginDto pluginDto, Type type, @Nullable String basePluginKey, String fileHash, boolean removed, @Nullable Long createdAt, long updatedAt) { - assertThat(pluginDto.getBasePluginKey()).isEqualTo(basePluginKey); - assertThat(pluginDto.getType()).isEqualTo(type); - assertThat(pluginDto.getFileHash()).isEqualTo(fileHash); - assertThat(pluginDto.isRemoved()).isEqualTo(removed); - assertThat(pluginDto.getCreatedAt()).isEqualTo(createdAt); - assertThat(pluginDto.getUpdatedAt()).isEqualTo(updatedAt); - } - -} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/UpgradeSuggestionsCleanerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/UpgradeSuggestionsCleanerTest.java deleted file mode 100644 index f82e082ef2f..00000000000 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/UpgradeSuggestionsCleanerTest.java +++ /dev/null @@ -1,148 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.startup; - -import com.tngtech.java.junit.dataprovider.DataProvider; -import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import com.tngtech.java.junit.dataprovider.UseDataProvider; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.sonar.api.SonarEdition; -import org.sonar.api.SonarRuntime; -import org.sonar.api.utils.System2; -import org.sonar.db.DbTester; -import org.sonar.db.ce.CeTaskMessageDto; -import org.sonar.db.ce.CeTaskMessageType; -import org.sonar.db.user.UserDismissedMessageDto; -import org.sonar.db.user.UserDto; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -@RunWith(DataProviderRunner.class) -public class UpgradeSuggestionsCleanerTest { - private static final String TASK_UUID = "b8d564dd-4ceb-4dba-8a3d-5fafa2d72cdf"; - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private final SonarRuntime sonarRuntime = mock(SonarRuntime.class); - private final UpgradeSuggestionsCleaner underTest = new UpgradeSuggestionsCleaner(dbTester.getDbClient(), sonarRuntime); - - private UserDto user; - - @Before - public void setup() { - user = dbTester.users().insertUser(); - } - - @DataProvider - public static Object[][] editionsWithCleanup() { - return new Object[][] { - {SonarEdition.DEVELOPER}, - {SonarEdition.ENTERPRISE}, - {SonarEdition.DATACENTER} - }; - } - - @DataProvider - public static Object[][] allEditions() { - return new Object[][] { - {SonarEdition.COMMUNITY}, - {SonarEdition.DEVELOPER}, - {SonarEdition.ENTERPRISE}, - {SonarEdition.DATACENTER} - }; - } - - @Test - @UseDataProvider("editionsWithCleanup") - public void start_cleans_up_obsolete_upgrade_suggestions(SonarEdition edition) { - when(sonarRuntime.getEdition()).thenReturn(edition); - insertCeTaskMessage("ctm1", CeTaskMessageType.GENERIC, "msg1"); - insertCeTaskMessage("ctm2", CeTaskMessageType.GENERIC, "msg2"); - insertCeTaskMessage("ctm3", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE, "upgrade-msg-1"); - insertInUserDismissedMessages("u1", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); - insertInUserDismissedMessages("u2", CeTaskMessageType.GENERIC); - - underTest.start(); - underTest.stop(); - - assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)) - .extracting(CeTaskMessageDto::getUuid) - .containsExactly("ctm1", "ctm2"); - assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)) - .extracting(UserDismissedMessageDto::getUuid) - .containsExactly("u2"); - } - - @Test - public void start_does_nothing_in_community_edition() { - when(sonarRuntime.getEdition()).thenReturn(SonarEdition.COMMUNITY); - insertCeTaskMessage("ctm1", CeTaskMessageType.GENERIC, "msg1"); - insertCeTaskMessage("ctm2", CeTaskMessageType.GENERIC, "msg2"); - insertCeTaskMessage("ctm3", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE, "upgrade-msg-1"); - insertInUserDismissedMessages("u1", CeTaskMessageType.SUGGEST_DEVELOPER_EDITION_UPGRADE); - insertInUserDismissedMessages("u2", CeTaskMessageType.GENERIC); - - underTest.start(); - - assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)) - .extracting(CeTaskMessageDto::getUuid) - .containsExactly("ctm1", "ctm2", "ctm3"); - assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)) - .extracting(UserDismissedMessageDto::getUuid) - .containsExactlyInAnyOrder("u1", "u2"); - } - - @Test - @UseDataProvider("allEditions") - public void start_does_nothing_when_no_suggest_upgrade_messages(SonarEdition edition) { - when(sonarRuntime.getEdition()).thenReturn(edition); - - underTest.start(); - - assertThat(dbTester.getDbClient().ceTaskMessageDao().selectByTask(dbTester.getSession(), TASK_UUID)).isEmpty(); - assertThat(dbTester.getDbClient().userDismissedMessagesDao().selectByUser(dbTester.getSession(), user)).isEmpty(); - } - - private void insertCeTaskMessage(String uuid, CeTaskMessageType messageType, String msg) { - CeTaskMessageDto dto = new CeTaskMessageDto() - .setUuid(uuid) - .setMessage(msg) - .setType(messageType) - .setTaskUuid(TASK_UUID); - dbTester.getDbClient().ceTaskMessageDao().insert(dbTester.getSession(), dto); - dbTester.getSession().commit(); - } - - private void insertInUserDismissedMessages(String uuid, CeTaskMessageType messageType) { - UserDismissedMessageDto dto = new UserDismissedMessageDto() - .setUuid(uuid) - .setUserUuid(user.getUuid()) - .setProjectUuid("PROJECT_1") - .setCeMessageType(messageType); - dbTester.getDbClient().userDismissedMessagesDao().insert(dbTester.getSession(), dto); - dbTester.getSession().commit(); - } -} diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java deleted file mode 100644 index ba51b1bb401..00000000000 --- a/server/sonar-webserver-core/src/test/java/org/sonar/server/webhook/WebhookQGChangeEventListenerTest.java +++ /dev/null @@ -1,329 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.webhook; - -import com.tngtech.java.junit.dataprovider.DataProvider; -import com.tngtech.java.junit.dataprovider.DataProviderRunner; -import com.tngtech.java.junit.dataprovider.UseDataProvider; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Random; -import java.util.Set; -import java.util.function.Supplier; -import javax.annotation.Nullable; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Mockito; -import org.sonar.api.config.Configuration; -import org.sonar.api.measures.Metric; -import org.sonar.api.utils.System2; -import org.sonar.core.util.UuidFactoryFast; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.component.AnalysisPropertyDto; -import org.sonar.db.component.BranchDto; -import org.sonar.db.component.BranchType; -import org.sonar.db.component.SnapshotDto; -import org.sonar.db.project.ProjectDto; -import org.sonar.server.qualitygate.EvaluatedQualityGate; -import org.sonar.server.qualitygate.changeevent.QGChangeEvent; -import org.sonar.server.qualitygate.changeevent.QGChangeEventListener; - -import static java.util.Arrays.stream; -import static java.util.Collections.emptySet; -import static java.util.stream.Stream.concat; -import static java.util.stream.Stream.of; -import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; -import static org.sonar.core.util.stream.MoreCollectors.toArrayList; -import static org.sonar.db.component.BranchType.BRANCH; - -@RunWith(DataProviderRunner.class) -public class WebhookQGChangeEventListenerTest { - - private static final Set CHANGED_ISSUES_ARE_IGNORED = emptySet(); - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private DbClient dbClient = dbTester.getDbClient(); - - private EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class); - private WebHooks webHooks = mock(WebHooks.class); - private WebhookPayloadFactory webhookPayloadFactory = mock(WebhookPayloadFactory.class); - private DbClient spiedOnDbClient = Mockito.spy(dbClient); - private WebhookQGChangeEventListener underTest = new WebhookQGChangeEventListener(webHooks, webhookPayloadFactory, spiedOnDbClient); - private DbClient mockedDbClient = mock(DbClient.class); - private WebhookQGChangeEventListener mockedUnderTest = new WebhookQGChangeEventListener(webHooks, webhookPayloadFactory, mockedDbClient); - - @Test - @UseDataProvider("allCombinationsOfStatuses") - public void onIssueChanges_has_no_effect_if_no_webhook_is_configured(Metric.Level previousStatus, Metric.Level newStatus) { - Configuration configuration1 = mock(Configuration.class); - when(newQualityGate.getStatus()).thenReturn(newStatus); - QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration1, previousStatus, newQualityGate); - mockWebhookDisabled(qualityGateEvent.getProject()); - - mockedUnderTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); - - verify(webHooks).isEnabled(qualityGateEvent.getProject()); - verifyNoInteractions(webhookPayloadFactory, mockedDbClient); - } - - @DataProvider - public static Object[][] allCombinationsOfStatuses() { - Metric.Level[] levelsAndNull = concat(of((Metric.Level) null), stream(Metric.Level.values())) - .toArray(Metric.Level[]::new); - Object[][] res = new Object[levelsAndNull.length * levelsAndNull.length][2]; - int i = 0; - for (Metric.Level previousStatus : levelsAndNull) { - for (Metric.Level newStatus : levelsAndNull) { - res[i][0] = previousStatus; - res[i][1] = newStatus; - i++; - } - } - return res; - } - - @Test - public void onIssueChanges_has_no_effect_if_event_has_neither_previousQGStatus_nor_qualityGate() { - Configuration configuration = mock(Configuration.class); - QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration, null, null); - mockWebhookEnabled(qualityGateEvent.getProject()); - - underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); - - verifyNoInteractions(webhookPayloadFactory, mockedDbClient); - } - - @Test - public void onIssueChanges_has_no_effect_if_event_has_same_status_in_previous_and_new_QG() { - Configuration configuration = mock(Configuration.class); - Metric.Level previousStatus = randomLevel(); - when(newQualityGate.getStatus()).thenReturn(previousStatus); - QGChangeEvent qualityGateEvent = newQGChangeEvent(configuration, previousStatus, newQualityGate); - mockWebhookEnabled(qualityGateEvent.getProject()); - - underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); - - verifyNoInteractions(webhookPayloadFactory, mockedDbClient); - } - - @Test - @UseDataProvider("newQGorNot") - public void onIssueChanges_calls_webhook_for_changeEvent_with_webhook_enabled(@Nullable EvaluatedQualityGate newQualityGate) { - ProjectAndBranch projectBranch = insertBranch(BRANCH, "foo"); - SnapshotDto analysis = insertAnalysisTask(projectBranch); - Configuration configuration = mock(Configuration.class); - mockPayloadSupplierConsumedByWebhooks(); - Map properties = new HashMap<>(); - properties.put("sonar.analysis.test1", randomAlphanumeric(50)); - properties.put("sonar.analysis.test2", randomAlphanumeric(5000)); - insertPropertiesFor(analysis.getUuid(), properties); - QGChangeEvent qualityGateEvent = newQGChangeEvent(projectBranch, analysis, configuration, newQualityGate); - mockWebhookEnabled(qualityGateEvent.getProject()); - - underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); - - ProjectAnalysis projectAnalysis = verifyWebhookCalledAndExtractPayloadFactoryArgument(projectBranch, analysis, qualityGateEvent.getProject()); - assertThat(projectAnalysis).isEqualTo( - new ProjectAnalysis( - new Project(projectBranch.project.getUuid(), projectBranch.project.getKey(), projectBranch.project.getName()), - null, - new Analysis(analysis.getUuid(), analysis.getCreatedAt(), analysis.getRevision()), - new Branch(false, "foo", Branch.Type.BRANCH), - newQualityGate, - null, - properties)); - } - - @Test - @UseDataProvider("newQGorNot") - public void onIssueChanges_calls_webhook_on_main_branch(@Nullable EvaluatedQualityGate newQualityGate) { - ProjectAndBranch mainBranch = insertMainBranch(); - SnapshotDto analysis = insertAnalysisTask(mainBranch); - Configuration configuration = mock(Configuration.class); - QGChangeEvent qualityGateEvent = newQGChangeEvent(mainBranch, analysis, configuration, newQualityGate); - mockWebhookEnabled(qualityGateEvent.getProject()); - - underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); - - verifyWebhookCalled(mainBranch, analysis, qualityGateEvent.getProject()); - } - - @Test - public void onIssueChanges_calls_webhook_on_branch() { - onIssueChangesCallsWebhookOnBranch(BRANCH); - } - - @Test - public void onIssueChanges_calls_webhook_on_pr() { - onIssueChangesCallsWebhookOnBranch(BranchType.PULL_REQUEST); - } - - public void onIssueChangesCallsWebhookOnBranch(BranchType branchType) { - ProjectAndBranch nonMainBranch = insertBranch(branchType, "foo"); - SnapshotDto analysis = insertAnalysisTask(nonMainBranch); - Configuration configuration = mock(Configuration.class); - QGChangeEvent qualityGateEvent = newQGChangeEvent(nonMainBranch, analysis, configuration, null); - mockWebhookEnabled(qualityGateEvent.getProject()); - - underTest.onIssueChanges(qualityGateEvent, CHANGED_ISSUES_ARE_IGNORED); - - verifyWebhookCalled(nonMainBranch, analysis, qualityGateEvent.getProject()); - } - - @DataProvider - public static Object[][] newQGorNot() { - EvaluatedQualityGate newQualityGate = mock(EvaluatedQualityGate.class); - return new Object[][] { - {null}, - {newQualityGate} - }; - } - - private void mockWebhookEnabled(ProjectDto... projects) { - for (ProjectDto dto : projects) { - when(webHooks.isEnabled(dto)).thenReturn(true); - } - } - - private void mockWebhookDisabled(ProjectDto... projects) { - for (ProjectDto dto : projects) { - when(webHooks.isEnabled(dto)).thenReturn(false); - } - } - - private void mockPayloadSupplierConsumedByWebhooks() { - Mockito.doAnswer(invocationOnMock -> { - Supplier supplier = (Supplier) invocationOnMock.getArguments()[1]; - supplier.get(); - return null; - }).when(webHooks) - .sendProjectAnalysisUpdate(any(), any()); - } - - private void insertPropertiesFor(String snapshotUuid, Map properties) { - List analysisProperties = properties.entrySet().stream() - .map(entry -> new AnalysisPropertyDto() - .setUuid(UuidFactoryFast.getInstance().create()) - .setAnalysisUuid(snapshotUuid) - .setKey(entry.getKey()) - .setValue(entry.getValue())) - .collect(toArrayList(properties.size())); - dbTester.getDbClient().analysisPropertiesDao().insert(dbTester.getSession(), analysisProperties); - dbTester.getSession().commit(); - } - - private SnapshotDto insertAnalysisTask(ProjectAndBranch projectAndBranch) { - return dbTester.components().insertSnapshot(projectAndBranch.getBranch()); - } - - private ProjectAnalysis verifyWebhookCalledAndExtractPayloadFactoryArgument(ProjectAndBranch projectAndBranch, SnapshotDto analysis, ProjectDto project) { - verifyWebhookCalled(projectAndBranch, analysis, project); - - return extractPayloadFactoryArguments(1).iterator().next(); - } - - private void verifyWebhookCalled(ProjectAndBranch projectAndBranch, SnapshotDto analysis, ProjectDto project) { - verify(webHooks).isEnabled(project); - verify(webHooks).sendProjectAnalysisUpdate( - eq(new WebHooks.Analysis(projectAndBranch.uuid(), analysis.getUuid(), null)), - any()); - } - - private List extractPayloadFactoryArguments(int time) { - ArgumentCaptor projectAnalysisCaptor = ArgumentCaptor.forClass(ProjectAnalysis.class); - verify(webhookPayloadFactory, Mockito.times(time)).create(projectAnalysisCaptor.capture()); - return projectAnalysisCaptor.getAllValues(); - } - - public ProjectAndBranch insertMainBranch() { - ProjectDto project = dbTester.components().insertPrivateProjectDto(); - BranchDto branch = dbTester.getDbClient().branchDao().selectByUuid(dbTester.getSession(), project.getUuid()).get(); - dbTester.commit(); - return new ProjectAndBranch(project, branch); - } - - public ProjectAndBranch insertBranch(BranchType type, String branchKey) { - ProjectDto project = dbTester.components().insertPrivateProjectDto(); - BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey(branchKey).setBranchType(type)); - return new ProjectAndBranch(project, branch); - } - - public ProjectAndBranch insertBranch(ProjectDto project, BranchType type, String branchKey) { - BranchDto branch = dbTester.components().insertProjectBranch(project, b -> b.setKey(branchKey).setBranchType(type)); - return new ProjectAndBranch(project, branch); - } - - private static class ProjectAndBranch { - private final ProjectDto project; - private final BranchDto branch; - - private ProjectAndBranch(ProjectDto project, BranchDto branch) { - this.project = project; - this.branch = branch; - } - - public ProjectDto getProject() { - return project; - } - - public BranchDto getBranch() { - return branch; - } - - public String uuid() { - return project.getUuid(); - } - - } - - private static QGChangeEvent newQGChangeEvent(Configuration configuration, @Nullable Metric.Level previousQQStatus, @Nullable EvaluatedQualityGate evaluatedQualityGate) { - return new QGChangeEvent(new ProjectDto(), new BranchDto(), new SnapshotDto(), configuration, previousQQStatus, () -> Optional.ofNullable(evaluatedQualityGate)); - } - - private static QGChangeEvent newQGChangeEvent(ProjectAndBranch branch, SnapshotDto analysis, Configuration configuration, @Nullable EvaluatedQualityGate evaluatedQualityGate) { - Metric.Level previousStatus = randomLevel(); - if (evaluatedQualityGate != null) { - Metric.Level otherLevel = stream(Metric.Level.values()) - .filter(s -> s != previousStatus) - .toArray(Metric.Level[]::new)[new Random().nextInt(Metric.Level.values().length - 1)]; - when(evaluatedQualityGate.getStatus()).thenReturn(otherLevel); - } - return new QGChangeEvent(branch.project, branch.branch, analysis, configuration, previousStatus, () -> Optional.ofNullable(evaluatedQualityGate)); - } - - private static Metric.Level randomLevel() { - return Metric.Level.values()[new Random().nextInt(Metric.Level.values().length)]; - } - -} diff --git a/server/sonar-webserver-es/src/it/java/org/sonar/server/permission/index/PermissionIndexerDaoIT.java b/server/sonar-webserver-es/src/it/java/org/sonar/server/permission/index/PermissionIndexerDaoIT.java new file mode 100644 index 00000000000..021856dbc80 --- /dev/null +++ b/server/sonar-webserver-es/src/it/java/org/sonar/server/permission/index/PermissionIndexerDaoIT.java @@ -0,0 +1,271 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.permission.index; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import org.assertj.core.api.Assertions; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.utils.System2; +import org.sonar.core.util.Uuids; +import org.sonar.core.util.stream.MoreCollectors; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.DbTester; +import org.sonar.db.component.ComponentDbTester; +import org.sonar.db.component.ComponentDto; +import org.sonar.db.permission.GroupPermissionDto; +import org.sonar.db.user.GroupDto; +import org.sonar.db.user.UserDbTester; +import org.sonar.db.user.UserDto; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.sonar.api.resources.Qualifiers.APP; +import static org.sonar.api.resources.Qualifiers.PROJECT; +import static org.sonar.api.resources.Qualifiers.VIEW; +import static org.sonar.api.web.UserRole.ADMIN; +import static org.sonar.api.web.UserRole.USER; + +public class PermissionIndexerDaoIT { + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private final DbClient dbClient = dbTester.getDbClient(); + private final DbSession dbSession = dbTester.getSession(); + private final ComponentDbTester componentDbTester = new ComponentDbTester(dbTester); + private final UserDbTester userDbTester = new UserDbTester(dbTester); + + private ComponentDto publicProject; + private ComponentDto privateProject1; + private ComponentDto privateProject2; + private ComponentDto view1; + private ComponentDto view2; + private ComponentDto application; + private UserDto user1; + private UserDto user2; + private GroupDto group; + + private final PermissionIndexerDao underTest = new PermissionIndexerDao(); + + @Before + public void setUp() { + publicProject = componentDbTester.insertPublicProject(); + privateProject1 = componentDbTester.insertPrivateProject(); + privateProject2 = componentDbTester.insertPrivateProject(); + view1 = componentDbTester.insertPublicPortfolio(); + view2 = componentDbTester.insertPublicPortfolio(); + application = componentDbTester.insertPublicApplication(); + user1 = userDbTester.insertUser(); + user2 = userDbTester.insertUser(); + group = userDbTester.insertGroup(); + } + + @Test + public void select_all() { + insertTestDataForProjectsAndViews(); + + Collection dtos = underTest.selectAll(dbClient, dbSession); + Assertions.assertThat(dtos).hasSize(6); + + IndexPermissions publicProjectAuthorization = getByProjectUuid(publicProject.uuid(), dtos); + isPublic(publicProjectAuthorization, PROJECT); + + IndexPermissions view1Authorization = getByProjectUuid(view1.uuid(), dtos); + isPublic(view1Authorization, VIEW); + + IndexPermissions applicationAuthorization = getByProjectUuid(application.uuid(), dtos); + isPublic(applicationAuthorization, APP); + + IndexPermissions privateProject1Authorization = getByProjectUuid(privateProject1.uuid(), dtos); + assertThat(privateProject1Authorization.getGroupUuids()).containsOnly(group.getUuid()); + assertThat(privateProject1Authorization.isAllowAnyone()).isFalse(); + assertThat(privateProject1Authorization.getUserUuids()).containsOnly(user1.getUuid(), user2.getUuid()); + assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT); + + IndexPermissions privateProject2Authorization = getByProjectUuid(privateProject2.uuid(), dtos); + assertThat(privateProject2Authorization.getGroupUuids()).isEmpty(); + assertThat(privateProject2Authorization.isAllowAnyone()).isFalse(); + assertThat(privateProject2Authorization.getUserUuids()).containsOnly(user1.getUuid()); + assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT); + + IndexPermissions view2Authorization = getByProjectUuid(view2.uuid(), dtos); + isPublic(view2Authorization, VIEW); + } + + @Test + public void selectByUuids() { + insertTestDataForProjectsAndViews(); + + Map dtos = underTest + .selectByUuids(dbClient, dbSession, asList(publicProject.uuid(), privateProject1.uuid(), privateProject2.uuid(), view1.uuid(), view2.uuid(), application.uuid())) + .stream() + .collect(MoreCollectors.uniqueIndex(IndexPermissions::getProjectUuid, Function.identity())); + Assertions.assertThat(dtos).hasSize(6); + + IndexPermissions publicProjectAuthorization = dtos.get(publicProject.uuid()); + isPublic(publicProjectAuthorization, PROJECT); + + IndexPermissions view1Authorization = dtos.get(view1.uuid()); + isPublic(view1Authorization, VIEW); + + IndexPermissions applicationAuthorization = dtos.get(application.uuid()); + isPublic(applicationAuthorization, APP); + + IndexPermissions privateProject1Authorization = dtos.get(privateProject1.uuid()); + assertThat(privateProject1Authorization.getGroupUuids()).containsOnly(group.getUuid()); + assertThat(privateProject1Authorization.isAllowAnyone()).isFalse(); + assertThat(privateProject1Authorization.getUserUuids()).containsOnly(user1.getUuid(), user2.getUuid()); + assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT); + + IndexPermissions privateProject2Authorization = dtos.get(privateProject2.uuid()); + assertThat(privateProject2Authorization.getGroupUuids()).isEmpty(); + assertThat(privateProject2Authorization.isAllowAnyone()).isFalse(); + assertThat(privateProject2Authorization.getUserUuids()).containsOnly(user1.getUuid()); + assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT); + + IndexPermissions view2Authorization = dtos.get(view2.uuid()); + isPublic(view2Authorization, VIEW); + } + + @Test + public void selectByUuids_returns_empty_list_when_project_does_not_exist() { + insertTestDataForProjectsAndViews(); + + List dtos = underTest.selectByUuids(dbClient, dbSession, singletonList("missing")); + Assertions.assertThat(dtos).isEmpty(); + } + + @Test + public void select_by_projects_with_high_number_of_projects() { + List projectUuids = new ArrayList<>(); + for (int i = 0; i < 3500; i++) { + ComponentDto project = dbTester.components().insertPrivateProject(Integer.toString(i)); + projectUuids.add(project.uuid()); + GroupPermissionDto dto = new GroupPermissionDto() + .setUuid(Uuids.createFast()) + .setGroupUuid(group.getUuid()) + .setGroupName(group.getName()) + .setRole(USER) + .setComponentUuid(project.uuid()) + .setComponentName(project.name()); + dbClient.groupPermissionDao().insert(dbSession, dto, project, null); + } + dbSession.commit(); + + assertThat(underTest.selectByUuids(dbClient, dbSession, projectUuids)) + .hasSize(3500) + .extracting(IndexPermissions::getProjectUuid) + .containsAll(projectUuids); + } + + @Test + public void return_private_project_without_any_permission_when_no_permission_in_DB() { + List dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid())); + + // no permissions + Assertions.assertThat(dtos).hasSize(1); + IndexPermissions dto = dtos.get(0); + assertThat(dto.getGroupUuids()).isEmpty(); + assertThat(dto.getUserUuids()).isEmpty(); + assertThat(dto.isAllowAnyone()).isFalse(); + assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid()); + assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier()); + } + + @Test + public void return_public_project_with_only_AllowAnyone_true_when_no_permission_in_DB() { + List dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(publicProject.uuid())); + + Assertions.assertThat(dtos).hasSize(1); + IndexPermissions dto = dtos.get(0); + assertThat(dto.getGroupUuids()).isEmpty(); + assertThat(dto.getUserUuids()).isEmpty(); + assertThat(dto.isAllowAnyone()).isTrue(); + assertThat(dto.getProjectUuid()).isEqualTo(publicProject.uuid()); + assertThat(dto.getQualifier()).isEqualTo(publicProject.qualifier()); + } + + @Test + public void return_private_project_with_AllowAnyone_false_and_user_id_when_user_is_granted_USER_permission_directly() { + dbTester.users().insertProjectPermissionOnUser(user1, USER, privateProject1); + List dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid())); + + Assertions.assertThat(dtos).hasSize(1); + IndexPermissions dto = dtos.get(0); + assertThat(dto.getGroupUuids()).isEmpty(); + assertThat(dto.getUserUuids()).containsOnly(user1.getUuid()); + assertThat(dto.isAllowAnyone()).isFalse(); + assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid()); + assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier()); + } + + @Test + public void return_private_project_with_AllowAnyone_false_and_group_id_but_not_user_id_when_user_is_granted_USER_permission_through_group() { + dbTester.users().insertMember(group, user1); + dbTester.users().insertProjectPermissionOnGroup(group, USER, privateProject1); + List dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid())); + + Assertions.assertThat(dtos).hasSize(1); + IndexPermissions dto = dtos.get(0); + assertThat(dto.getGroupUuids()).containsOnly(group.getUuid()); + assertThat(dto.getUserUuids()).isEmpty(); + assertThat(dto.isAllowAnyone()).isFalse(); + assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid()); + assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier()); + } + + private void isPublic(IndexPermissions view1Authorization, String qualifier) { + assertThat(view1Authorization.getGroupUuids()).isEmpty(); + assertThat(view1Authorization.isAllowAnyone()).isTrue(); + assertThat(view1Authorization.getUserUuids()).isEmpty(); + assertThat(view1Authorization.getQualifier()).isEqualTo(qualifier); + } + + private static IndexPermissions getByProjectUuid(String projectUuid, Collection dtos) { + return dtos.stream().filter(dto -> dto.getProjectUuid().equals(projectUuid)).findFirst().orElseThrow(IllegalArgumentException::new); + } + + private void insertTestDataForProjectsAndViews() { + // user1 has USER access on both private projects + userDbTester.insertProjectPermissionOnUser(user1, ADMIN, publicProject); + userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject1); + userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject2); + userDbTester.insertProjectPermissionOnUser(user1, ADMIN, view1); + userDbTester.insertProjectPermissionOnUser(user1, ADMIN, application); + + // user2 has USER access on privateProject1 only + userDbTester.insertProjectPermissionOnUser(user2, USER, privateProject1); + userDbTester.insertProjectPermissionOnUser(user2, ADMIN, privateProject2); + + // group1 has USER access on privateProject1 only + userDbTester.insertProjectPermissionOnGroup(group, USER, privateProject1); + userDbTester.insertProjectPermissionOnGroup(group, ADMIN, privateProject1); + userDbTester.insertProjectPermissionOnGroup(group, ADMIN, view1); + userDbTester.insertProjectPermissionOnGroup(group, ADMIN, application); + } +} diff --git a/server/sonar-webserver-es/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java b/server/sonar-webserver-es/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java deleted file mode 100644 index d847c5e67f6..00000000000 --- a/server/sonar-webserver-es/src/test/java/org/sonar/server/permission/index/PermissionIndexerDaoTest.java +++ /dev/null @@ -1,271 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.permission.index; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import org.assertj.core.api.Assertions; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.utils.System2; -import org.sonar.core.util.Uuids; -import org.sonar.core.util.stream.MoreCollectors; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.DbTester; -import org.sonar.db.component.ComponentDbTester; -import org.sonar.db.component.ComponentDto; -import org.sonar.db.permission.GroupPermissionDto; -import org.sonar.db.user.GroupDto; -import org.sonar.db.user.UserDbTester; -import org.sonar.db.user.UserDto; - -import static java.util.Arrays.asList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; -import static org.sonar.api.resources.Qualifiers.APP; -import static org.sonar.api.resources.Qualifiers.PROJECT; -import static org.sonar.api.resources.Qualifiers.VIEW; -import static org.sonar.api.web.UserRole.ADMIN; -import static org.sonar.api.web.UserRole.USER; - -public class PermissionIndexerDaoTest { - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private final DbClient dbClient = dbTester.getDbClient(); - private final DbSession dbSession = dbTester.getSession(); - private final ComponentDbTester componentDbTester = new ComponentDbTester(dbTester); - private final UserDbTester userDbTester = new UserDbTester(dbTester); - - private ComponentDto publicProject; - private ComponentDto privateProject1; - private ComponentDto privateProject2; - private ComponentDto view1; - private ComponentDto view2; - private ComponentDto application; - private UserDto user1; - private UserDto user2; - private GroupDto group; - - private final PermissionIndexerDao underTest = new PermissionIndexerDao(); - - @Before - public void setUp() { - publicProject = componentDbTester.insertPublicProject(); - privateProject1 = componentDbTester.insertPrivateProject(); - privateProject2 = componentDbTester.insertPrivateProject(); - view1 = componentDbTester.insertPublicPortfolio(); - view2 = componentDbTester.insertPublicPortfolio(); - application = componentDbTester.insertPublicApplication(); - user1 = userDbTester.insertUser(); - user2 = userDbTester.insertUser(); - group = userDbTester.insertGroup(); - } - - @Test - public void select_all() { - insertTestDataForProjectsAndViews(); - - Collection dtos = underTest.selectAll(dbClient, dbSession); - Assertions.assertThat(dtos).hasSize(6); - - IndexPermissions publicProjectAuthorization = getByProjectUuid(publicProject.uuid(), dtos); - isPublic(publicProjectAuthorization, PROJECT); - - IndexPermissions view1Authorization = getByProjectUuid(view1.uuid(), dtos); - isPublic(view1Authorization, VIEW); - - IndexPermissions applicationAuthorization = getByProjectUuid(application.uuid(), dtos); - isPublic(applicationAuthorization, APP); - - IndexPermissions privateProject1Authorization = getByProjectUuid(privateProject1.uuid(), dtos); - assertThat(privateProject1Authorization.getGroupUuids()).containsOnly(group.getUuid()); - assertThat(privateProject1Authorization.isAllowAnyone()).isFalse(); - assertThat(privateProject1Authorization.getUserUuids()).containsOnly(user1.getUuid(), user2.getUuid()); - assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT); - - IndexPermissions privateProject2Authorization = getByProjectUuid(privateProject2.uuid(), dtos); - assertThat(privateProject2Authorization.getGroupUuids()).isEmpty(); - assertThat(privateProject2Authorization.isAllowAnyone()).isFalse(); - assertThat(privateProject2Authorization.getUserUuids()).containsOnly(user1.getUuid()); - assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT); - - IndexPermissions view2Authorization = getByProjectUuid(view2.uuid(), dtos); - isPublic(view2Authorization, VIEW); - } - - @Test - public void selectByUuids() { - insertTestDataForProjectsAndViews(); - - Map dtos = underTest - .selectByUuids(dbClient, dbSession, asList(publicProject.uuid(), privateProject1.uuid(), privateProject2.uuid(), view1.uuid(), view2.uuid(), application.uuid())) - .stream() - .collect(MoreCollectors.uniqueIndex(IndexPermissions::getProjectUuid, Function.identity())); - Assertions.assertThat(dtos).hasSize(6); - - IndexPermissions publicProjectAuthorization = dtos.get(publicProject.uuid()); - isPublic(publicProjectAuthorization, PROJECT); - - IndexPermissions view1Authorization = dtos.get(view1.uuid()); - isPublic(view1Authorization, VIEW); - - IndexPermissions applicationAuthorization = dtos.get(application.uuid()); - isPublic(applicationAuthorization, APP); - - IndexPermissions privateProject1Authorization = dtos.get(privateProject1.uuid()); - assertThat(privateProject1Authorization.getGroupUuids()).containsOnly(group.getUuid()); - assertThat(privateProject1Authorization.isAllowAnyone()).isFalse(); - assertThat(privateProject1Authorization.getUserUuids()).containsOnly(user1.getUuid(), user2.getUuid()); - assertThat(privateProject1Authorization.getQualifier()).isEqualTo(PROJECT); - - IndexPermissions privateProject2Authorization = dtos.get(privateProject2.uuid()); - assertThat(privateProject2Authorization.getGroupUuids()).isEmpty(); - assertThat(privateProject2Authorization.isAllowAnyone()).isFalse(); - assertThat(privateProject2Authorization.getUserUuids()).containsOnly(user1.getUuid()); - assertThat(privateProject2Authorization.getQualifier()).isEqualTo(PROJECT); - - IndexPermissions view2Authorization = dtos.get(view2.uuid()); - isPublic(view2Authorization, VIEW); - } - - @Test - public void selectByUuids_returns_empty_list_when_project_does_not_exist() { - insertTestDataForProjectsAndViews(); - - List dtos = underTest.selectByUuids(dbClient, dbSession, singletonList("missing")); - Assertions.assertThat(dtos).isEmpty(); - } - - @Test - public void select_by_projects_with_high_number_of_projects() { - List projectUuids = new ArrayList<>(); - for (int i = 0; i < 3500; i++) { - ComponentDto project = dbTester.components().insertPrivateProject(Integer.toString(i)); - projectUuids.add(project.uuid()); - GroupPermissionDto dto = new GroupPermissionDto() - .setUuid(Uuids.createFast()) - .setGroupUuid(group.getUuid()) - .setGroupName(group.getName()) - .setRole(USER) - .setComponentUuid(project.uuid()) - .setComponentName(project.name()); - dbClient.groupPermissionDao().insert(dbSession, dto, project, null); - } - dbSession.commit(); - - assertThat(underTest.selectByUuids(dbClient, dbSession, projectUuids)) - .hasSize(3500) - .extracting(IndexPermissions::getProjectUuid) - .containsAll(projectUuids); - } - - @Test - public void return_private_project_without_any_permission_when_no_permission_in_DB() { - List dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid())); - - // no permissions - Assertions.assertThat(dtos).hasSize(1); - IndexPermissions dto = dtos.get(0); - assertThat(dto.getGroupUuids()).isEmpty(); - assertThat(dto.getUserUuids()).isEmpty(); - assertThat(dto.isAllowAnyone()).isFalse(); - assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid()); - assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier()); - } - - @Test - public void return_public_project_with_only_AllowAnyone_true_when_no_permission_in_DB() { - List dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(publicProject.uuid())); - - Assertions.assertThat(dtos).hasSize(1); - IndexPermissions dto = dtos.get(0); - assertThat(dto.getGroupUuids()).isEmpty(); - assertThat(dto.getUserUuids()).isEmpty(); - assertThat(dto.isAllowAnyone()).isTrue(); - assertThat(dto.getProjectUuid()).isEqualTo(publicProject.uuid()); - assertThat(dto.getQualifier()).isEqualTo(publicProject.qualifier()); - } - - @Test - public void return_private_project_with_AllowAnyone_false_and_user_id_when_user_is_granted_USER_permission_directly() { - dbTester.users().insertProjectPermissionOnUser(user1, USER, privateProject1); - List dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid())); - - Assertions.assertThat(dtos).hasSize(1); - IndexPermissions dto = dtos.get(0); - assertThat(dto.getGroupUuids()).isEmpty(); - assertThat(dto.getUserUuids()).containsOnly(user1.getUuid()); - assertThat(dto.isAllowAnyone()).isFalse(); - assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid()); - assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier()); - } - - @Test - public void return_private_project_with_AllowAnyone_false_and_group_id_but_not_user_id_when_user_is_granted_USER_permission_through_group() { - dbTester.users().insertMember(group, user1); - dbTester.users().insertProjectPermissionOnGroup(group, USER, privateProject1); - List dtos = underTest.selectByUuids(dbClient, dbSession, singletonList(privateProject1.uuid())); - - Assertions.assertThat(dtos).hasSize(1); - IndexPermissions dto = dtos.get(0); - assertThat(dto.getGroupUuids()).containsOnly(group.getUuid()); - assertThat(dto.getUserUuids()).isEmpty(); - assertThat(dto.isAllowAnyone()).isFalse(); - assertThat(dto.getProjectUuid()).isEqualTo(privateProject1.uuid()); - assertThat(dto.getQualifier()).isEqualTo(privateProject1.qualifier()); - } - - private void isPublic(IndexPermissions view1Authorization, String qualifier) { - assertThat(view1Authorization.getGroupUuids()).isEmpty(); - assertThat(view1Authorization.isAllowAnyone()).isTrue(); - assertThat(view1Authorization.getUserUuids()).isEmpty(); - assertThat(view1Authorization.getQualifier()).isEqualTo(qualifier); - } - - private static IndexPermissions getByProjectUuid(String projectUuid, Collection dtos) { - return dtos.stream().filter(dto -> dto.getProjectUuid().equals(projectUuid)).findFirst().orElseThrow(IllegalArgumentException::new); - } - - private void insertTestDataForProjectsAndViews() { - // user1 has USER access on both private projects - userDbTester.insertProjectPermissionOnUser(user1, ADMIN, publicProject); - userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject1); - userDbTester.insertProjectPermissionOnUser(user1, USER, privateProject2); - userDbTester.insertProjectPermissionOnUser(user1, ADMIN, view1); - userDbTester.insertProjectPermissionOnUser(user1, ADMIN, application); - - // user2 has USER access on privateProject1 only - userDbTester.insertProjectPermissionOnUser(user2, USER, privateProject1); - userDbTester.insertProjectPermissionOnUser(user2, ADMIN, privateProject2); - - // group1 has USER access on privateProject1 only - userDbTester.insertProjectPermissionOnGroup(group, USER, privateProject1); - userDbTester.insertProjectPermissionOnGroup(group, ADMIN, privateProject1); - userDbTester.insertProjectPermissionOnGroup(group, ADMIN, view1); - userDbTester.insertProjectPermissionOnGroup(group, ADMIN, application); - } -} diff --git a/server/sonar-webserver-pushapi/src/it/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorIT.java b/server/sonar-webserver-pushapi/src/it/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorIT.java new file mode 100644 index 00000000000..0600d517b21 --- /dev/null +++ b/server/sonar-webserver-pushapi/src/it/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorIT.java @@ -0,0 +1,101 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.pushapi.scheduler.purge; + +import java.nio.charset.StandardCharsets; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.config.Configuration; +import org.sonar.api.utils.System2; +import org.sonar.db.DbClient; +import org.sonar.db.DbTester; +import org.sonar.db.pushevent.PushEventDto; +import org.sonar.server.util.GlobalLockManagerImpl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeScheduler.ENQUEUE_DELAY_IN_SECONDS; +import static org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeScheduler.INITIAL_DELAY_IN_SECONDS; + +public class PushEventsPurgeSchedulerAndExecutorIT { + + private static final String PUSH_EVENT_UUID = "push_event_uuid"; + private static final long INITIAL_AND_ENQUE_DELAY_MS = 1L; + + @Rule + public DbTester dbTester = DbTester.create(System2.INSTANCE); + + private PushEventsPurgeScheduler pushEventsPurgeScheduler; + + @Before + public void setup() { + Configuration configuration = mock(Configuration.class); + when(configuration.getLong(INITIAL_DELAY_IN_SECONDS)).thenReturn(Optional.of(INITIAL_AND_ENQUE_DELAY_MS)); + when(configuration.getLong(ENQUEUE_DELAY_IN_SECONDS)).thenReturn(Optional.of(INITIAL_AND_ENQUE_DELAY_MS)); + + GlobalLockManagerImpl lockManager = mock(GlobalLockManagerImpl.class); + when(lockManager.tryLock(any(), anyInt())).thenReturn(true); + + DbClient dbClient = dbTester.getDbClient(); + pushEventsPurgeScheduler = new PushEventsPurgeScheduler( + dbClient, + configuration, + lockManager, + new PushEventsPurgeExecutorServiceImpl(), + System2.INSTANCE + ); + } + + @Test + public void pushEventsPurgeScheduler_shouldCleanUpPeriodically() throws InterruptedException { + insertOldPushEvent(); + assertThat(pushEventIsCleanedUp()).isFalse(); + + pushEventsPurgeScheduler.start(); + + await() + .atMost(3, TimeUnit.SECONDS) + .pollDelay(500, TimeUnit.MILLISECONDS) + .until(this::pushEventIsCleanedUp); + } + + private void insertOldPushEvent() { + PushEventDto pushEventDto = new PushEventDto(); + pushEventDto.setUuid(PUSH_EVENT_UUID); + pushEventDto.setName("test_event"); + pushEventDto.setProjectUuid("test_project_uuid"); + pushEventDto.setPayload("payload".getBytes(StandardCharsets.UTF_8)); + pushEventDto.setCreatedAt(1656633600); + dbTester.getDbClient().pushEventDao().insert(dbTester.getSession(), pushEventDto); + dbTester.commit(); + } + + private boolean pushEventIsCleanedUp() { + return dbTester.getDbClient().pushEventDao().selectByUuid(dbTester.getSession(), PUSH_EVENT_UUID) == null; + } + +} diff --git a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorTest.java b/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorTest.java deleted file mode 100644 index e82fc3c18a8..00000000000 --- a/server/sonar-webserver-pushapi/src/test/java/org/sonar/server/pushapi/scheduler/purge/PushEventsPurgeSchedulerAndExecutorTest.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.pushapi.scheduler.purge; - -import java.nio.charset.StandardCharsets; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.config.Configuration; -import org.sonar.api.utils.System2; -import org.sonar.db.DbClient; -import org.sonar.db.DbTester; -import org.sonar.db.pushevent.PushEventDto; -import org.sonar.server.util.GlobalLockManagerImpl; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.awaitility.Awaitility.await; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeScheduler.ENQUEUE_DELAY_IN_SECONDS; -import static org.sonar.server.pushapi.scheduler.purge.PushEventsPurgeScheduler.INITIAL_DELAY_IN_SECONDS; - -public class PushEventsPurgeSchedulerAndExecutorTest { - - private static final String PUSH_EVENT_UUID = "push_event_uuid"; - private static final long INITIAL_AND_ENQUE_DELAY_MS = 1L; - - @Rule - public DbTester dbTester = DbTester.create(System2.INSTANCE); - - private PushEventsPurgeScheduler pushEventsPurgeScheduler; - - @Before - public void setup() { - Configuration configuration = mock(Configuration.class); - when(configuration.getLong(INITIAL_DELAY_IN_SECONDS)).thenReturn(Optional.of(INITIAL_AND_ENQUE_DELAY_MS)); - when(configuration.getLong(ENQUEUE_DELAY_IN_SECONDS)).thenReturn(Optional.of(INITIAL_AND_ENQUE_DELAY_MS)); - - GlobalLockManagerImpl lockManager = mock(GlobalLockManagerImpl.class); - when(lockManager.tryLock(any(), anyInt())).thenReturn(true); - - DbClient dbClient = dbTester.getDbClient(); - pushEventsPurgeScheduler = new PushEventsPurgeScheduler( - dbClient, - configuration, - lockManager, - new PushEventsPurgeExecutorServiceImpl(), - System2.INSTANCE - ); - } - - @Test - public void pushEventsPurgeScheduler_shouldCleanUpPeriodically() throws InterruptedException { - insertOldPushEvent(); - assertThat(pushEventIsCleanedUp()).isFalse(); - - pushEventsPurgeScheduler.start(); - - await() - .atMost(3, TimeUnit.SECONDS) - .pollDelay(500, TimeUnit.MILLISECONDS) - .until(this::pushEventIsCleanedUp); - } - - private void insertOldPushEvent() { - PushEventDto pushEventDto = new PushEventDto(); - pushEventDto.setUuid(PUSH_EVENT_UUID); - pushEventDto.setName("test_event"); - pushEventDto.setProjectUuid("test_project_uuid"); - pushEventDto.setPayload("payload".getBytes(StandardCharsets.UTF_8)); - pushEventDto.setCreatedAt(1656633600); - dbTester.getDbClient().pushEventDao().insert(dbTester.getSession(), pushEventDto); - dbTester.commit(); - } - - private boolean pushEventIsCleanedUp() { - return dbTester.getDbClient().pushEventDao().selectByUuid(dbTester.getSession(), PUSH_EVENT_UUID) == null; - } - -} diff --git a/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/SonarLintConnectionFilterIT.java b/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/SonarLintConnectionFilterIT.java new file mode 100644 index 00000000000..45f4be0b9bd --- /dev/null +++ b/server/sonar-webserver/src/it/java/org/sonar/server/platform/web/SonarLintConnectionFilterIT.java @@ -0,0 +1,134 @@ +/* + * SonarQube + * Copyright (C) 2009-2023 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.web; + +import java.io.IOException; +import javax.annotation.CheckForNull; +import javax.annotation.Nullable; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import org.junit.Rule; +import org.junit.Test; +import org.sonar.api.impl.utils.TestSystem2; +import org.sonar.db.DbTester; +import org.sonar.db.user.UserDto; +import org.sonar.server.user.ServerUserSession; +import org.sonar.server.user.ThreadLocalUserSession; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SonarLintConnectionFilterIT { + private static final String LOGIN = "user1"; + private final TestSystem2 system2 = new TestSystem2(); + + @Rule + public DbTester dbTester = DbTester.create(system2); + + @Test + public void update() throws IOException, ServletException { + system2.setNow(10_000_000L); + addUser(LOGIN, 1_000_000L); + + runFilter(LOGIN, "SonarLint for IntelliJ"); + assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L); + } + + @Test + public void update_first_time() throws IOException, ServletException { + system2.setNow(10_000_000L); + addUser(LOGIN, null); + + runFilter(LOGIN, "SonarLint for IntelliJ"); + assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L); + } + + @Test + public void only_applies_to_api() { + SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), mock(ThreadLocalUserSession.class), system2); + assertThat(underTest.doGetPattern().matches("/api/test")).isTrue(); + assertThat(underTest.doGetPattern().matches("/test")).isFalse(); + + } + + @Test + public void do_nothing_if_no_sonarlint_agent() throws IOException, ServletException { + system2.setNow(10_000L); + addUser(LOGIN, 1_000L); + + runFilter(LOGIN, "unknown"); + runFilter(LOGIN, null); + assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000L); + } + + @Test + public void do_nothing_if_not_logged_in() throws IOException, ServletException { + system2.setNow(10_000_000L); + addUser("invalid", 1_000_000L); + + runFilter(LOGIN, "SonarLint for IntelliJ"); + assertThat(getLastUpdate("invalid")).isEqualTo(1_000_000L); + } + + @Test + public void dont_fail_if_no_user_set() throws IOException, ServletException { + SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), new ThreadLocalUserSession(), system2); + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getHeader("User-Agent")).thenReturn("sonarlint"); + FilterChain chain = mock(FilterChain.class); + underTest.doFilter(request, mock(ServletResponse.class), chain); + verify(chain).doFilter(any(), any()); + } + + @Test + public void only_update_if_not_updated_within_1h() throws IOException, ServletException { + system2.setNow(2_000_000L); + addUser(LOGIN, 1_000_000L); + + runFilter(LOGIN, "SonarLint for IntelliJ"); + assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000_000L); + } + + private void addUser(String login, @Nullable Long lastUpdate) { + dbTester.users().insertUser(u -> u.setLogin(login).setLastSonarlintConnectionDate(lastUpdate)); + } + + @CheckForNull + private Long getLastUpdate(String login) { + return dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), login).getLastSonarlintConnectionDate(); + } + + private void runFilter(String loggedInUser, @Nullable String agent) throws IOException, ServletException { + UserDto user = dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), loggedInUser); + ThreadLocalUserSession session = new ThreadLocalUserSession(); + session.set(new ServerUserSession(dbTester.getDbClient(), user)); + SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), session, system2); + HttpServletRequest request = mock(HttpServletRequest.class); + when(request.getHeader("User-Agent")).thenReturn(agent); + FilterChain chain = mock(FilterChain.class); + underTest.doFilter(request, mock(ServletResponse.class), chain); + verify(chain).doFilter(any(), any()); + } +} diff --git a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java b/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java deleted file mode 100644 index fd08c97a365..00000000000 --- a/server/sonar-webserver/src/test/java/org/sonar/server/platform/web/SonarLintConnectionFilterTest.java +++ /dev/null @@ -1,134 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2023 SonarSource SA - * mailto:info AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform.web; - -import java.io.IOException; -import javax.annotation.CheckForNull; -import javax.annotation.Nullable; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import org.junit.Rule; -import org.junit.Test; -import org.sonar.api.impl.utils.TestSystem2; -import org.sonar.db.DbTester; -import org.sonar.db.user.UserDto; -import org.sonar.server.user.ServerUserSession; -import org.sonar.server.user.ThreadLocalUserSession; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class SonarLintConnectionFilterTest { - private static final String LOGIN = "user1"; - private final TestSystem2 system2 = new TestSystem2(); - - @Rule - public DbTester dbTester = DbTester.create(system2); - - @Test - public void update() throws IOException, ServletException { - system2.setNow(10_000_000L); - addUser(LOGIN, 1_000_000L); - - runFilter(LOGIN, "SonarLint for IntelliJ"); - assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L); - } - - @Test - public void update_first_time() throws IOException, ServletException { - system2.setNow(10_000_000L); - addUser(LOGIN, null); - - runFilter(LOGIN, "SonarLint for IntelliJ"); - assertThat(getLastUpdate(LOGIN)).isEqualTo(10_000_000L); - } - - @Test - public void only_applies_to_api() { - SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), mock(ThreadLocalUserSession.class), system2); - assertThat(underTest.doGetPattern().matches("/api/test")).isTrue(); - assertThat(underTest.doGetPattern().matches("/test")).isFalse(); - - } - - @Test - public void do_nothing_if_no_sonarlint_agent() throws IOException, ServletException { - system2.setNow(10_000L); - addUser(LOGIN, 1_000L); - - runFilter(LOGIN, "unknown"); - runFilter(LOGIN, null); - assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000L); - } - - @Test - public void do_nothing_if_not_logged_in() throws IOException, ServletException { - system2.setNow(10_000_000L); - addUser("invalid", 1_000_000L); - - runFilter(LOGIN, "SonarLint for IntelliJ"); - assertThat(getLastUpdate("invalid")).isEqualTo(1_000_000L); - } - - @Test - public void dont_fail_if_no_user_set() throws IOException, ServletException { - SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), new ThreadLocalUserSession(), system2); - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getHeader("User-Agent")).thenReturn("sonarlint"); - FilterChain chain = mock(FilterChain.class); - underTest.doFilter(request, mock(ServletResponse.class), chain); - verify(chain).doFilter(any(), any()); - } - - @Test - public void only_update_if_not_updated_within_1h() throws IOException, ServletException { - system2.setNow(2_000_000L); - addUser(LOGIN, 1_000_000L); - - runFilter(LOGIN, "SonarLint for IntelliJ"); - assertThat(getLastUpdate(LOGIN)).isEqualTo(1_000_000L); - } - - private void addUser(String login, @Nullable Long lastUpdate) { - dbTester.users().insertUser(u -> u.setLogin(login).setLastSonarlintConnectionDate(lastUpdate)); - } - - @CheckForNull - private Long getLastUpdate(String login) { - return dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), login).getLastSonarlintConnectionDate(); - } - - private void runFilter(String loggedInUser, @Nullable String agent) throws IOException, ServletException { - UserDto user = dbTester.getDbClient().userDao().selectByLogin(dbTester.getSession(), loggedInUser); - ThreadLocalUserSession session = new ThreadLocalUserSession(); - session.set(new ServerUserSession(dbTester.getDbClient(), user)); - SonarLintConnectionFilter underTest = new SonarLintConnectionFilter(dbTester.getDbClient(), session, system2); - HttpServletRequest request = mock(HttpServletRequest.class); - when(request.getHeader("User-Agent")).thenReturn(agent); - FilterChain chain = mock(FilterChain.class); - underTest.doFilter(request, mock(ServletResponse.class), chain); - verify(chain).doFilter(any(), any()); - } -}