From 984ac870408a5ad6c444693ff7b06af103aad734 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Mon, 23 Apr 2018 11:30:32 +0200 Subject: [PATCH] SONAR-10430 populate FILE_SOURCES.LINE_COUNT at beginning of analysis --- ...ProjectAnalysisTaskContainerPopulator.java | 3 + .../dbmigration/DbMigrationModule.java | 30 +++ .../PopulateFileSourceLineCount.java | 69 ++++++ .../ProjectAnalysisDataChange.java | 32 +++ .../ProjectAnalysisDataChanges.java | 29 +++ .../ProjectAnalysisDataChangesImpl.java | 65 ++++++ .../dbmigration/package-info.java | 23 ++ .../step/DbMigrationsStep.java | 52 +++++ .../step/ReportComputationSteps.java | 1 + .../dbmigration/DbMigrationModuleTest.java | 53 +++++ .../PopulateFileSourceLineCountTest.java | 207 ++++++++++++++++++ .../ProjectAnalysisDataChangesImplTest.java | 78 +++++++ .../step/DbMigrationsStepTest.java | 103 +++++++++ .../file_sources.sql | 17 ++ 14 files changed, 762 insertions(+) create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModule.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCount.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChange.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChanges.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImpl.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/package-info.java create mode 100644 server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStep.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModuleTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImplTest.java create mode 100644 server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStepTest.java create mode 100644 server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest/file_sources.sql diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java index 948f56d7d32..800583830a3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java @@ -41,6 +41,7 @@ import org.sonar.server.computation.task.projectanalysis.component.DisabledCompo import org.sonar.server.computation.task.projectanalysis.component.MergeBranchComponentUuids; import org.sonar.server.computation.task.projectanalysis.component.ShortBranchComponentsWithIssues; import org.sonar.server.computation.task.projectanalysis.component.TreeRootHolderImpl; +import org.sonar.server.computation.task.projectanalysis.dbmigration.DbMigrationModule; import org.sonar.server.computation.task.projectanalysis.duplication.CrossProjectDuplicationStatusHolderImpl; import org.sonar.server.computation.task.projectanalysis.duplication.DuplicationMeasures; import org.sonar.server.computation.task.projectanalysis.duplication.DuplicationRepositoryImpl; @@ -165,6 +166,8 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop // File System new ComputationTempFolderProvider(), + DbMigrationModule.class, + MetricModule.class, // holders diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModule.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModule.java new file mode 100644 index 00000000000..b62d965d07f --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModule.java @@ -0,0 +1,30 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.computation.task.projectanalysis.dbmigration; + +import org.sonar.core.platform.Module; + +public class DbMigrationModule extends Module { + @Override + protected void configureModule() { + add(ProjectAnalysisDataChangesImpl.class); + ProjectAnalysisDataChangesImpl.getDataChangeClasses().forEach(this::add); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCount.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCount.java new file mode 100644 index 00000000000..a4fa5624196 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCount.java @@ -0,0 +1,69 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.computation.task.projectanalysis.dbmigration; + +import com.google.common.collect.Iterables; +import java.sql.SQLException; +import org.sonar.ce.queue.CeTask; +import org.sonar.db.Database; +import org.sonar.db.source.FileSourceDto; +import org.sonar.server.platform.db.migration.step.DataChange; +import org.sonar.server.platform.db.migration.step.MassUpdate; +import org.sonar.server.platform.db.migration.step.Select; +import org.sonar.server.platform.db.migration.step.SqlStatement; + +import static org.sonar.db.source.FileSourceDto.LINE_COUNT_NOT_POPULATED; + +public class PopulateFileSourceLineCount extends DataChange implements ProjectAnalysisDataChange { + private final CeTask ceTask; + + public PopulateFileSourceLineCount(Database database, CeTask ceTask) { + super(database); + this.ceTask = ceTask; + } + + @Override + protected void execute(Context context) throws SQLException { + Long unInitializedFileSources = context.prepareSelect("select count(1) from file_sources where line_count = ? and project_uuid = ?") + .setInt(1, LINE_COUNT_NOT_POPULATED) + .setString(2, ceTask.getComponentUuid()) + .get(row -> row.getLong(1)); + + if (unInitializedFileSources != null && unInitializedFileSources > 0) { + MassUpdate massUpdate = context.prepareMassUpdate(); + massUpdate.select("select id,line_hashes from file_sources where line_count = ? and project_uuid = ?") + .setInt(1, LINE_COUNT_NOT_POPULATED) + .setString(2, ceTask.getComponentUuid()); + massUpdate.update("update file_sources set line_count = ? where id = ?"); + massUpdate.rowPluralName("line counts of sources of project " + ceTask.getComponentUuid()); + massUpdate.execute(PopulateFileSourceLineCount::handle); + } + } + + private static boolean handle(Select.Row row, SqlStatement update) throws SQLException { + int rowId = row.getInt(1); + String rawData = row.getNullableString(2); + + int lineCount = rawData == null ? 0 : Iterables.size(FileSourceDto.LINES_HASHES_SPLITTER.split(rawData)); + update.setInt(1, lineCount); + update.setInt(2, rowId); + return true; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChange.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChange.java new file mode 100644 index 00000000000..e8318434b53 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChange.java @@ -0,0 +1,32 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.computation.task.projectanalysis.dbmigration; + +import org.sonar.server.platform.db.migration.step.MigrationStep; + +/** + * Marker interface of {@link MigrationStep} for the implementations to be run in + * {@link org.sonar.server.computation.task.projectanalysis.step.DbMigrationsStep DbMigrationsStep}. + *

