]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-11463 Remove path from scanner report and add module directories
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Mon, 26 Nov 2018 17:53:51 +0000 (11:53 -0600)
committersonartech <sonartech@sonarsource.com>
Wed, 16 Jan 2019 08:43:02 +0000 (09:43 +0100)
15 files changed:
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactory.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ReportModulesPath.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStep.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentTreeBuilderTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ComponentUuidFactoryTest.java
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ReportModulesPathTest.java [new file with mode: 0644]
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/BuildComponentTreeStepTest.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/report/MetadataPublisher.java
sonar-scanner-engine/src/main/java/org/sonar/scanner/scan/DefaultInputModuleHierarchy.java
sonar-scanner-engine/src/test/java/org/sonar/scanner/report/MetadataPublisherTest.java
sonar-scanner-protocol/src/main/java/org/sonar/scanner/protocol/viewer/ScannerReportViewerApp.java
sonar-scanner-protocol/src/main/protobuf/scanner_report.proto
sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportReaderTest.java
sonar-scanner-protocol/src/test/java/org/sonar/scanner/protocol/output/ScannerReportWriterTest.java

index b87e740492a8a93759e390b723d10bfb0a3d923c..3a23ea8cbe19e8c6b635a1ca978422068f2ba488 100644 (file)
  */
 package org.sonar.ce.task.projectanalysis.component;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.api.resources.Qualifiers;
