]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9694 Consider all issue's locations when backdating
authorJulien HENRY <julien.henry@sonarsource.com>
Thu, 10 Aug 2017 08:32:58 +0000 (10:32 +0200)
committerJulien HENRY <julien.henry@sonarsource.com>
Thu, 7 Sep 2017 06:33:31 +0000 (08:33 +0200)
server/sonar-server/src/main/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculator.java
server/sonar-server/src/test/java/org/sonar/server/computation/task/projectanalysis/issue/IssueCreationDateCalculatorTest.java

index 92d4c4b9015d0aa35db7dd4c214c87425c957eac..873cd23b3af329dcb47f812695650e46f405f924 100644 (file)
 package org.sonar.server.computation.task.projectanalysis.issue;
 
 import java.time.format.DateTimeFormatter;
+import java.util.Comparator;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.Optional;
+import java.util.Set;
 import java.util.function.Supplier;
+import java.util.stream.IntStream;
 import org.sonar.api.utils.DateUtils;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.issue.DefaultIssue;
 import org.sonar.core.issue.IssueChangeContext;
+import org.sonar.db.protobuf.DbCommons.TextRange;
+import org.sonar.db.protobuf.DbIssues;
+import org.sonar.db.protobuf.DbIssues.Flow;
+import org.sonar.db.protobuf.DbIssues.Location;
 import org.sonar.server.computation.task.projectanalysis.analysis.Analysis;
 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
 import org.sonar.server.computation.task.projectanalysis.analysis.ScannerPlugin;
@@ -122,11 +130,21 @@ public class IssueCreationDateCalculator extends IssueVisitor {
   }
 
   private static Optional<Changeset> getChangeset(ScmInfo scmInfo, DefaultIssue issue) {
-    Integer line = issue.getLine();
-    if (line != null) {
-      Changeset changesetForLine = scmInfo.getChangesetForLine(line);
-      if (changesetForLine != null) {
-        return Optional.of(changesetForLine);
+    Set<Integer> involvedLines = new HashSet<>();
+    DbIssues.Locations locations = issue.getLocations();
+    if (locations != null) {
+      if (locations.hasTextRange()) {
+        addLines(involvedLines, locations.getTextRange());
+      }
+      for (Flow f : locations.getFlowList()) {
+        for (Location l : f.getLocationList()) {
+          addLines(involvedLines, l.getTextRange());
+        }
+      }
+      if (!involvedLines.isEmpty()) {
+        return involvedLines.stream()
+          .map(scmInfo::getChangesetForLine)
+          .max(Comparator.comparingLong(Changeset::getDate));
       }
     }
 
@@ -138,6 +156,10 @@ public class IssueCreationDateCalculator extends IssueVisitor {
     return Optional.empty();
   }
 
+  private static void addLines(Set<Integer> involvedLines, TextRange range) {
+    IntStream.rangeClosed(range.getStartLine(), range.getEndLine()).forEach(involvedLines::add);
+  }
+
   private static Date getChangeDate(Changeset changesetForLine) {
     return DateUtils.longToDate(changesetForLine.getDate());
   }
index 26103d3462dab50cbfd8bdb3e7688381f078c2d3..f0e84c58a061b2fa803962324afc070877886195 100644 (file)
@@ -27,6 +27,11 @@ import org.junit.Before;
 import org.junit.Test;
 import org.sonar.api.rule.RuleKey;
 import org.sonar.core.issue.DefaultIssue;
+import org.sonar.db.protobuf.DbCommons.TextRange;
+import org.sonar.db.protobuf.DbIssues;
+import org.sonar.db.protobuf.DbIssues.Flow;
+import org.sonar.db.protobuf.DbIssues.Location;
+import org.sonar.db.protobuf.DbIssues.Locations.Builder;
 import org.sonar.server.computation.task.projectanalysis.analysis.Analysis;
 import org.sonar.server.computation.task.projectanalysis.analysis.AnalysisMetadataHolder;
 import org.sonar.server.computation.task.projectanalysis.analysis.ScannerPlugin;
@@ -60,6 +65,7 @@ public class IssueCreationDateCalculatorTest {
   private IssueCreationDateCalculator calculator;
   private Analysis baseAnalysis;
   private Map<String, ScannerPlugin> scannerPlugins;
+  private ScmInfo scmInfo;
 
   @Before
   public void before() {
@@ -218,6 +224,52 @@ public class IssueCreationDateCalculatorTest {
     assertChangeOfCreationDateTo(1200L);
   }
 
+  @Test
+  public void should_use_primary_location_when_backdating() {
+    currentAnalysisIs(3000L);
+
+    newIssue();
+    when(issue.getLocations()).thenReturn(DbIssues.Locations.newBuilder().setTextRange(range(2, 3)).build());
+    withScmAt(2, 1200L);
+    withScmAt(3, 1300L);
+
+    run();
+
+    assertChangeOfCreationDateTo(1300L);
+  }
+
+  @Test
+  public void should_use_flows_location_when_backdating() {
+    currentAnalysisIs(3000L);
+
+    newIssue();
+    Builder builder = DbIssues.Locations.newBuilder()
+      .setTextRange(range(2, 3));
+    Flow.Builder secondary = Flow.newBuilder().addLocation(Location.newBuilder().setTextRange(range(4, 5)));
+    builder.addFlow(secondary).build();
+    Flow.Builder flow = Flow.newBuilder()
+      .addLocation(Location.newBuilder().setTextRange(range(6, 7)))
+      .addLocation(Location.newBuilder().setTextRange(range(8, 9)));
+    builder.addFlow(flow).build();
+    when(issue.getLocations()).thenReturn(builder.build());
+    withScmAt(2, 1200L);
+    withScmAt(3, 1300L);
+    withScmAt(4, 1400L);
+    withScmAt(5, 1500L);
+    withScmAt(6, 1600L);
+    withScmAt(7, 1700L);
+    withScmAt(8, 1800L);
+    withScmAt(9, 1900L);
+
+    run();
+
+    assertChangeOfCreationDateTo(1900L);
+  }
+
+  private org.sonar.db.protobuf.DbCommons.TextRange.Builder range(int startLine, int endLine) {
+    return TextRange.newBuilder().setStartLine(startLine).setEndLine(endLine);
+  }
+
   private void previousAnalysisWas(long analysisDate) {
     when(analysisMetadataHolder.getBaseAnalysis())
       .thenReturn(baseAnalysis);
@@ -253,13 +305,25 @@ public class IssueCreationDateCalculatorTest {
   }
 
   private void withScm(long blame) {
-    ScmInfo scmInfo = mock(ScmInfo.class);
+    createMockScmInfo();
     Changeset changeset = Changeset.newChangesetBuilder().setDate(blame).setRevision("1").build();
-    when(scmInfoRepository.getScmInfo(component))
-      .thenReturn(Optional.of(scmInfo));
     when(scmInfo.getLatestChangeset()).thenReturn(changeset);
   }
 
+  private void createMockScmInfo() {
+    if (scmInfo == null) {
+      scmInfo = mock(ScmInfo.class);
+      when(scmInfoRepository.getScmInfo(component))
+        .thenReturn(Optional.of(scmInfo));
+    }
+  }
+
+  private void withScmAt(int line, long blame) {
+    createMockScmInfo();
+    Changeset changeset = Changeset.newChangesetBuilder().setDate(blame).setRevision("1").build();
+    when(scmInfo.getChangesetForLine(line)).thenReturn(changeset);
+  }
+
   private void ruleCreatedAt(long createdAt) {
     when(activeRule.getCreatedAt()).thenReturn(createdAt);
   }