Browse Source

SONAR-16370 compute issue hashes when processing scanner report

tags/9.5.0.56709
Lukasz Jarocki 2 years ago
parent
commit
7cb2cbffa1

+ 37
- 3
server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java View File

@@ -25,7 +25,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.regex.Pattern;
import javax.annotation.Nullable;
import org.apache.commons.codec.digest.DigestUtils;
import org.sonar.api.rule.RuleKey;
import org.sonar.api.rules.RuleType;
import org.sonar.api.utils.Duration;
@@ -37,6 +39,7 @@ import org.sonar.ce.task.projectanalysis.issue.commonrule.CommonRuleEngine;
import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter;
import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolder;
import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
import org.sonar.ce.task.projectanalysis.source.SourceLinesRepository;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Input;
import org.sonar.core.issue.tracking.LazyInput;
@@ -54,21 +57,24 @@ import static org.sonar.api.issue.Issue.STATUS_OPEN;
import static org.sonar.api.issue.Issue.STATUS_TO_REVIEW;

public class TrackerRawInputFactory {
private static final Pattern MATCH_ALL_WHITESPACES = Pattern.compile("\\s");
private static final long DEFAULT_EXTERNAL_ISSUE_EFFORT = 0L;
private final TreeRootHolder treeRootHolder;
private final BatchReportReader reportReader;
private final CommonRuleEngine commonRuleEngine;
private final IssueFilter issueFilter;
private final SourceLinesHashRepository sourceLinesHash;
private final SourceLinesRepository sourceLinesRepository;
private final RuleRepository ruleRepository;
private final ActiveRulesHolder activeRulesHolder;

public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader,
SourceLinesHashRepository sourceLinesHash, CommonRuleEngine commonRuleEngine, IssueFilter issueFilter, RuleRepository ruleRepository,
public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader, SourceLinesHashRepository sourceLinesHash,
SourceLinesRepository sourceLinesRepository, CommonRuleEngine commonRuleEngine, IssueFilter issueFilter, RuleRepository ruleRepository,
ActiveRulesHolder activeRulesHolder) {
this.treeRootHolder = treeRootHolder;
this.reportReader = reportReader;
this.sourceLinesHash = sourceLinesHash;
this.sourceLinesRepository = sourceLinesRepository;
this.commonRuleEngine = commonRuleEngine;
this.issueFilter = issueFilter;
this.ruleRepository = ruleRepository;
@@ -185,7 +191,9 @@ public class TrackerRawInputFactory {
}
DbIssues.Locations.Builder dbLocationsBuilder = DbIssues.Locations.newBuilder();
if (reportIssue.hasTextRange()) {
dbLocationsBuilder.setTextRange(convertTextRange(reportIssue.getTextRange()));
DbCommons.TextRange.Builder textRange = convertTextRange(reportIssue.getTextRange());
dbLocationsBuilder.setTextRange(textRange);
dbLocationsBuilder.setChecksum(calculateLocationHash(textRange));
}
for (ScannerReport.Flow flow : reportIssue.getFlowList()) {
if (flow.getLocationCount() > 0) {
@@ -294,10 +302,36 @@ public class TrackerRawInputFactory {
ScannerReport.TextRange sourceRange = source.getTextRange();
DbCommons.TextRange.Builder targetRange = convertTextRange(sourceRange);
target.setTextRange(targetRange);
target.setChecksum(calculateLocationHash(targetRange));
}
return Optional.of(target.build());
}

private String calculateLocationHash(DbCommons.TextRange.Builder textRange) {
try (CloseableIterator<String> linesIterator = sourceLinesRepository.readLines(component)) {
StringBuilder toHash = new StringBuilder();
int lineNo = 1;
while (linesIterator.hasNext()) {
String line = linesIterator.next();
if (lineNo == textRange.getStartLine() && lineNo == textRange.getEndLine()) {
toHash.append(line, textRange.getStartOffset(), textRange.getEndOffset());
} else if (lineNo == textRange.getStartLine()) {
toHash.append(line, textRange.getStartOffset(), line.length());
} else if (lineNo > textRange.getStartLine() && lineNo < textRange.getEndLine()) {
toHash.append(line);
} else if (lineNo == textRange.getEndLine()) {
toHash.append(line, 0, textRange.getEndOffset());
} else if (lineNo > textRange.getEndLine()) {
break;
}
lineNo++;
}
String issueContentWithoutWhitespaces = MATCH_ALL_WHITESPACES.matcher(toHash.toString()).replaceAll("");
return DigestUtils.md5Hex(issueContentWithoutWhitespaces);
}

}

private DbCommons.TextRange.Builder convertTextRange(ScannerReport.TextRange sourceRange) {
DbCommons.TextRange.Builder targetRange = DbCommons.TextRange.newBuilder();
targetRange.setStartLine(sourceRange.getStartLine());

+ 3
- 1
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java View File

@@ -49,6 +49,7 @@ import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderRule;
import org.sonar.ce.task.projectanalysis.qualityprofile.AlwaysActiveRulesHolderImpl;
import org.sonar.ce.task.projectanalysis.source.NewLinesRepository;
import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
import org.sonar.ce.task.projectanalysis.source.SourceLinesRepository;
import org.sonar.ce.task.projectanalysis.source.SourceLinesRepositoryRule;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.FieldDiffs;
@@ -126,6 +127,7 @@ public class IntegrateIssuesVisitorTest {
private final SiblingsIssueMerger issueStatusCopier = mock(SiblingsIssueMerger.class);
private final ReferenceBranchComponentUuids referenceBranchComponentUuids = mock(ReferenceBranchComponentUuids.class);
private final SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class);
private final SourceLinesRepository sourceLinesRepository = mock(SourceLinesRepository.class);
private final NewLinesRepository newLinesRepository = mock(NewLinesRepository.class);
private TargetBranchComponentUuids targetBranchComponentUuids = mock(TargetBranchComponentUuids.class);
private ArgumentCaptor<DefaultIssue> defaultIssueCaptor;
@@ -149,7 +151,7 @@ public class IntegrateIssuesVisitorTest {
when(movedFilesRepository.getOriginalFile(any(Component.class))).thenReturn(Optional.empty());

DbClient dbClient = dbTester.getDbClient();
TrackerRawInputFactory rawInputFactory = new TrackerRawInputFactory(treeRootHolder, reportReader, sourceLinesHash, new CommonRuleEngineImpl(),
TrackerRawInputFactory rawInputFactory = new TrackerRawInputFactory(treeRootHolder, reportReader, sourceLinesHash, sourceLinesRepository, new CommonRuleEngineImpl(),
issueFilter, ruleRepositoryRule, activeRulesHolder);
TrackerBaseInputFactory baseInputFactory = new TrackerBaseInputFactory(issuesLoader, dbClient, movedFilesRepository, mock(ReportModulesPath.class), analysisMetadataHolder,
new IssueFieldsSetter(), mock(ComponentsWithUnprocessedIssues.class));

+ 127
- 21
server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java View File

@@ -25,6 +25,10 @@ import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.stream.IntStream;
import org.apache.commons.codec.digest.DigestUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -41,8 +45,10 @@ import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter;
import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRule;
import org.sonar.ce.task.projectanalysis.qualityprofile.ActiveRulesHolderRule;
import org.sonar.ce.task.projectanalysis.source.SourceLinesHashRepository;
import org.sonar.ce.task.projectanalysis.source.SourceLinesRepository;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.core.issue.tracking.Input;
import org.sonar.core.util.CloseableIterator;
import org.sonar.db.protobuf.DbIssues;
import org.sonar.scanner.protocol.Constants;
import org.sonar.scanner.protocol.output.ScannerReport;
@@ -66,13 +72,10 @@ public class TrackerRawInputFactoryTest {

private static final String FILE_UUID = "fake_uuid";
private static final String ANOTHER_FILE_UUID = "another_fake_uuid";
private static int FILE_REF = 2;
private static int NOT_IN_REPORT_FILE_REF = 3;
private static int ANOTHER_FILE_REF = 4;

private static ReportComponent FILE = ReportComponent.builder(Component.Type.FILE, FILE_REF).setUuid(FILE_UUID).build();
private static ReportComponent ANOTHER_FILE = ReportComponent.builder(Component.Type.FILE, ANOTHER_FILE_REF).setUuid(ANOTHER_FILE_UUID).build();
private static ReportComponent PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1).addChildren(FILE, ANOTHER_FILE).build();
private static final String EXAMPLE_LINE_OF_CODE_FORMAT = "int example = line + of + code + %d; ";
private static final int FILE_REF = 2;
private static final int NOT_IN_REPORT_FILE_REF = 3;
private static final int ANOTHER_FILE_REF = 4;

@Rule
public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT);
@@ -83,11 +86,24 @@ public class TrackerRawInputFactoryTest {
@Rule
public RuleRepositoryRule ruleRepository = new RuleRepositoryRule();

private SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class);
private CommonRuleEngine commonRuleEngine = mock(CommonRuleEngine.class);
private IssueFilter issueFilter = mock(IssueFilter.class);
private TrackerRawInputFactory underTest = new TrackerRawInputFactory(treeRootHolder, reportReader, sourceLinesHash,
commonRuleEngine, issueFilter, ruleRepository, activeRulesHolder);
private static final ReportComponent FILE = ReportComponent.builder(Component.Type.FILE, FILE_REF).setUuid(FILE_UUID).build();
private static final ReportComponent ANOTHER_FILE = ReportComponent.builder(Component.Type.FILE, ANOTHER_FILE_REF).setUuid(ANOTHER_FILE_UUID).build();
private static final ReportComponent PROJECT = ReportComponent.builder(Component.Type.PROJECT, 1).addChildren(FILE, ANOTHER_FILE).build();

private final SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class);
private final SourceLinesRepository sourceLinesRepository = mock(SourceLinesRepository.class);
private final CommonRuleEngine commonRuleEngine = mock(CommonRuleEngine.class);
private final IssueFilter issueFilter = mock(IssueFilter.class);
private final TrackerRawInputFactory underTest = new TrackerRawInputFactory(treeRootHolder, reportReader, sourceLinesHash,
sourceLinesRepository, commonRuleEngine, issueFilter, ruleRepository, activeRulesHolder);

@Before
public void before() {
Iterator<String> stringIterator = IntStream.rangeClosed(1, 9)
.mapToObj(i -> String.format(EXAMPLE_LINE_OF_CODE_FORMAT, i))
.iterator();
when(sourceLinesRepository.readLines(any())).thenReturn(CloseableIterator.from(stringIterator));
}

@Test
public void load_source_hash_sequences() {
@@ -118,7 +134,7 @@ public class TrackerRawInputFactoryTest {

when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setTextRange(TextRange.newBuilder().setStartLine(2).build())
.setTextRange(newTextRange(2))
.setMsg("the message")
.setRuleRepository(ruleKey.repository())
.setRuleKey(ruleKey.rule())
@@ -146,6 +162,80 @@ public class TrackerRawInputFactoryTest {
assertThat(issue.tags()).isEmpty();
assertInitializedIssue(issue);
assertThat(issue.effort()).isNull();

assertLocationHashIsMadeOf(input, "intexample=line+of+code+2;");
}

@Test
public void calculateLocationHash_givenIssueOn3Lines_calculateHashOn3Lines() {
RuleKey ruleKey = RuleKey.of("java", "S001");
markRuleAsActive(ruleKey);
when(issueFilter.accept(any(), eq(FILE))).thenReturn(true);

ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setTextRange(TextRange.newBuilder()
.setStartLine(1)
.setEndLine(3)
.setStartOffset(0)
.setEndOffset(EXAMPLE_LINE_OF_CODE_FORMAT.length() - 1)
.build())
.setMsg("the message")
.setRuleRepository(ruleKey.repository())
.setRuleKey(ruleKey.rule())
.build();
reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));

Input<DefaultIssue> input = underTest.create(FILE);

assertLocationHashIsMadeOf(input, "intexample=line+of+code+1;intexample=line+of+code+2;intexample=line+of+code+3;");
}

@Test
public void calculateLocationHash_givenIssuePartiallyOn1Line_calculateHashOnAPartOfLine() {
RuleKey ruleKey = RuleKey.of("java", "S001");
markRuleAsActive(ruleKey);
when(issueFilter.accept(any(), eq(FILE))).thenReturn(true);

ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setTextRange(TextRange.newBuilder()
.setStartLine(1)
.setEndLine(1)
.setStartOffset(13)
.setEndOffset(EXAMPLE_LINE_OF_CODE_FORMAT.length() - 1)
.build())
.setMsg("the message")
.setRuleRepository(ruleKey.repository())
.setRuleKey(ruleKey.rule())
.build();
reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));

Input<DefaultIssue> input = underTest.create(FILE);

assertLocationHashIsMadeOf(input, "line+of+code+1;");
}

@Test
public void calculateLocationHash_givenIssuePartiallyOn1LineAndPartiallyOnThirdLine_calculateHashAccordingly() {
RuleKey ruleKey = RuleKey.of("java", "S001");
markRuleAsActive(ruleKey);
when(issueFilter.accept(any(), eq(FILE))).thenReturn(true);

ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setTextRange(TextRange.newBuilder()
.setStartLine(1)
.setEndLine(3)
.setStartOffset(13)
.setEndOffset(11)
.build())
.setMsg("the message")
.setRuleRepository(ruleKey.repository())
.setRuleKey(ruleKey.rule())
.build();
reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));