@@ -36,11 +38,11 @@ import org.sonar.db.component.KeyWithUuidDto;
 public class ComponentUuidFactory {
   private final Map<String, String> uuidsByKey = new HashMap<>();
 
-  public ComponentUuidFactory(DbClient dbClient, DbSession dbSession, String rootKey) {
-    Map<String, String> modulePathsByUuid = loadModulePathsByUuid(dbClient, dbSession, rootKey);
+  public ComponentUuidFactory(DbClient dbClient, DbSession dbSession, String rootKey, Supplier<Map<String, String>> reportModulesPath) {
+    Map<String, String> modulePathsByUuid = loadModulePathsByUuid(dbClient, dbSession, rootKey, reportModulesPath);
 
     if (modulePathsByUuid.isEmpty()) {
-      // only contains root project
+      // only contains root project or we don't have relative paths for other modules anyway
       List<KeyWithUuidDto> keys = dbClient.componentDao().selectUuidsByKeyFromProjectKey(dbSession, rootKey);
       keys.forEach(dto -> uuidsByKey.put(dto.key(), dto.uuid()));
     } else {
@@ -57,33 +59,24 @@ public class ComponentUuidFactory {
     return dbClient.componentDao().selectComponentsWithModuleUuidFromProjectKey(dbSession, rootKey);
   }
 
-  private static Map<String, String> loadModulePathsByUuid(DbClient dbClient, DbSession dbSession, String rootKey) {
+  private static Map<String, String> loadModulePathsByUuid(DbClient dbClient, DbSession dbSession, String rootKey, Supplier<Map<String, String>> reportModulesPath) {
     List<ComponentDto> moduleDtos = dbClient.componentDao()
       .selectEnabledModulesFromProjectKey(dbSession, rootKey, false).stream()
       .filter(c -> Qualifiers.MODULE.equals(c.qualifier()))
       .collect(Collectors.toList());
 
-    Map<String, ComponentDto> dtoByUuid = moduleDtos.stream()
-      .collect(Collectors.toMap(ComponentDto::uuid, dto -> dto));
+    if (moduleDtos.isEmpty()) {
+      return Collections.emptyMap();
+    }
 
+    Map<String, String> pathByModuleKey = reportModulesPath.get();
     Map<String, String> modulePathByUuid = new HashMap<>();
-
     for (ComponentDto dto : moduleDtos) {
-      String modulePath = null;
-      ComponentDto currentDto = dto;
-      while (currentDto != null && currentDto.moduleUuid() != null) {
-        String path = currentDto.path();
-        if (modulePath == null) {
-          modulePath = path;
-        } else {
-          modulePath = path + "/" + modulePath;
-        }
-        currentDto = dtoByUuid.get(currentDto.moduleUuid());
+      String relativePath = pathByModuleKey.get(dto.getKey());
+      if (relativePath != null) {
+        modulePathByUuid.put(dto.uuid(), relativePath);
       }
-
-      modulePathByUuid.put(dto.uuid(), modulePath);
     }
-
     return modulePathByUuid;
   }
 
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ReportModulesPath.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/component/ReportModulesPath.java
new file mode 100644 (file)
index 0000000..57c8e8c
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ * 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.ce.task.projectanalysis.component;
+
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.function.Supplier;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
+import org.sonar.scanner.protocol.output.ScannerReport;
+
+public class ReportModulesPath implements Supplier<Map<String, String>> {
+  private final BatchReportReader reader;
+
+  public ReportModulesPath(BatchReportReader reader) {
+    this.reader = reader;
+  }
+
+  public Map<String, String> get() {
+    ScannerReport.Metadata metadata = reader.readMetadata();
+    Map<String, String> modulesProjectRelativePathByKey = metadata.getModulesProjectRelativePathByKeyMap();
+    if (modulesProjectRelativePathByKey.isEmpty()) {
+      return collectModulesPathFromHierarchy(metadata);
+    }
+    return modulesProjectRelativePathByKey;
+  }
+
+  /**
+   * This should only be needed if we receive a report of the previous version without the path per module explicitly set
+   * (due to blue/green deployment)
+   * Can be removed in any future version
+   */
+  private Map<String, String> collectModulesPathFromHierarchy(ScannerReport.Metadata metadata) {
+    ScannerReport.Component root = reader.readComponent(metadata.getRootComponentRef());
+    Map<String, String> modulesPathByKey = new LinkedHashMap<>();
+    LinkedList<Integer> queue = new LinkedList<>();
+    queue.addAll(root.getChildRefList());
+
+    while (!queue.isEmpty()) {
+      ScannerReport.Component component = reader.readComponent(queue.removeFirst());
+      if (component.getType() == ScannerReport.Component.ComponentType.MODULE) {
+        queue.addAll(component.getChildRefList());
+        modulesPathByKey.put(component.getKey(), component.getProjectRelativePath());
+      }
+    }
+
+    return modulesPathByKey;
+  }
+
+}
index 7f661f1cde325f7ce2dcd8463accacbe16dcb565..3d67ddb385f81199579b52deb09fc448e78b1a02 100644 (file)
@@ -35,6 +35,7 @@ import org.sonar.ce.task.projectanalysis.component.ConfigurationRepositoryImpl;
 import org.sonar.ce.task.projectanalysis.component.DbIdsRepositoryImpl;
 import org.sonar.ce.task.projectanalysis.component.DisabledComponentsHolderImpl;
 import org.sonar.ce.task.projectanalysis.component.MergeBranchComponentUuids;
+import org.sonar.ce.task.projectanalysis.component.ReportModulesPath;
 import org.sonar.ce.task.projectanalysis.component.ShortBranchComponentsWithIssues;
 import org.sonar.ce.task.projectanalysis.component.TreeRootHolderImpl;
 import org.sonar.ce.task.projectanalysis.dbmigration.DbMigrationModule;
@@ -179,7 +180,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop
       new ComputationTempFolderProvider(),
 
       DbMigrationModule.class,
-
+      ReportModulesPath.class,
       MetricModule.class,
 
       // holders
index 342e5cdbc8147b97f9dbbef83ad1b3d3f90119ce..b1511df95ab0f9b7cb6c275f919fd6d4b9fb9837 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.ce.task.projectanalysis.component.ComponentTreeBuilder;
 import org.sonar.ce.task.projectanalysis.component.ComponentUuidFactory;
 import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
 import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolder;
+import org.sonar.ce.task.projectanalysis.component.ReportModulesPath;
 import org.sonar.ce.task.projectanalysis.issue.IssueRelocationToRoot;
 import org.sonar.ce.task.step.ComputationStep;
 import org.sonar.db.DbClient;
@@ -49,14 +50,16 @@ public class BuildComponentTreeStep implements ComputationStep {
   private final MutableTreeRootHolder treeRootHolder;
   private final MutableAnalysisMetadataHolder analysisMetadataHolder;
   private final IssueRelocationToRoot issueRelocationToRoot;
+  private final ReportModulesPath reportModulesPath;
 
-  public BuildComponentTreeStep(DbClient dbClient, BatchReportReader reportReader,
-    MutableTreeRootHolder treeRootHolder, MutableAnalysisMetadataHolder analysisMetadataHolder, IssueRelocationToRoot issueRelocationToRoot) {
+  public BuildComponentTreeStep(DbClient dbClient, BatchReportReader reportReader, MutableTreeRootHolder treeRootHolder,
+    MutableAnalysisMetadataHolder analysisMetadataHolder, IssueRelocationToRoot issueRelocationToRoot, ReportModulesPath reportModulesPath) {
     this.dbClient = dbClient;
     this.reportReader = reportReader;
     this.treeRootHolder = treeRootHolder;
     this.analysisMetadataHolder = analysisMetadataHolder;
     this.issueRelocationToRoot = issueRelocationToRoot;
+    this.reportModulesPath = reportModulesPath;
   }
 
   @Override
@@ -70,12 +73,13 @@ public class BuildComponentTreeStep implements ComputationStep {
       ScannerReport.Component reportProject = reportReader.readComponent(analysisMetadataHolder.getRootComponentRef());
       ComponentKeyGenerator keyGenerator = loadKeyGenerator();
       ComponentKeyGenerator publicKeyGenerator = loadPublicKeyGenerator();
+      ScannerReport.Metadata metadata = reportReader.readMetadata();
 
       // root key of branch, not necessarily of project
       String rootKey = keyGenerator.generateKey(reportProject, null);
 
       // loads the UUIDs from database. If they don't exist, then generate new ones
-      ComponentUuidFactory componentUuidFactory = new ComponentUuidFactory(dbClient, dbSession, rootKey);
+      ComponentUuidFactory componentUuidFactory = new ComponentUuidFactory(dbClient, dbSession, rootKey, reportModulesPath);
 
       String rootUuid = componentUuidFactory.getOrCreateForKey(rootKey);
       SnapshotDto baseAnalysis = loadBaseAnalysis(dbSession, rootUuid);
@@ -86,7 +90,7 @@ public class BuildComponentTreeStep implements ComputationStep {
         analysisMetadataHolder.getProject(),
         analysisMetadataHolder.getBranch(),
         baseAnalysis, issueRelocationToRoot);
-      String relativePathFromScmRoot = reportReader.readMetadata().getRelativePathFromScmRoot();
+      String relativePathFromScmRoot = metadata.getRelativePathFromScmRoot();
 
       Component reportTreeRoot = builder.buildProject(reportProject, relativePathFromScmRoot);
 
index 9f4920dac21c24762571b44731cab707caa60430..00d22565e21a799dfa1ef8525b5fdd8ba19d3f48 100644 (file)
@@ -416,7 +416,7 @@ public class ComponentTreeBuilderTest {
     scannerComponentProvider.add(newBuilder()
       .setRef(2)
       .setType(DIRECTORY)
-      .setPath("/")
+      .setProjectRelativePath("/")
       .addChildRef(3));
     scannerComponentProvider.add(newBuilder()
       .setRef(3)
index 90ad37a15cbc3fbdd24c1f3ddd5e023691e2919e..3d84801d17a3ebf37ba58c1af4de76d24e8e3870 100644 (file)
@@ -19,6 +19,9 @@
  */
 package org.sonar.ce.task.projectanalysis.component;
 
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
 import org.junit.Rule;
 import org.junit.Test;
 import org.sonar.api.utils.System2;
@@ -27,19 +30,23 @@ import org.sonar.db.component.ComponentDto;
 import org.sonar.db.component.ComponentTesting;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
 
 public class ComponentUuidFactoryTest {
 
   @Rule
   public DbTester db = DbTester.create(System2.INSTANCE);
 
+  private ReportModulesPath reportModulesPath = mock(ReportModulesPath.class);
+
   @Test
   public void load_uuids_from_existing_components_in_db() {
     ComponentDto project = db.components().insertPrivateProject();
     ComponentDto module = db.components().insertComponent(ComponentTesting
-      .newModuleDto(project).setPath("module1"));
-
-    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey());
+      .newModuleDto(project));
+    when(reportModulesPath.get()).thenReturn(Collections.singletonMap(module.getKey(), "module1_path"));
+    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey(), reportModulesPath);
 
     assertThat(underTest.getOrCreateForKey(project.getDbKey())).isEqualTo(project.uuid());
     assertThat(underTest.getOrCreateForKey(module.getDbKey())).isNotEqualTo(module.uuid());
@@ -49,11 +56,9 @@ public class ComponentUuidFactoryTest {
   public void migrate_project_with_modules() {
     ComponentDto project = db.components().insertPrivateProject(dto -> dto.setDbKey("project"));
     ComponentDto module1 = db.components().insertComponent(ComponentTesting.newModuleDto(project)
-      .setDbKey("project:module1")
-      .setPath("module1_path"));
+      .setDbKey("project:module1"));
     ComponentDto module2 = db.components().insertComponent(ComponentTesting.newModuleDto(module1)
-      .setDbKey("project:module1:module2")
-      .setPath("module2_path"));
+      .setDbKey("project:module1:module2"));
     ComponentDto file1 = db.components().insertComponent(ComponentTesting.newFileDto(project)
       .setDbKey("project:file1")
       .setPath("file1_path"));
@@ -62,8 +67,11 @@ public class ComponentUuidFactoryTest {
       .setPath("file2_path"));
 
     assertThat(file2.moduleUuidPath()).isEqualTo("." + project.uuid() + "." + module1.uuid() + "." + module2.uuid() + ".");
-
-    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey());
+    Map<String, String> modulesRelativePaths = new HashMap<>();
+    modulesRelativePaths.put("project:module1", "module1_path");
+    modulesRelativePaths.put("project:module1:module2", "module1_path/module2_path");
+    when(reportModulesPath.get()).thenReturn(modulesRelativePaths);
+    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey(), reportModulesPath);
 
     // migrated files
     assertThat(underTest.getOrCreateForKey("project:file1_path")).isEqualTo(file1.uuid());
@@ -84,51 +92,23 @@ public class ComponentUuidFactoryTest {
     ComponentDto project = db.components().insertPrivateProject(dto -> dto.setDbKey("project"));
     ComponentDto module1 = db.components().insertComponent(ComponentTesting.newModuleDto(project)
       .setDbKey("project:module1")
-      .setEnabled(false)
-      .setPath("module1_path"));
+      .setEnabled(false));
     ComponentDto file1 = db.components().insertComponent(ComponentTesting.newFileDto(module1)
       .setDbKey("project:file1")
       .setEnabled(false)
       .setPath("file1_path"));
+    when(reportModulesPath.get()).thenReturn(Collections.singletonMap("project:module1", "module1_path"));
 
-    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey());
+    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey(), reportModulesPath);
 
     // migrated files
     assertThat(underTest.getOrCreateForKey("project:module1_path/file1_path")).isEqualTo(file1.uuid());
   }
 
-  @Test
-  public void migrate_project_having_modules_without_paths() {
-    ComponentDto project = db.components().insertPrivateProject(dto -> dto.setDbKey("project"));
-    ComponentDto module = db.components().insertComponent(ComponentTesting.newModuleDto(project)
-      .setDbKey("project:module")
-      .setPath(null));
-    ComponentDto file = db.components().insertComponent(ComponentTesting.newFileDto(module)
-      .setDbKey("project:module:file")
-      .setPath("file_path"));
-
-    assertThat(file.moduleUuidPath()).isEqualTo("." + project.uuid() + "." + module.uuid() + ".");
-
-    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), project.getDbKey());
-
-    // file will have this key since the module has a null path
-    assertThat(underTest.getOrCreateForKey("project:file_path")).isEqualTo(file.uuid());
-
-    // migrated module
-    // TODO!!
-    //assertThat(underTest.getOrCreateForKey("project:module")).isEqualTo(module.uuid());
-
-    // project remains the same
-    //assertThat(underTest.getOrCreateForKey(project.getDbKey())).isEqualTo(project.uuid());
-
-    // old keys with modules don't exist
-    assertThat(underTest.getOrCreateForKey(module.getDbKey())).isNotEqualTo(module.uuid());
-    assertThat(underTest.getOrCreateForKey(file.getDbKey())).isNotEqualTo(file.uuid());
-  }
-
   @Test
   public void generate_uuid_if_it_does_not_exist_in_db() {
-    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), "theProjectKey");
+    when(reportModulesPath.get()).thenReturn(Collections.emptyMap());
+    ComponentUuidFactory underTest = new ComponentUuidFactory(db.getDbClient(), db.getSession(), "theProjectKey", reportModulesPath);
 
     String generatedKey = underTest.getOrCreateForKey("foo");
     assertThat(generatedKey).isNotEmpty();
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ReportModulesPathTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/component/ReportModulesPathTest.java
new file mode 100644 (file)
index 0000000..760c98d
--- /dev/null
@@ -0,0 +1,89 @@
+/*
+ * 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.ce.task.projectanalysis.component;
+
+import java.util.Arrays;
+import java.util.Map;
+import javax.annotation.Nullable;
+import org.junit.Test;
+import org.sonar.ce.task.projectanalysis.batch.BatchReportReader;
+import org.sonar.scanner.protocol.output.ScannerReport;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class ReportModulesPathTest {
+  private BatchReportReader reader = mock(BatchReportReader.class);
+  private ReportModulesPath reportModulesPath = new ReportModulesPath(reader);
+  private ScannerReport.Component root = addComponent(1, "project", ScannerReport.Component.ComponentType.PROJECT, null, 2);
+
+  @Test
+  public void should_not_read_hierarchy_if_metadata_available() {
+    when(reader.readMetadata()).thenReturn(ScannerReport.Metadata.newBuilder()
+      .putModulesProjectRelativePathByKey("module1", "path1")
+      .setRootComponentRef(1)
+      .build());
+    Map<String, String> pathByModuleKey = reportModulesPath.get();
+
+    assertThat(pathByModuleKey).containsExactly(entry("module1", "path1"));
+    verify(reader).readMetadata();
+    verifyNoMoreInteractions(reader);
+  }
+
+  @Test
+  public void should_read_hierarchy_if_metadata_not_available() {
+    when(reader.readMetadata()).thenReturn(ScannerReport.Metadata.newBuilder().setRootComponentRef(1).build());
+    addComponent(2, "project:module1", ScannerReport.Component.ComponentType.MODULE, "path1", 3);
+    addComponent(3, "project:module1:module2", ScannerReport.Component.ComponentType.MODULE, "path1/path2", 4);
+    addComponent(4, "project:module1:module2:dir", ScannerReport.Component.ComponentType.DIRECTORY, "path1/path2/dir");
+
+    Map<String, String> pathByModuleKey = reportModulesPath.get();
+
+    assertThat(pathByModuleKey).containsOnly(
+      entry("project:module1", "path1"),
+      entry("project:module1:module2", "path1/path2"));
+    verify(reader).readMetadata();
+    verify(reader).readComponent(1);
+    verify(reader).readComponent(2);
+    verify(reader).readComponent(3);
+    verify(reader).readComponent(4);
+
+    verifyNoMoreInteractions(reader);
+  }
+
+  private ScannerReport.Component addComponent(int ref, String key, ScannerReport.Component.ComponentType type, @Nullable String path, Integer... children) {
+    ScannerReport.Component.Builder builder = ScannerReport.Component.newBuilder()
+      .setRef(ref)
+      .setKey(key)
+      .addAllChildRef(Arrays.asList(children))
+      .setType(type);
+
+    if (path != null) {
+      builder.setProjectRelativePath(path);
+    }
+    ScannerReport.Component component = builder.build();
+    when(reader.readComponent(ref)).thenReturn(component);
+    return component;
+  }
+}
index ba95929db6a894734dc121c33b3b9d0df5ff2c65..5d7b05fc7c5aa9eb18f797bba91d4b5f0804497c 100644 (file)
@@ -35,6 +35,7 @@ import org.sonar.ce.task.projectanalysis.batch.BatchReportReaderRule;
 import org.sonar.ce.task.projectanalysis.component.Component;
 import org.sonar.ce.task.projectanalysis.component.DefaultBranchImpl;
 import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule;
+import org.sonar.ce.task.projectanalysis.component.ReportModulesPath;
 import org.sonar.ce.task.projectanalysis.issue.IssueRelocationToRoot;
 import org.sonar.ce.task.step.TestComputationStepContext;
 import org.sonar.db.DbClient;
@@ -102,11 +103,12 @@ public class BuildComponentTreeStepTest {
   public MutableTreeRootHolderRule treeRootHolder = new MutableTreeRootHolderRule();
   @Rule
   public MutableAnalysisMetadataHolderRule analysisMetadataHolder = new MutableAnalysisMetadataHolderRule();
-
+  private ReportModulesPath reportModulesPath = new ReportModulesPath(reportReader);
   private IssueRelocationToRoot issueRelocationToRoot = mock(IssueRelocationToRoot.class);
 
   private DbClient dbClient = dbTester.getDbClient();
-  private BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder, issueRelocationToRoot);
+  private BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder,
+    issueRelocationToRoot, reportModulesPath);
 
   @Test(expected = NullPointerException.class)
   public void fails_if_root_component_does_not_exist_in_reportReader() {
@@ -202,7 +204,8 @@ public class BuildComponentTreeStepTest {
     reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, DIR_REF_1));
     reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, "module/" + REPORT_DIR_PATH_1, FILE_1_REF));
     reportReader.putComponent(componentWithPath(FILE_1_REF, FILE, "module/" + REPORT_FILE_PATH_1));
-
+    reportReader.setMetadata(ScannerReport.Metadata.newBuilder().putModulesProjectRelativePathByKey(REPORT_MODULE_KEY,
+      "module").build());
     underTest.execute(new TestComputationStepContext());
 
     verifyComponentByRef(ROOT_REF, REPORT_PROJECT_KEY, "ABCD");
@@ -221,7 +224,8 @@ public class BuildComponentTreeStepTest {
       .setAnalysisDate(ANALYSIS_DATE)
       .setProject(Project.from(newPrivateProjectDto(newOrganizationDto()).setDbKey(REPORT_PROJECT_KEY)))
       .setBranch(branch);
-    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder, issueRelocationToRoot);
+    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder,
+      issueRelocationToRoot, reportModulesPath);
     reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, MODULE_REF));
     reportReader.putComponent(component(MODULE_REF, MODULE, REPORT_MODULE_KEY, DIR_REF_1));
     reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, REPORT_DIR_PATH_1, FILE_1_REF));
@@ -248,7 +252,8 @@ public class BuildComponentTreeStepTest {
       .setAnalysisDate(ANALYSIS_DATE)
       .setProject(Project.from(newPrivateProjectDto(newOrganizationDto()).setDbKey(REPORT_PROJECT_KEY)))
       .setBranch(branch);
-    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder, issueRelocationToRoot);
+    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder,
+      issueRelocationToRoot, reportModulesPath);
     reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, MODULE_REF, LEAFLESS_MODULE_REF));
     reportReader.putComponent(component(MODULE_REF, MODULE, REPORT_MODULE_KEY, DIR_REF_1));
     reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, REPORT_DIR_PATH_1, FILE_1_REF));
@@ -288,7 +293,8 @@ public class BuildComponentTreeStepTest {
       .setAnalysisDate(ANALYSIS_DATE)
       .setProject(Project.from(newPrivateProjectDto(newOrganizationDto()).setDbKey(REPORT_PROJECT_KEY)))
       .setBranch(branch);
-    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder, issueRelocationToRoot);
+    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder,
+      issueRelocationToRoot, reportModulesPath);
     reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, MODULE_REF, LEAFLESS_MODULE_REF));
     reportReader.putComponent(component(MODULE_REF, MODULE, REPORT_MODULE_KEY, DIR_REF_1));
     reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, REPORT_DIR_PATH_1, FILE_1_REF));
@@ -335,7 +341,8 @@ public class BuildComponentTreeStepTest {
       .setAnalysisDate(ANALYSIS_DATE)
       .setProject(Project.from(projectDto))
       .setBranch(branch);
-    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder, issueRelocationToRoot);
+    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder,
+      issueRelocationToRoot, reportModulesPath);
     reportReader.putComponent(component(ROOT_REF, PROJECT, branchDto.getKey()));
 
     underTest.execute(new TestComputationStepContext());
@@ -350,7 +357,8 @@ public class BuildComponentTreeStepTest {
       .setAnalysisDate(ANALYSIS_DATE)
       .setProject(Project.from(newPrivateProjectDto(newOrganizationDto()).setDbKey(REPORT_PROJECT_KEY)))
       .setBranch(branch);
-    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder, issueRelocationToRoot);
+    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder,
+      issueRelocationToRoot, reportModulesPath);
     reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, MODULE_REF));
     reportReader.putComponent(component(MODULE_REF, MODULE, REPORT_MODULE_KEY, DIR_REF_1));
     reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, REPORT_DIR_PATH_1, FILE_1_REF));
@@ -370,7 +378,8 @@ public class BuildComponentTreeStepTest {
       .setAnalysisDate(ANALYSIS_DATE)
       .setProject(Project.from(newPrivateProjectDto(newOrganizationDto()).setDbKey(REPORT_PROJECT_KEY)))
       .setBranch(new DefaultBranchImpl("origin/feature"));
-    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder, issueRelocationToRoot);
+    BuildComponentTreeStep underTest = new BuildComponentTreeStep(dbClient, reportReader, treeRootHolder, analysisMetadataHolder,
+      issueRelocationToRoot, reportModulesPath);
     reportReader.putComponent(component(ROOT_REF, PROJECT, REPORT_PROJECT_KEY, MODULE_REF));
     reportReader.putComponent(component(MODULE_REF, MODULE, REPORT_MODULE_KEY, DIR_REF_1));
     reportReader.putComponent(componentWithPath(DIR_REF_1, DIRECTORY, REPORT_DIR_PATH_1, FILE_1_REF));
index 26ca46ace2c2170f72eed6be67cd38151f0e6261..2b36b3b5d28b907815e240914618f1cfd76da469 100644 (file)
@@ -21,11 +21,13 @@ package org.sonar.scanner.report;
 
 import java.io.File;
 import java.nio.file.Path;
+import java.util.LinkedList;
 import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.regex.Pattern;
 import javax.annotation.Nullable;
 import org.sonar.api.batch.fs.internal.AbstractProjectOrModule;
+import org.sonar.api.batch.fs.internal.DefaultInputModule;
 import org.sonar.api.batch.fs.internal.InputModuleHierarchy;
 import org.sonar.api.batch.scm.ScmProvider;
 import org.sonar.api.utils.log.Logger;
@@ -59,8 +61,8 @@ public class MetadataPublisher implements ReportPublisherStep {
   private final ScmConfiguration scmConfiguration;
 
   public MetadataPublisher(ProjectAnalysisInfo projectAnalysisInfo, InputModuleHierarchy moduleHierarchy, ScanProperties properties,
-                           QualityProfiles qProfiles, CpdSettings cpdSettings, ScannerPluginRepository pluginRepository, BranchConfiguration branchConfiguration,
-                           @Nullable ScmConfiguration scmConfiguration) {
+    QualityProfiles qProfiles, CpdSettings cpdSettings, ScannerPluginRepository pluginRepository, BranchConfiguration branchConfiguration,
+    @Nullable ScmConfiguration scmConfiguration) {
     this.projectAnalysisInfo = projectAnalysisInfo;
     this.moduleHierarchy = moduleHierarchy;
     this.properties = properties;
@@ -72,7 +74,7 @@ public class MetadataPublisher implements ReportPublisherStep {
   }
 
   public MetadataPublisher(ProjectAnalysisInfo projectAnalysisInfo, InputModuleHierarchy moduleHierarchy, ScanProperties properties,
-                           QualityProfiles qProfiles, CpdSettings cpdSettings, ScannerPluginRepository pluginRepository, BranchConfiguration branchConfiguration) {
+    QualityProfiles qProfiles, CpdSettings cpdSettings, ScannerPluginRepository pluginRepository, BranchConfiguration branchConfiguration) {
     this(projectAnalysisInfo, moduleHierarchy, properties, qProfiles, cpdSettings, pluginRepository, branchConfiguration, null);
   }
 
@@ -99,20 +101,37 @@ public class MetadataPublisher implements ReportPublisherStep {
     }
 
     for (QProfile qp : qProfiles.findAll()) {
-      builder.getMutableQprofilesPerLanguage().put(qp.getLanguage(), ScannerReport.Metadata.QProfile.newBuilder()
+      builder.putQprofilesPerLanguage(qp.getLanguage(), ScannerReport.Metadata.QProfile.newBuilder()
         .setKey(qp.getKey())
         .setLanguage(qp.getLanguage())
         .setName(qp.getName())
         .setRulesUpdatedAt(qp.getRulesUpdatedAt().getTime()).build());
     }
     for (Entry<String, ScannerPlugin> pluginEntry : pluginRepository.getPluginsByKey().entrySet()) {
-      builder.getMutablePluginsByKey().put(pluginEntry.getKey(), ScannerReport.Metadata.Plugin.newBuilder()
+      builder.putPluginsByKey(pluginEntry.getKey(), ScannerReport.Metadata.Plugin.newBuilder()
         .setKey(pluginEntry.getKey())
         .setUpdatedAt(pluginEntry.getValue().getUpdatedAt()).build());
     }
+
+    addModulesRelativePaths(builder);
+
     writer.writeMetadata(builder.build());
   }
 
+  private void addModulesRelativePaths(ScannerReport.Metadata.Builder builder) {
+    LinkedList<DefaultInputModule> queue = new LinkedList<>();
+    queue.add(moduleHierarchy.root());
+
+    while (!queue.isEmpty()) {
+      DefaultInputModule module = queue.removeFirst();
+      queue.addAll(moduleHierarchy.children(module));
+      String relativePath = moduleHierarchy.relativePath(module);
+      if (relativePath != null) {
+        builder.putModulesProjectRelativePathByKey(module.key(), relativePath);
+      }
+    }
+  }
+
   private void addScmInformation(ScannerReport.Metadata.Builder builder) {
     ScmProvider scmProvider = scmConfiguration.provider();
     if (scmProvider != null) {
index 8c37407023a4abfd2c3c1d6f640597c5fc5c66ed..3c9995269be9400778cc528c0bf151b558bfdd86 100644 (file)
@@ -84,11 +84,10 @@ public class DefaultInputModuleHierarchy implements InputModuleHierarchy {
   public String relativePath(DefaultInputModule module) {
     AbstractProjectOrModule parent = parent(module);
     if (parent == null) {
-      return null;
+      return "";
     }
-    DefaultInputModule inputModule = (DefaultInputModule) module;
     Path parentBaseDir = parent.getBaseDir();
-    Path moduleBaseDir = inputModule.getBaseDir();
+    Path moduleBaseDir = module.getBaseDir();
 
     return PathResolver.relativize(parentBaseDir, moduleBaseDir).orElse(null);
   }
index 5eda8a2114d1abbb247721754cb706f3cd066c2c..744cabca3ede5b743ce1d610057ed395b434639a 100644 (file)
@@ -22,8 +22,10 @@ package org.sonar.scanner.report;
 import com.google.common.collect.ImmutableMap;
 import java.io.File;
 import java.io.IOException;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Optional;
 import org.junit.Before;
@@ -81,14 +83,27 @@ public class MetadataPublisherTest {
     when(scmProvider.relativePathFromScmRoot(any(Path.class))).thenReturn(Paths.get("dummy/path"));
     when(scmProvider.revisionId(any(Path.class))).thenReturn("dummy-sha1");
 
-    createPublisher(ProjectDefinition.create().setKey("foo"));
+    createPublisher(ProjectDefinition.create());
     when(pluginRepository.getPluginsByKey()).thenReturn(emptyMap());
   }
 
   private void createPublisher(ProjectDefinition def) throws IOException {
-    rootModule = new DefaultInputModule(def.setBaseDir(temp.newFolder()).setWorkDir(temp.newFolder()), TestInputFileBuilder.nextBatchId());
+    Path rootBaseDir = temp.newFolder().toPath();
+    Path moduleBaseDir = rootBaseDir.resolve("moduleDir");
+    Files.createDirectory(moduleBaseDir);
+    rootModule = new DefaultInputModule(def
+      .setBaseDir(rootBaseDir.toFile())
+      .setKey("root")
+      .setWorkDir(temp.newFolder()), TestInputFileBuilder.nextBatchId());
     inputModuleHierarchy = mock(InputModuleHierarchy.class);
     when(inputModuleHierarchy.root()).thenReturn(rootModule);
+    DefaultInputModule child = new DefaultInputModule(ProjectDefinition.create()
+      .setKey("module")
+      .setBaseDir(moduleBaseDir.toFile())
+      .setWorkDir(temp.newFolder()), TestInputFileBuilder.nextBatchId());
+    when(inputModuleHierarchy.children(rootModule)).thenReturn(Collections.singletonList(child));
+    when(inputModuleHierarchy.relativePath(child)).thenReturn("modulePath");
+    when(inputModuleHierarchy.relativePath(rootModule)).thenReturn("");
     branches = mock(BranchConfiguration.class);
     scmConfiguration = mock(ScmConfiguration.class);
     when(scmConfiguration.provider()).thenReturn(scmProvider);
@@ -111,18 +126,18 @@ public class MetadataPublisherTest {
     ScannerReportReader reader = new ScannerReportReader(outputDir);
     ScannerReport.Metadata metadata = reader.readMetadata();
     assertThat(metadata.getAnalysisDate()).isEqualTo(1234567L);
-    assertThat(metadata.getProjectKey()).isEqualTo("foo");
-    assertThat(metadata.getProjectKey()).isEqualTo("foo");
-    assertThat(metadata.getQprofilesPerLanguage()).containsOnly(entry("java", org.sonar.scanner.protocol.output.ScannerReport.Metadata.QProfile.newBuilder()
+    assertThat(metadata.getProjectKey()).isEqualTo("root");
+    assertThat(metadata.getModulesProjectRelativePathByKeyMap()).containsOnly(entry("module", "modulePath"), entry("root", ""));
+    assertThat(metadata.getQprofilesPerLanguageMap()).containsOnly(entry("java", org.sonar.scanner.protocol.output.ScannerReport.Metadata.QProfile.newBuilder()
       .setKey("q1")
       .setName("Q1")
       .setLanguage("java")
       .setRulesUpdatedAt(date.getTime())
       .build()));
     assertThat(metadata.getPluginsByKey()).containsOnly(entry("java", org.sonar.scanner.protocol.output.ScannerReport.Metadata.Plugin.newBuilder()
-      .setKey("java")
-      .setUpdatedAt(12345)
-      .build()),
+        .setKey("java")
+        .setUpdatedAt(12345)
+        .build()),
       entry("php", org.sonar.scanner.protocol.output.ScannerReport.Metadata.Plugin.newBuilder()
         .setKey("php")
         .setUpdatedAt(45678)
@@ -146,7 +161,7 @@ public class MetadataPublisherTest {
     ScannerReportReader reader = new ScannerReportReader(outputDir);
     ScannerReport.Metadata metadata = reader.readMetadata();
     assertThat(metadata.getAnalysisDate()).isEqualTo(1234567L);
-    assertThat(metadata.getProjectKey()).isEqualTo("foo");
+    assertThat(metadata.getProjectKey()).isEqualTo("root");
     assertThat(metadata.getDeprecatedBranch()).isEqualTo("myBranch");
     assertThat(metadata.getCrossProjectDuplicationActivated()).isFalse();
   }
@@ -162,7 +177,7 @@ public class MetadataPublisherTest {
 
     ScannerReportReader reader = new ScannerReportReader(outputDir);
     ScannerReport.Metadata metadata = reader.readMetadata();
-    assertThat(properties.organizationKey()).isEqualTo(Optional.of("SonarSource"));
+    assertThat(metadata.getOrganizationKey()).isEqualTo("SonarSource");
   }
 
   @Test
index dca9835a5b1281a5d6b353ce4710859fdc64a444..0b2f132e1c328dceb44f758eaaeaeedc41338c5c 100644 (file)
@@ -229,7 +229,7 @@ public class ScannerReportViewerApp {
         return component.getName();
       case DIRECTORY:
       case FILE:
-        return component.getPath();
+        return component.getProjectRelativePath();
       default:
         throw new IllegalArgumentException("Unknow component type: " + component.getType());
     }
index f81097b5a92dc356355623cf5eb3b9e78d9ea9de..57f86f9a73a131987393e9ea74e2d395b92b17e2 100644 (file)
@@ -47,6 +47,7 @@ message Metadata {
   string scm_revision_id = 13;
 
   string pull_request_key = 14;
+  map<string, string> modules_project_relative_path_by_key = 15;
 
   message QProfile {
     string key = 1;
@@ -101,8 +102,6 @@ message ComponentLink {
 message Component {
   int32 ref = 1;
 
-  // Path relative to module base directory
-  string path = 2 [deprecated=true];
   string name = 3;
   ComponentType type = 4;
   bool is_test = 5;
index 33b14b37ac1217618e35824d0ef619a979580140..68fe5aa71886cc97ab904494311f99939f6514e0 100644 (file)
@@ -82,10 +82,10 @@ public class ScannerReportReaderTest {
     ScannerReportWriter writer = new ScannerReportWriter(dir);
     ScannerReport.Component.Builder component = ScannerReport.Component.newBuilder()
       .setRef(1)
-      .setPath("src/main/java/Foo.java");
+      .setProjectRelativePath("src/main/java/Foo.java");
     writer.writeComponent(component.build());
 
-    assertThat(underTest.readComponent(1).getPath()).isEqualTo("src/main/java/Foo.java");
+    assertThat(underTest.readComponent(1).getProjectRelativePath()).isEqualTo("src/main/java/Foo.java");
   }
 
   @Test(expected = IllegalStateException.class)
index 2b6d3e402f166f4ae7a67fbf19e1a466b585ba2b..ec33c74d38e5092502bb8f6fb2384d00f697e127 100644 (file)
@@ -80,7 +80,7 @@ public class ScannerReportWriterTest {
     ScannerReport.Component.Builder component = ScannerReport.Component.newBuilder()
       .setRef(1)
       .setLanguage("java")
-      .setPath("src/Foo.java")
+      .setProjectRelativePath("src/Foo.java")
       .setType(ComponentType.FILE)
       .setIsTest(false)
       .addChildRef(5)
@@ -142,13 +142,13 @@ public class ScannerReportWriterTest {
 
     // write data
     ScannerReport.AdHocRule rule = ScannerReport.AdHocRule.newBuilder()
-            .setEngineId("eslint")
-            .setRuleId("123")
-            .setName("Foo")
-            .setDescription("Description")
-            .setSeverity(Constants.Severity.BLOCKER)
-            .setType(ScannerReport.IssueType.BUG)
-            .build();
+      .setEngineId("eslint")
+      .setRuleId("123")
+      .setName("Foo")
+      .setDescription("Description")
+      .setSeverity(Constants.Severity.BLOCKER)
+      .setType(ScannerReport.IssueType.BUG)
+      .build();
     underTest.appendAdHocRule(rule);
 
     File file = underTest.getFileStructure().adHocRules();