]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10430 populate FILE_SOURCES.LINE_COUNT at beginning of analysis
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Mon, 23 Apr 2018 09:30:32 +0000 (11:30 +0200)
committerSonarTech <sonartech@sonarsource.com>
Mon, 28 May 2018 18:20:44 +0000 (20:20 +0200)
14 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModule.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCount.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChange.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChanges.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/dbmigration/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStep.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/step/ReportComputationSteps.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/DbMigrationModuleTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/dbmigration/ProjectAnalysisDataChangesImplTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/step/DbMigrationsStepTest.java [new file with mode: 0644]
server/sonar-server/src/test/resources/org/sonar/server/computation/task/projectanalysis/dbmigration/PopulateFileSourceLineCountTest/file_sources.sql [new file with mode: 0644]

index 948f56d7d329801fc31fec3379c3ffeb3eed0290..800583830a3a343ad52574b7c2940889ab90a143 100644 (file)
@@ -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 (file)
index 0000000..b62d965
--- /dev/null
@@ -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 (file)
index 0000000..a4fa562
--- /dev/null
@@ -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 (file)
index 0000000..e831843
--- /dev/null
@@ -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 (file)
index 0000000..c40b3da
--- /dev/null
@@ -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 (file)
index 0000000..83b3238
--- /dev/null
@@ -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 (file)
index 0000000..25c33db
--- /dev/null
@@ -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 (file)
index 0000000..0a44b82
--- /dev/null
@@ -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);
+    }
+  }
+
+}
index 852be83599eeeb775dd4cf80a69cf013c0c91721..e75c98dba7486df7baa293c2e99c6db9ed52aab1 100644 (file)
@@ -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 (file)
index 0000000..2928224
--- /dev/null
@@ -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 (file)
index 0000000..87ba32e
--- /dev/null
@@ -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 (file)
index 0000000..3886bfa
--- /dev/null
@@ -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 (file)
index 0000000..4808484
--- /dev/null
@@ -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 (file)
index 0000000..bb379b9
--- /dev/null
@@ -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");