From 7603603aec3c165e4739c8a74282114c49bb08f3 Mon Sep 17 00:00:00 2001 From: Lukasz Jarocki Date: Mon, 23 May 2022 15:12:50 +0200 Subject: [PATCH] SONAR-16370 computing new issues hashes. version takes into account issues that can be be on many components --- .../issue/TrackerRawInputFactory.java | 54 ++++- .../issue/IntegrateIssuesVisitorTest.java | 6 +- .../issue/TrackerRawInputFactoryTest.java | 203 ++++++++++++++++-- .../src/main/protobuf/db-issues.proto | 2 + 4 files changed, 239 insertions(+), 26 deletions(-) diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java index 988c778cce7..d77b4e64bb1 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactory.java @@ -25,10 +25,13 @@ 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; +import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.ce.task.projectanalysis.batch.BatchReportReader; import org.sonar.ce.task.projectanalysis.component.Component; @@ -37,6 +40,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 +58,25 @@ 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 Logger LOGGER = Loggers.get(TrackerRawInputFactory.class); + 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 +193,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, component)); } for (ScannerReport.Flow flow : reportIssue.getFlowList()) { if (flow.getLocationCount() > 0) { @@ -294,10 +304,48 @@ public class TrackerRawInputFactory { ScannerReport.TextRange sourceRange = source.getTextRange(); DbCommons.TextRange.Builder targetRange = convertTextRange(sourceRange); target.setTextRange(targetRange); + target.setChecksum(calculateLocationHash(targetRange, source.getComponentRef())); } return Optional.of(target.build()); } + private String calculateLocationHash(DbCommons.TextRange.Builder textRange, int componentRef) { + if (this.component.getReportAttributes().getRef() == null) { + LOGGER.warn("Line hash for one of the issues in component" + component.getName() + " will not be calculated"); + return ""; + } + if (componentRef == this.component.getReportAttributes().getRef()) { + return calculateLocationHash(textRange, this.component); + } + Component textRangeComponent = treeRootHolder.getComponentByRef(componentRef); + return calculateLocationHash(textRange, textRangeComponent); + } + + private String calculateLocationHash(DbCommons.TextRange.Builder textRange, Component component) { + try (CloseableIterator linesIterator = sourceLinesRepository.readLines(component)) { + StringBuilder toHash = new StringBuilder(); + int lineNumber = 1; + while (linesIterator.hasNext()) { + String line = linesIterator.next(); + if (lineNumber == textRange.getStartLine() && lineNumber == textRange.getEndLine()) { + toHash.append(line, textRange.getStartOffset(), textRange.getEndOffset()); + } else if (lineNumber == textRange.getStartLine()) { + toHash.append(line, textRange.getStartOffset(), line.length()); + } else if (lineNumber > textRange.getStartLine() && lineNumber < textRange.getEndLine()) { + toHash.append(line); + } else if (lineNumber == textRange.getEndLine()) { + toHash.append(line, 0, textRange.getEndOffset()); + } else if (lineNumber > textRange.getEndLine()) { + break; + } + lineNumber++; + } + 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()); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java index 986f6d1f9af..10fd7448c27 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/IntegrateIssuesVisitorTest.java @@ -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; @@ -128,6 +129,7 @@ public class IntegrateIssuesVisitorTest { private final SourceLinesHashRepository sourceLinesHash = mock(SourceLinesHashRepository.class); private final NewLinesRepository newLinesRepository = mock(NewLinesRepository.class); private TargetBranchComponentUuids targetBranchComponentUuids = mock(TargetBranchComponentUuids.class); + private final SourceLinesRepository sourceLinesRepository = mock(SourceLinesRepository.class); private ArgumentCaptor defaultIssueCaptor; private final ComponentIssuesLoader issuesLoader = new ComponentIssuesLoader(dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule, new MapSettings().asConfig(), @@ -149,8 +151,8 @@ 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(), - issueFilter, ruleRepositoryRule, activeRulesHolder); + 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)); TrackerTargetBranchInputFactory targetInputFactory = new TrackerTargetBranchInputFactory(issuesLoader, targetBranchComponentUuids, dbClient); diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java index 2547336bd8f..f823bebd098 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/TrackerRawInputFactoryTest.java @@ -25,6 +25,11 @@ 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.List; +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 +46,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 +73,12 @@ 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 String LINE_IN_THE_MAIN_FILE = "String string = 'line-in-the-main-file';"; + private static final String LINE_IN_ANOTHER_FILE = "String string = 'line-in-the-another-file';"; + 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 +89,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 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 +137,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 +165,123 @@ 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 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 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 input = underTest.create(FILE); + + assertLocationHashIsMadeOf(input, "line+of+code+1;intexample=line+of+code+2;intexample"); + } + + @Test + public void calculateLocationHash_givenIssueOn2Components_calculateHashesByReading2Files() { + when(sourceLinesRepository.readLines(any())).thenReturn( + newOneLineIterator(LINE_IN_THE_MAIN_FILE), + newOneLineIterator(LINE_IN_THE_MAIN_FILE), + newOneLineIterator(LINE_IN_ANOTHER_FILE)); + RuleKey ruleKey = RuleKey.of("java", "S001"); + markRuleAsActive(ruleKey); + when(issueFilter.accept(any(), eq(FILE))).thenReturn(true); + + ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder() + .setTextRange(newTextRange(1, LINE_IN_THE_MAIN_FILE.length())) + .setMsg("the message") + .setRuleRepository(ruleKey.repository()) + .setRuleKey(ruleKey.rule()) + .setSeverity(Constants.Severity.BLOCKER) + .setGap(3.14) + .addFlow(ScannerReport.Flow.newBuilder() + .addLocation(ScannerReport.IssueLocation.newBuilder() + .setComponentRef(FILE_REF) + .setMsg("Secondary location in same file") + .setTextRange(newTextRange(1, LINE_IN_THE_MAIN_FILE.length()))) + .addLocation(ScannerReport.IssueLocation.newBuilder() + .setComponentRef(ANOTHER_FILE_REF) + .setMsg("Secondary location in other file") + .setTextRange(newTextRange(1, LINE_IN_ANOTHER_FILE.length()))) + .build()) + .build(); + reportReader.putIssues(FILE.getReportAttributes().getRef(), singletonList(reportIssue)); + + Input input = underTest.create(FILE); + DefaultIssue issue = Iterators.getOnlyElement(input.getIssues().iterator()); + + DbIssues.Locations locations = issue.getLocations(); + + assertThat(locations.getFlow(0).getLocation(0).getChecksum()).isEqualTo(DigestUtils.md5Hex("Stringstring='line-in-the-main-file';")); + assertThat(locations.getFlow(0).getLocation(1).getChecksum()).isEqualTo(DigestUtils.md5Hex("Stringstring='line-in-the-another-file';")); + } + + private CloseableIterator newOneLineIterator(String lineContent) { + return CloseableIterator.from(List.of(lineContent).iterator()); } @Test @@ -184,7 +320,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 +330,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 +362,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 +406,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 +441,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 +462,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 +555,30 @@ public class TrackerRawInputFactoryTest { ruleRepository.add(dumbRule); } + private TextRange newTextRange(int issueOnLine, int endOffset) { + return TextRange.newBuilder() + .setStartLine(issueOnLine) + .setEndLine(issueOnLine) + .setStartOffset(0) + .setEndOffset(endOffset) + .build(); + } + + 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 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); + } } diff --git a/server/sonar-db-dao/src/main/protobuf/db-issues.proto b/server/sonar-db-dao/src/main/protobuf/db-issues.proto index c57edc7b0e7..6b64ce30f59 100644 --- a/server/sonar-db-dao/src/main/protobuf/db-issues.proto +++ b/server/sonar-db-dao/src/main/protobuf/db-issues.proto @@ -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; } -- 2.39.5