Input<DefaultIssue> input = underTest.create(FILE);

assertLocationHashIsMadeOf(input, "line+of+code+1;intexample=line+of+code+2;intexample");
}

@Test
@@ -184,7 +274,7 @@ public class TrackerRawInputFactoryTest {

when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setTextRange(TextRange.newBuilder().setStartLine(2).build())
.setTextRange(newTextRange(2))
.setMsg("the message")
.setRuleRepository(ruleKey.repository())
.setRuleKey(ruleKey.rule())
@@ -194,15 +284,15 @@ public class TrackerRawInputFactoryTest {
.addLocation(ScannerReport.IssueLocation.newBuilder()
.setComponentRef(FILE_REF)
.setMsg("Secondary location in same file")
.setTextRange(TextRange.newBuilder().setStartLine(2).build()))
.setTextRange(newTextRange(2)))
.addLocation(ScannerReport.IssueLocation.newBuilder()
.setComponentRef(NOT_IN_REPORT_FILE_REF)
.setMsg("Secondary location in a missing file")
.setTextRange(TextRange.newBuilder().setStartLine(3).build()))
.setTextRange(newTextRange(3)))
.addLocation(ScannerReport.IssueLocation.newBuilder()
.setComponentRef(ANOTHER_FILE_REF)
.setMsg("Secondary location in another file")
.setTextRange(TextRange.newBuilder().setStartLine(3).build()))
.setTextRange(newTextRange(3)))
.build())
.build();
reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue));
@@ -226,7 +316,7 @@ public class TrackerRawInputFactoryTest {
public void load_external_issues_from_report(IssueType issueType, RuleType expectedRuleType, String expectedStatus) {
when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder()
.setTextRange(TextRange.newBuilder().setStartLine(2).build())
.setTextRange(newTextRange(2))
.setMsg("the message")
.setEngineId("eslint")
.setRuleId("S001")
@@ -270,7 +360,7 @@ public class TrackerRawInputFactoryTest {
public void load_external_issues_from_report_with_default_effort(IssueType issueType, RuleType expectedRuleType, String expectedStatus) {
when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.ExternalIssue reportIssue = ScannerReport.ExternalIssue.newBuilder()
.setTextRange(TextRange.newBuilder().setStartLine(2).build())
.setTextRange(newTextRange(2))
.setMsg("the message")
.setEngineId("eslint")
.setRuleId("S001")
@@ -305,7 +395,7 @@ public class TrackerRawInputFactoryTest {

when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setTextRange(TextRange.newBuilder().setStartLine(2).build())
.setTextRange(newTextRange(2))
.setMsg("the message")
.setRuleRepository(ruleKey.repository())
.setRuleKey(ruleKey.rule())
@@ -326,7 +416,7 @@ public class TrackerRawInputFactoryTest {
when(issueFilter.accept(any(), eq(FILE))).thenReturn(false);
when(sourceLinesHash.getLineHashesMatchingDBVersion(FILE)).thenReturn(Collections.singletonList("line"));
ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder()
.setTextRange(TextRange.newBuilder().setStartLine(2).build())
.setTextRange(newTextRange(2))
.setMsg("the message")
.setRuleRepository(ruleKey.repository())
.setRuleKey(ruleKey.rule())
@@ -419,5 +509,21 @@ public class TrackerRawInputFactoryTest {
ruleRepository.add(dumbRule);
}

private TextRange newTextRange(int issueOnLine) {
return TextRange.newBuilder()
.setStartLine(issueOnLine)
.setEndLine(issueOnLine)
.setStartOffset(0)
.setEndOffset(EXAMPLE_LINE_OF_CODE_FORMAT.length() - 1)
.build();
}

private void assertLocationHashIsMadeOf(Input<DefaultIssue> input, String stringToHash) {
DefaultIssue defaultIssue = Iterators.getOnlyElement(input.getIssues().iterator());
String expectedHash = DigestUtils.md5Hex(stringToHash);
DbIssues.Locations locations = defaultIssue.getLocations();

assertThat(locations.getChecksum()).isEqualTo(expectedHash);
}

}

+ 0
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/issue/IssueMapper.java View File

@@ -67,5 +67,4 @@ public interface IssueMapper {
Collection<IssueGroupDto> selectIssueGroupsByBaseComponent(
@Param("baseComponent") ComponentDto baseComponent,
@Param("leakPeriodBeginningDate") long leakPeriodBeginningDate);

}

+ 2
- 0
server/sonar-db-dao/src/main/protobuf/db-issues.proto View File

@@ -33,6 +33,7 @@ option optimize_for = SPEED;
message Locations {
optional sonarqube.db.commons.TextRange text_range = 1;
repeated Flow flow = 2;
optional string checksum = 3;
}

message Flow {
@@ -44,4 +45,5 @@ message Location {
// Only when component is a file. Can be empty for a file if this is an issue global to the file.
optional sonarqube.db.commons.TextRange text_range = 2;
optional string msg = 3;
optional string checksum = 4;
}

Loading…
Cancel
Save