aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java3
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModule.java30
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCount.java69
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChange.java32
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChanges.java29
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImpl.java65
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/package-info.java23
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStep.java52
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationSteps.java1
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModuleTest.java53
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest.java207
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImplTest.java78
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStepTest.java103
-rw-r--r--server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest/file_sources.sql17
14 files changed, 762 insertions, 0 deletions
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}.
+ * <p>
+ * {@link MigrationStep} execute during project report analysis should perform <strong>only data change operations</strong>.
+ */
+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<ProjectAnalysisDataChange> 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<Class<? extends ProjectAnalysisDataChange>> DATA_CHANGE_CLASSES_IN_ORDER_OF_EXECUTION = of(
+ PopulateFileSourceLineCount.class);
+ private final List<ProjectAnalysisDataChange> 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<? extends Class<? extends ProjectAnalysisDataChange>, 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<Class<? extends ProjectAnalysisDataChange>> getDataChangeClasses() {
+ return DATA_CHANGE_CLASSES_IN_ORDER_OF_EXECUTION;
+ }
+
+ @Override
+ public List<ProjectAnalysisDataChange> 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<Class<? extends ComputationStep>> 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<ProjectAnalysisDataChange> dataChanges = underTest.getDataChanges();
+
+ List<? extends Class<?>> 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");