+ * {@link MigrationStep} execute during project report analysis should perform only data change operations. + */ +public interface ProjectAnalysisDataChange extends MigrationStep { + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChanges.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChanges.java new file mode 100644 index 00000000000..c40b3dae60e --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChanges.java @@ -0,0 +1,29 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.computation.task.projectanalysis.dbmigration; + +import java.util.List; + +public interface ProjectAnalysisDataChanges { + /** + * @return {@link ProjectAnalysisDataChange} instances to be executed in order. + */ + List getDataChanges(); +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImpl.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImpl.java new file mode 100644 index 00000000000..83b323881da --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImpl.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.computation.task.projectanalysis.dbmigration; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkState; +import static com.google.common.collect.ImmutableList.of; +import static org.sonar.core.util.stream.MoreCollectors.toList; +import static org.sonar.core.util.stream.MoreCollectors.uniqueIndex; + +/** + * Implementation of {@link ProjectAnalysisDataChanges} based on an ordered list of {@link ProjectAnalysisDataChange} + * classes and the {@link ProjectAnalysisDataChange} instances which can be injected by the container. + */ +public class ProjectAnalysisDataChangesImpl implements ProjectAnalysisDataChanges { + private static final List> DATA_CHANGE_CLASSES_IN_ORDER_OF_EXECUTION = of( + PopulateFileSourceLineCount.class); + private final List dataChangeInstances; + + public ProjectAnalysisDataChangesImpl(ProjectAnalysisDataChange[] dataChanges) { + checkArgument(dataChanges.length == DATA_CHANGE_CLASSES_IN_ORDER_OF_EXECUTION.size(), + "Number of ProjectAnalysisDataChange instance available (%s) is inconsistent with the number of declared ProjectAnalysisDataChange types (%s)", + dataChanges.length, + DATA_CHANGE_CLASSES_IN_ORDER_OF_EXECUTION.size()); + Map, ProjectAnalysisDataChange> dataChangesByClass = Arrays.stream(dataChanges) + .collect(uniqueIndex(ProjectAnalysisDataChange::getClass)); + dataChangeInstances = DATA_CHANGE_CLASSES_IN_ORDER_OF_EXECUTION.stream() + .map(dataChangesByClass::get) + .filter(Objects::nonNull) + .collect(toList(DATA_CHANGE_CLASSES_IN_ORDER_OF_EXECUTION.size())); + checkState(dataChangeInstances.size() == DATA_CHANGE_CLASSES_IN_ORDER_OF_EXECUTION.size(), + "Some of the ProjectAnalysisDataChange type declared have no instance in the container"); + } + + static List> getDataChangeClasses() { + return DATA_CHANGE_CLASSES_IN_ORDER_OF_EXECUTION; + } + + @Override + public List getDataChanges() { + return dataChangeInstances; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/package-info.java new file mode 100644 index 00000000000..25c33db2ddf --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.computation.task.projectanalysis.dbmigration; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStep.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStep.java new file mode 100644 index 00000000000..0a44b823feb --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStep.java @@ -0,0 +1,52 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.computation.task.projectanalysis.step; + +import java.sql.SQLException; +import org.sonar.server.computation.task.projectanalysis.dbmigration.ProjectAnalysisDataChange; +import org.sonar.server.computation.task.projectanalysis.dbmigration.ProjectAnalysisDataChanges; +import org.sonar.server.computation.task.step.ComputationStep; + +public class DbMigrationsStep implements ComputationStep { + private final ProjectAnalysisDataChanges dataChanges; + + public DbMigrationsStep(ProjectAnalysisDataChanges dataChanges) { + this.dataChanges = dataChanges; + } + + @Override + public String getDescription() { + return "Execute DB migrations for current project"; + } + + @Override + public void execute() { + dataChanges.getDataChanges().forEach(DbMigrationsStep::execute); + } + + private static void execute(ProjectAnalysisDataChange dataChange) { + try { + dataChange.execute(); + } catch (SQLException e) { + throw new IllegalStateException("Failed to perform DB migration for project", e); + } + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationSteps.java b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationSteps.java index 852be83599e..e75c98dba74 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationSteps.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationSteps.java @@ -35,6 +35,7 @@ public class ReportComputationSteps extends AbstractComputationSteps { private static final List> STEPS = Arrays.asList( ExtractReportStep.class, PersistScannerContextStep.class, + DbMigrationsStep.class, GenerateAnalysisUuid.class, // Builds Component tree diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModuleTest.java new file mode 100644 index 00000000000..2928224410d --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModuleTest.java @@ -0,0 +1,53 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.computation.task.projectanalysis.dbmigration; + +import java.util.Objects; +import org.junit.Test; +import org.sonar.core.platform.ComponentContainer; + +import static org.assertj.core.api.Assertions.assertThat; + +public class DbMigrationModuleTest { + private DbMigrationModule underTest = new DbMigrationModule(); + + @Test + public void module_configure_ProjectAnalysisDataChanges_implementation() { + ComponentContainer container = new ComponentContainer(); + + underTest.configure(container); + + assertThat(container.getPicoContainer().getComponentAdapters(ProjectAnalysisDataChanges.class)) + .hasSize(1); + } + + @Test + public void module_includes_ProjectAnalysisDataChange_classes() { + ComponentContainer container = new ComponentContainer(); + + underTest.configure(container); + + assertThat(ProjectAnalysisDataChangesImpl.getDataChangeClasses() + .stream() + .map(t -> container.getPicoContainer().getComponentAdapter(t)) + .filter(Objects::nonNull) + ).hasSize(ProjectAnalysisDataChangesImpl.getDataChangeClasses().size()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest.java new file mode 100644 index 00000000000..87ba32e4b67 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest.java @@ -0,0 +1,207 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.computation.task.projectanalysis.dbmigration; + +import com.tngtech.java.junit.dataprovider.DataProvider; +import com.tngtech.java.junit.dataprovider.DataProviderRunner; +import com.tngtech.java.junit.dataprovider.UseDataProvider; +import java.sql.SQLException; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.runner.RunWith; +import org.sonar.api.utils.System2; +import org.sonar.ce.queue.CeTask; +import org.sonar.db.DbTester; +import org.sonar.db.source.FileSourceDto; + +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +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.source.FileSourceDto.LINE_COUNT_NOT_POPULATED; + +@RunWith(DataProviderRunner.class) +public class PopulateFileSourceLineCountTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Rule + public DbTester db = DbTester.createForSchema(System2.INSTANCE, PopulateFileSourceLineCountTest.class, "file_sources.sql"); + + private Random random = new Random(); + private CeTask ceTask = mock(CeTask.class); + private PopulateFileSourceLineCount underTest = new PopulateFileSourceLineCount(db.database(), ceTask); + + @Test + public void execute_has_no_effect_on_empty_table() throws SQLException { + underTest.execute(); + } + + @Test + @UseDataProvider("anyType") + public void execute_populates_line_count_of_any_type(String type) throws SQLException { + String projectUuid = randomAlphanumeric(4); + String fileUuid = randomAlphanumeric(5); + when(ceTask.getComponentUuid()).thenReturn(projectUuid); + int lineCount = 1 + random.nextInt(15); + insertUnpopulatedFileSource(projectUuid, fileUuid, type, lineCount); + assertThat(getLineCountByFileUuid(fileUuid)).isEqualTo(LINE_COUNT_NOT_POPULATED); + + underTest.execute(); + + assertThat(getLineCountByFileUuid(fileUuid)).isEqualTo(lineCount); + } + + @Test + @UseDataProvider("anyType") + public void execute_changes_only_file_source_with_LINE_COUNT_NOT_POPULATED_value(String type) throws SQLException { + String projectUuid = randomAlphanumeric(4); + String fileUuid1 = randomAlphanumeric(5); + String fileUuid2 = randomAlphanumeric(6); + String fileUuid3 = randomAlphanumeric(7); + int lineCountFile1 = 100 + random.nextInt(15); + int lineCountFile2 = 50 + random.nextInt(15); + int lineCountFile3 = 150 + random.nextInt(15); + + when(ceTask.getComponentUuid()).thenReturn(projectUuid); + insertPopulatedFileSource(projectUuid, fileUuid1, type, lineCountFile1); + int badLineCountFile2 = insertInconsistentPopulatedFileSource(projectUuid, fileUuid2, type, lineCountFile2); + insertUnpopulatedFileSource(projectUuid, fileUuid3, type, lineCountFile3); + assertThat(getLineCountByFileUuid(fileUuid1)).isEqualTo(lineCountFile1); + assertThat(getLineCountByFileUuid(fileUuid2)).isEqualTo(badLineCountFile2); + assertThat(getLineCountByFileUuid(fileUuid3)).isEqualTo(LINE_COUNT_NOT_POPULATED); + + underTest.execute(); + + assertThat(getLineCountByFileUuid(fileUuid1)).isEqualTo(lineCountFile1); + assertThat(getLineCountByFileUuid(fileUuid2)).isEqualTo(badLineCountFile2); + assertThat(getLineCountByFileUuid(fileUuid3)).isEqualTo(lineCountFile3); + } + + @Test + @UseDataProvider("anyType") + public void execute_changes_only_file_source_of_CeTask_component_uuid(String type) throws SQLException { + String projectUuid1 = randomAlphanumeric(4); + String projectUuid2 = randomAlphanumeric(5); + String fileUuid1 = randomAlphanumeric(6); + String fileUuid2 = randomAlphanumeric(7); + int lineCountFile1 = 100 + random.nextInt(15); + int lineCountFile2 = 30 + random.nextInt(15); + + when(ceTask.getComponentUuid()).thenReturn(projectUuid1); + insertUnpopulatedFileSource(projectUuid1, fileUuid1, type, lineCountFile1); + insertUnpopulatedFileSource(projectUuid2, fileUuid2, type, lineCountFile2); + + underTest.execute(); + + assertThat(getLineCountByFileUuid(fileUuid1)).isEqualTo(lineCountFile1); + assertThat(getLineCountByFileUuid(fileUuid2)).isEqualTo(LINE_COUNT_NOT_POPULATED); + } + + @Test + @UseDataProvider("anyType") + public void execute_set_line_count_to_zero_when_file_source_has_no_line_hashes(String type) throws SQLException { + String projectUuid = randomAlphanumeric(4); + String fileUuid1 = randomAlphanumeric(5); + + when(ceTask.getComponentUuid()).thenReturn(projectUuid); + insertFileSource(projectUuid, fileUuid1, type, null, LINE_COUNT_NOT_POPULATED); + + underTest.execute(); + + assertThat(getLineCountByFileUuid(fileUuid1)).isZero(); + } + + @Test + @UseDataProvider("anyType") + public void execute_set_line_count_to_1_when_file_source_has_empty_line_hashes(String type) throws SQLException { + String projectUuid = randomAlphanumeric(4); + String fileUuid1 = randomAlphanumeric(5); + + when(ceTask.getComponentUuid()).thenReturn(projectUuid); + insertFileSource(projectUuid, fileUuid1, type, "", LINE_COUNT_NOT_POPULATED); + + underTest.execute(); + + assertThat(getLineCountByFileUuid(fileUuid1)).isEqualTo(1); + } + + @DataProvider + public static Object[][] anyType() { + return new Object[][] { + { FileSourceDto.Type.SOURCE}, + { FileSourceDto.Type.TEST}, + { null}, + { randomAlphanumeric(3)}, + }; + } + + private int getLineCountByFileUuid(String fileUuid) { + Long res = (Long) db.selectFirst("select line_count as \"LINE_COUNT\" from file_sources where file_uuid = '" + fileUuid + "'") + .get("LINE_COUNT"); + return res.intValue(); + } + + private void insertUnpopulatedFileSource(String projectUuid, String fileUuid, @Nullable String dataType, int numberOfHashes) { + String lineHashes = generateLineHashes(numberOfHashes); + + insertFileSource(projectUuid, fileUuid, dataType, lineHashes, LINE_COUNT_NOT_POPULATED); + } + + private void insertPopulatedFileSource(String projectUuid, String fileUuid, @Nullable String dataType, int lineCount) { + String lineHashes = generateLineHashes(lineCount); + + insertFileSource(projectUuid, fileUuid, dataType, lineHashes, lineCount); + } + + private int insertInconsistentPopulatedFileSource(String projectUuid, String fileUuid, @Nullable String dataType, int lineCount) { + String lineHashes = generateLineHashes(lineCount); + int badLineCount = lineCount + random.nextInt(6); + + insertFileSource(projectUuid, fileUuid, dataType, lineHashes, badLineCount); + + return badLineCount; + } + + private static String generateLineHashes(int numberOfHashes) { + return IntStream.range(0, numberOfHashes) + .mapToObj(String::valueOf) + .collect(Collectors.joining("\n")); + } + + private void insertFileSource(String projectUuid, String fileUuid, @Nullable String dataType, @Nullable String lineHashes, int lineCount) { + db.executeInsert( + "FILE_SOURCES", + "PROJECT_UUID", projectUuid, + "FILE_UUID", fileUuid, + "LINE_HASHES", lineHashes, + "DATA_TYPE", dataType, + "LINE_COUNT", lineCount, + "CREATED_AT", 1_222_333L, + "UPDATED_AT", 1_222_333L); + db.commit(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImplTest.java new file mode 100644 index 00000000000..3886bfa344e --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImplTest.java @@ -0,0 +1,78 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.computation.task.projectanalysis.dbmigration; + +import java.util.List; +import java.util.stream.Collectors; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.ce.queue.CeTask; +import org.sonar.db.Database; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +public class ProjectAnalysisDataChangesImplTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + @Test + public void constructor_throws_IAE_if_argument_is_empty() { + ProjectAnalysisDataChange[] empty = new ProjectAnalysisDataChange[0]; + int expectedArraySize = ProjectAnalysisDataChangesImpl.getDataChangeClasses().size(); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Number of ProjectAnalysisDataChange instance available (0) is inconsistent with " + + "the number of declared ProjectAnalysisDataChange types (" + expectedArraySize + ")"); + + new ProjectAnalysisDataChangesImpl(empty); + } + + @Test + public void constructor_throws_ISE_if_an_instance_of_declared_class_is_missing() { + ProjectAnalysisDataChange[] wrongInstance = new ProjectAnalysisDataChange[] { + mock(ProjectAnalysisDataChange.class) + }; + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("Some of the ProjectAnalysisDataChange type declared have no instance in the container"); + + new ProjectAnalysisDataChangesImpl(wrongInstance); + + } + + @Test + public void getDataChanges_returns_instances_of_classes_in_order_defined_by_getDataChangeClasses() { + Database database = mock(Database.class); + CeTask ceTask = mock(CeTask.class); + ProjectAnalysisDataChangesImpl underTest = new ProjectAnalysisDataChangesImpl(new ProjectAnalysisDataChange[] { + new PopulateFileSourceLineCount(database, ceTask) + }); + + List dataChanges = underTest.getDataChanges(); + + List> dataChangeClasses = dataChanges + .stream() + .map(ProjectAnalysisDataChange::getClass) + .collect(Collectors.toList()); + assertThat(dataChangeClasses).isEqualTo(ProjectAnalysisDataChangesImpl.getDataChangeClasses()); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStepTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStepTest.java new file mode 100644 index 00000000000..4808484e5a8 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStepTest.java @@ -0,0 +1,103 @@ +/* + * SonarQube + * Copyright (C) 2009-2018 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.computation.task.projectanalysis.step; + +import com.google.common.collect.ImmutableList; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.Random; +import java.util.stream.IntStream; +import org.junit.Test; +import org.mockito.InOrder; +import org.mockito.Mockito; +import org.sonar.server.computation.task.projectanalysis.dbmigration.ProjectAnalysisDataChange; +import org.sonar.server.computation.task.projectanalysis.dbmigration.ProjectAnalysisDataChanges; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class DbMigrationsStepTest { + private ProjectAnalysisDataChanges projectAnalysisDataChanges = mock(ProjectAnalysisDataChanges.class); + + private DbMigrationsStep underTest = new DbMigrationsStep(projectAnalysisDataChanges); + + @Test + public void execute_has_no_effect_if_there_is_no_DataChange() { + underTest.execute(); + } + + @Test + public void execute_calls_execute_on_DataChange_instances_in_order_provided_by_ProjectAnalysisDataChanges() { + ProjectAnalysisDataChange[] dataChanges = IntStream.range(0, 5 + new Random().nextInt(5)) + .mapToObj(i -> mock(ProjectAnalysisDataChange.class)) + .toArray(ProjectAnalysisDataChange[]::new); + InOrder inOrder = Mockito.inOrder((Object[]) dataChanges); + when(projectAnalysisDataChanges.getDataChanges()).thenReturn(Arrays.asList(dataChanges)); + + underTest.execute(); + + Arrays.stream(dataChanges).forEach(t -> { + try { + inOrder.verify(t).execute(); + } catch (SQLException e) { + throw new RuntimeException("mock execute method throw an exception??!!??", e); + } + }); + } + + @Test + public void execute_stops_executing_and_throws_ISE_at_first_failing_DataChange() throws SQLException { + ProjectAnalysisDataChange okMock1 = mock(ProjectAnalysisDataChange.class); + ProjectAnalysisDataChange okMock2 = mock(ProjectAnalysisDataChange.class); + ProjectAnalysisDataChange failingMock1 = mock(ProjectAnalysisDataChange.class); + SQLException expected = new SQLException("Faiking DataChange throwing a SQLException"); + doThrow(expected).when(failingMock1).execute(); + ProjectAnalysisDataChange okMock3 = mock(ProjectAnalysisDataChange.class); + ProjectAnalysisDataChange failingMock2 = mock(ProjectAnalysisDataChange.class); + doThrow(new SQLException("Faiking another failing DataChange throwing a SQLException but which should never be thrown")) + .when(failingMock2) + .execute(); + ProjectAnalysisDataChange okMock4 = mock(ProjectAnalysisDataChange.class); + InOrder inOrder = Mockito.inOrder(okMock1, okMock2, failingMock1, okMock3, failingMock2, okMock4); + when(projectAnalysisDataChanges.getDataChanges()).thenReturn(ImmutableList.of( + okMock1, okMock2, failingMock1, okMock3, failingMock2, okMock4 + )); + + try { + underTest.execute(); + fail("A IllegalStateException should have been thrown"); + } catch (IllegalStateException e) { + assertThat(e) + .hasCause(expected); + inOrder.verify(okMock1).execute(); + inOrder.verify(okMock2).execute(); + inOrder.verify(failingMock1).execute(); + inOrder.verifyNoMoreInteractions(); + } + } + + @Test + public void verify_description() { + assertThat(underTest.getDescription()).isNotEmpty(); + } +} diff --git a/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest/file_sources.sql b/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest/file_sources.sql new file mode 100644 index 00000000000..bb379b9e112 --- /dev/null +++ b/server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest/file_sources.sql @@ -0,0 +1,17 @@ +CREATE TABLE "FILE_SOURCES" ( + "ID" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY (START WITH 1, INCREMENT BY 1), + "PROJECT_UUID" VARCHAR(50) NOT NULL, + "FILE_UUID" VARCHAR(50) NOT NULL, + "LINE_HASHES" CLOB, + "LINE_COUNT" INTEGER NOT NULL, + "BINARY_DATA" BLOB, + "DATA_TYPE" VARCHAR(20), + "DATA_HASH" VARCHAR(50), + "SRC_HASH" VARCHAR(50), + "REVISION" VARCHAR(100), + "CREATED_AT" BIGINT NOT NULL, + "UPDATED_AT" BIGINT NOT NULL +); +CREATE INDEX "FILE_SOURCES_PROJECT_UUID" ON "FILE_SOURCES" ("PROJECT_UUID"); +CREATE UNIQUE INDEX "FILE_SOURCES_UUID_TYPE" ON "FILE_SOURCES" ("FILE_UUID", "DATA_TYPE"); +CREATE INDEX "FILE_SOURCES_UPDATED_AT" ON "FILE_SOURCES" ("UPDATED_AT"); -- 2.39.5