From ad1024286aff2d16a3b3aa5f926613d596348f91 Mon Sep 17 00:00:00 2001 From: Aurelien Poscia Date: Fri, 19 Aug 2022 16:30:53 +0200 Subject: [PATCH] SONAR-17210 - Generate the SARIF report --- ...ProjectAnalysisTaskContainerPopulator.java | 4 +- .../ce/task/projectanalysis/issue/Rule.java | 4 + .../task/projectanalysis/issue/RuleImpl.java | 21 +++ .../issue/RuleRepositoryImpl.java | 10 ++ .../{pushevent => locations/flow}/Flow.java | 2 +- .../locations/flow/FlowGenerator.java | 85 ++++++++++ .../flow}/Location.java | 2 +- .../flow}/TextRange.java | 23 ++- .../pushevent/PushEventFactory.java | 30 +--- .../pushevent/TaintVulnerabilityRaised.java | 2 + .../task/projectanalysis/issue/DumbRule.java | 10 ++ .../locations/flow/FlowGeneratorTest.java | 160 ++++++++++++++++++ .../pushevent/PushEventFactoryTest.java | 4 +- 13 files changed, 329 insertions(+), 28 deletions(-) rename server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/{pushevent => locations/flow}/Flow.java (95%) create mode 100644 server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/FlowGenerator.java rename server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/{pushevent => locations/flow}/Location.java (96%) rename server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/{pushevent => locations/flow}/TextRange.java (71%) create mode 100644 server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/locations/flow/FlowGeneratorTest.java diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java index a90a512cd8a..674b1c3f9c4 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/container/ProjectAnalysisTaskContainerPopulator.java @@ -55,10 +55,10 @@ import org.sonar.ce.task.projectanalysis.filesystem.ComputationTempFolderProvide import org.sonar.ce.task.projectanalysis.issue.BaseIssuesLoader; import org.sonar.ce.task.projectanalysis.issue.CloseIssuesOnRemovedComponentsVisitor; import org.sonar.ce.task.projectanalysis.issue.ClosedIssuesInputFactory; -import org.sonar.ce.task.projectanalysis.issue.ComputeLocationHashesVisitor; import org.sonar.ce.task.projectanalysis.issue.ComponentIssuesLoader; import org.sonar.ce.task.projectanalysis.issue.ComponentIssuesRepositoryImpl; import org.sonar.ce.task.projectanalysis.issue.ComponentsWithUnprocessedIssues; +import org.sonar.ce.task.projectanalysis.issue.ComputeLocationHashesVisitor; import org.sonar.ce.task.projectanalysis.issue.DebtCalculator; import org.sonar.ce.task.projectanalysis.issue.DefaultAssignee; import org.sonar.ce.task.projectanalysis.issue.EffortAggregator; @@ -105,6 +105,7 @@ import org.sonar.ce.task.projectanalysis.issue.commonrule.SkippedTestRule; import org.sonar.ce.task.projectanalysis.issue.commonrule.TestErrorRule; import org.sonar.ce.task.projectanalysis.issue.filter.IssueFilter; import org.sonar.ce.task.projectanalysis.language.LanguageRepositoryImpl; +import org.sonar.ce.task.projectanalysis.locations.flow.FlowGenerator; import org.sonar.ce.task.projectanalysis.measure.MeasureComputersHolderImpl; import org.sonar.ce.task.projectanalysis.measure.MeasureComputersVisitor; import org.sonar.ce.task.projectanalysis.measure.MeasureRepositoryImpl; @@ -256,6 +257,7 @@ public final class ProjectAnalysisTaskContainerPopulator implements ContainerPop ComponentIssuesRepositoryImpl.class, IssueFilter.class, + FlowGenerator.class, // push events PushEventFactory.class, diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/Rule.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/Rule.java index 6d70363bb94..a6cf2028e90 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/Rule.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/Rule.java @@ -58,4 +58,8 @@ public interface Rule { @CheckForNull String getPluginKey(); + + String getDefaultRuleDescription(); + + String getSeverity(); } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleImpl.java index f8600a0f1d3..ee29fda5c08 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleImpl.java @@ -20,6 +20,7 @@ package org.sonar.ce.task.projectanalysis.issue; import com.google.common.base.MoreObjects; +import java.util.Optional; import java.util.Set; import javax.annotation.CheckForNull; import javax.annotation.Nullable; @@ -29,6 +30,7 @@ import org.sonar.api.rule.RuleStatus; import org.sonar.api.rules.RuleType; import org.sonar.api.server.debt.DebtRemediationFunction; import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction; +import org.sonar.db.rule.RuleDescriptionSectionDto; import org.sonar.db.rule.RuleDto; import static com.google.common.collect.Sets.union; @@ -47,6 +49,8 @@ public class RuleImpl implements Rule { private final String pluginKey; private final boolean isExternal; private final boolean isAdHoc; + private final String defaultRuleDescription; + private final String severity; public RuleImpl(RuleDto dto) { this.uuid = dto.getUuid(); @@ -60,6 +64,14 @@ public class RuleImpl implements Rule { this.pluginKey = dto.getPluginKey(); this.isExternal = dto.isExternal(); this.isAdHoc = dto.isAdHoc(); + this.defaultRuleDescription = getNonNullDefaultRuleDescription(dto); + this.severity = Optional.ofNullable(dto.getSeverityString()).orElse(dto.getAdHocSeverity()); + } + + private static String getNonNullDefaultRuleDescription(RuleDto dto) { + return Optional.ofNullable(dto.getDefaultRuleDescriptionSection()) + .map(RuleDescriptionSectionDto::getContent) + .orElse(""); } @Override @@ -109,6 +121,15 @@ public class RuleImpl implements Rule { return pluginKey; } + public String getDefaultRuleDescription() { + return defaultRuleDescription; + } + + @Override + public String getSeverity() { + return severity; + } + @Override public boolean equals(@Nullable Object o) { if (this == o) { diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java index dcf299a5295..4ada1d7bc2d 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/issue/RuleRepositoryImpl.java @@ -211,5 +211,15 @@ public class RuleRepositoryImpl implements RuleRepository { public String getPluginKey() { return null; } + + @Override + public String getDefaultRuleDescription() { + return addHocRule.getDescription(); + } + + @Override + public String getSeverity() { + return addHocRule.getSeverity(); + } } } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/Flow.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/Flow.java similarity index 95% rename from server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/Flow.java rename to server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/Flow.java index 75671df34fe..2b7e8447213 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/Flow.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/Flow.java @@ -17,7 +17,7 @@ * 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.pushevent; +package org.sonar.ce.task.projectanalysis.locations.flow; import java.util.List; diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/FlowGenerator.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/FlowGenerator.java new file mode 100644 index 00000000000..be25776a588 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/FlowGenerator.java @@ -0,0 +1,85 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.locations.flow; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import javax.annotation.Nullable; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; +import org.sonar.db.protobuf.DbCommons; +import org.sonar.db.protobuf.DbIssues; + +import static java.util.stream.Collectors.toCollection; + +public class FlowGenerator { + private final TreeRootHolder treeRootHolder; + + public FlowGenerator(TreeRootHolder treeRootHolder) { + this.treeRootHolder = treeRootHolder; + } + + public List convertFlows(String componentName, @Nullable DbIssues.Locations issueLocations) { + if (issueLocations == null) { + return Collections.emptyList(); + } + return issueLocations.getFlowList().stream() + .map(sourceFlow -> toFlow(componentName, sourceFlow)) + .collect(Collectors.toCollection(LinkedList::new)); + } + + private Flow toFlow(String componentName, DbIssues.Flow sourceFlow) { + Flow flow = new Flow(); + List locations = getFlowLocations(componentName, sourceFlow); + flow.setLocations(locations); + return flow; + } + + private List getFlowLocations(String componentName, DbIssues.Flow sourceFlow) { + return sourceFlow.getLocationList().stream() + .map(sourceLocation -> toLocation(componentName, sourceLocation)) + .collect(toCollection(LinkedList::new)); + } + + private Location toLocation(String componentName, DbIssues.Location sourceLocation) { + Location location = new Location(); + Component locationComponent = treeRootHolder.getComponentByUuid(sourceLocation.getComponentId()); + String filePath = Optional.ofNullable(locationComponent).map(Component::getName).orElse(componentName); + location.setFilePath(filePath); + location.setMessage(sourceLocation.getMsg()); + + TextRange textRange = getTextRange(sourceLocation.getTextRange(), sourceLocation.getChecksum()); + location.setTextRange(textRange); + return location; + } + + private static TextRange getTextRange(DbCommons.TextRange source, String checksum) { + TextRange textRange = new TextRange(); + textRange.setStartLine(source.getStartLine()); + textRange.setStartLineOffset(source.getStartOffset()); + textRange.setEndLine(source.getEndLine()); + textRange.setEndLineOffset(source.getEndOffset()); + textRange.setHash(checksum); + return textRange; + } +} diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/Location.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/Location.java similarity index 96% rename from server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/Location.java rename to server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/Location.java index 016dbfa71c9..bedae2197f6 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/Location.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/Location.java @@ -17,7 +17,7 @@ * 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.pushevent; +package org.sonar.ce.task.projectanalysis.locations.flow; public class Location { private String filePath; diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/TextRange.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/TextRange.java similarity index 71% rename from server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/TextRange.java rename to server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/TextRange.java index ce7ebc528dd..16e336cf764 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/TextRange.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/locations/flow/TextRange.java @@ -17,7 +17,9 @@ * 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.pushevent; +package org.sonar.ce.task.projectanalysis.locations.flow; + +import java.util.Objects; public class TextRange { private int startLine; @@ -69,4 +71,23 @@ public class TextRange { public void setHash(String hash) { this.hash = hash; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TextRange textRange = (TextRange) o; + return getStartLine() == textRange.getStartLine() && getStartLineOffset() == textRange.getStartLineOffset() && getEndLine() == textRange.getEndLine() + && getEndLineOffset() == textRange.getEndLineOffset() && Objects.equals(getHash(), textRange.getHash()); + } + + @Override + public int hashCode() { + return Objects.hash(getStartLine(), getStartLineOffset(), getEndLine(), getEndLineOffset(), getHash()); + } + } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/PushEventFactory.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/PushEventFactory.java index 35e68978082..18a8a17b8fb 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/PushEventFactory.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/PushEventFactory.java @@ -21,8 +21,6 @@ package org.sonar.ce.task.projectanalysis.pushevent; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import java.util.LinkedList; -import java.util.List; import java.util.Objects; import java.util.Optional; import org.jetbrains.annotations.NotNull; @@ -30,6 +28,9 @@ import org.sonar.api.ce.ComputeEngineSide; import org.sonar.ce.task.projectanalysis.analysis.AnalysisMetadataHolder; import org.sonar.ce.task.projectanalysis.component.Component; import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; +import org.sonar.ce.task.projectanalysis.locations.flow.FlowGenerator; +import org.sonar.ce.task.projectanalysis.locations.flow.Location; +import org.sonar.ce.task.projectanalysis.locations.flow.TextRange; import org.sonar.core.issue.DefaultIssue; import org.sonar.db.protobuf.DbCommons; import org.sonar.db.protobuf.DbIssues; @@ -38,7 +39,6 @@ import org.sonar.server.issue.TaintChecker; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; -import static java.util.Objects.requireNonNullElse; @ComputeEngineSide public class PushEventFactory { @@ -47,12 +47,14 @@ public class PushEventFactory { private final TreeRootHolder treeRootHolder; private final AnalysisMetadataHolder analysisMetadataHolder; private final TaintChecker taintChecker; + private final FlowGenerator flowGenerator; public PushEventFactory(TreeRootHolder treeRootHolder, - AnalysisMetadataHolder analysisMetadataHolder, TaintChecker taintChecker) { + AnalysisMetadataHolder analysisMetadataHolder, TaintChecker taintChecker, FlowGenerator flowGenerator) { this.treeRootHolder = treeRootHolder; this.analysisMetadataHolder = analysisMetadataHolder; this.taintChecker = taintChecker; + this.flowGenerator = flowGenerator; } public Optional raiseEventOnIssue(DefaultIssue currentIssue) { @@ -110,25 +112,7 @@ public class PushEventFactory { mainLocation.setTextRange(mainLocationTextRange); event.setMainLocation(mainLocation); - List flows = new LinkedList<>(); - for (DbIssues.Flow sourceFlow : issueLocations.getFlowList()) { - Flow flow = new Flow(); - List locations = new LinkedList<>(); - for (DbIssues.Location sourceLocation : sourceFlow.getLocationList()) { - Location location = new Location(); - var locationComponent = treeRootHolder.getComponentByUuid(sourceLocation.getComponentId()); - location.setFilePath(requireNonNullElse(locationComponent, component).getName()); - location.setMessage(sourceLocation.getMsg()); - - TextRange textRange = getTextRange(sourceLocation.getTextRange(), sourceLocation.getChecksum()); - location.setTextRange(textRange); - - locations.add(location); - } - flow.setLocations(locations); - flows.add(flow); - } - event.setFlows(flows); + event.setFlows(flowGenerator.convertFlows(component.getName(), issueLocations)); return event; } diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/TaintVulnerabilityRaised.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/TaintVulnerabilityRaised.java index e9ab37d368f..a3ddd139ef6 100644 --- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/TaintVulnerabilityRaised.java +++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/pushevent/TaintVulnerabilityRaised.java @@ -20,6 +20,8 @@ package org.sonar.ce.task.projectanalysis.pushevent; import java.util.List; +import org.sonar.ce.task.projectanalysis.locations.flow.Flow; +import org.sonar.ce.task.projectanalysis.locations.flow.Location; public class TaintVulnerabilityRaised { diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DumbRule.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DumbRule.java index 00a7e960a58..7ce05f22a7c 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DumbRule.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/issue/DumbRule.java @@ -95,6 +95,16 @@ public class DumbRule implements Rule { return pluginKey; } + @Override + public String getDefaultRuleDescription() { + return null; + } + + @Override + public String getSeverity() { + return null; + } + @Override public boolean isExternal() { return isExternal; diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/locations/flow/FlowGeneratorTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/locations/flow/FlowGeneratorTest.java new file mode 100644 index 00000000000..ddc27950ce2 --- /dev/null +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/locations/flow/FlowGeneratorTest.java @@ -0,0 +1,160 @@ +/* + * SonarQube + * Copyright (C) 2009-2022 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.locations.flow; + +import java.util.List; +import java.util.Map; +import org.apache.commons.lang.math.RandomUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.sonar.ce.task.projectanalysis.component.Component; +import org.sonar.ce.task.projectanalysis.component.TreeRootHolder; +import org.sonar.db.protobuf.DbCommons; +import org.sonar.db.protobuf.DbIssues; + +import static java.util.function.Function.identity; +import static java.util.stream.Collectors.toMap; +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class FlowGeneratorTest { + + private static final String COMPONENT_NAME = "test_comp"; + + @Mock + private TreeRootHolder treeRootHolder; + + @InjectMocks + private FlowGenerator flowGenerator; + + @Test + public void convertFlows_withNullDbLocations_returnsEmptyList() { + assertThat(flowGenerator.convertFlows(COMPONENT_NAME, null)).isEmpty(); + } + + @Test + public void convertFlows_withEmptyDbLocations_returnsEmptyList() { + DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder().build(); + assertThat(flowGenerator.convertFlows(COMPONENT_NAME, issueLocations)).isEmpty(); + } + + @Test + public void convertFlows_withSingleDbLocations_returnsCorrectFlow() { + DbIssues.Location location = createDbLocation("comp_id_1"); + DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder() + .addFlow(createFlow(location)) + .build(); + + List flows = flowGenerator.convertFlows(COMPONENT_NAME, issueLocations); + + assertThat(flows).hasSize(1); + Flow singleFlow = flows.iterator().next(); + + assertThat(singleFlow.getLocations()).hasSize(1); + Location singleLocation = singleFlow.getLocations().iterator().next(); + + assertLocationMatches(singleLocation, location); + } + + @Test + public void convertFlows_with2FlowsSingleDbLocations_returnsCorrectFlow() { + DbIssues.Location location1 = createDbLocation("comp_id_1"); + DbIssues.Location location2 = createDbLocation("comp_id_2"); + DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder() + .addFlow(createFlow(location1)) + .addFlow(createFlow(location2)) + .build(); + + List flows = flowGenerator.convertFlows(COMPONENT_NAME, issueLocations); + + assertThat(flows).hasSize(2).extracting(f -> f.getLocations().size()).containsExactly(1, 1); + Map toDbLocation = Map.of( + "file_path_" + location1.getComponentId(), location1, + "file_path_" + location2.getComponentId(), location2); + flows.stream() + .map(actualFlow -> actualFlow.getLocations().iterator().next()) + .forEach(l -> assertLocationMatches(l, toDbLocation.get(l.getFilePath()))); + } + + @Test + public void convertFlows_with2DbLocations_returns() { + DbIssues.Location location1 = createDbLocation("comp_id_1"); + DbIssues.Location location2 = createDbLocation("comp_id_2"); + DbIssues.Locations issueLocations = DbIssues.Locations.newBuilder() + .addFlow(createFlow(location1, location2)) + .build(); + + List flows = flowGenerator.convertFlows(COMPONENT_NAME, issueLocations); + + assertThat(flows).hasSize(1); + Flow singleFlow = flows.iterator().next(); + + assertThat(singleFlow.getLocations()).hasSize(2); + Map pathToLocations = singleFlow.getLocations() + .stream() + .collect(toMap(Location::getFilePath, identity())); + + assertLocationMatches(pathToLocations.get("file_path_comp_id_1"), location1); + assertLocationMatches(pathToLocations.get("file_path_comp_id_2"), location2); + + } + + private DbIssues.Location createDbLocation(String componentId) { + org.sonar.db.protobuf.DbCommons.TextRange textRange = org.sonar.db.protobuf.DbCommons.TextRange.newBuilder() + .setStartLine(RandomUtils.nextInt()) + .setEndLine(RandomUtils.nextInt()) + .setStartOffset(RandomUtils.nextInt()) + .setEndOffset(RandomUtils.nextInt()) + .build(); + + Component component = mock(Component.class); + when(component.getName()).thenReturn("file_path_" + componentId); + when(treeRootHolder.getComponentByUuid(componentId)).thenReturn(component); + return DbIssues.Location.newBuilder() + .setComponentId(componentId) + .setChecksum("hash" + randomAlphanumeric(10)) + .setTextRange(textRange) + .setMsg("msg" + randomAlphanumeric(15)) + .build(); + } + + private static DbIssues.Flow createFlow(DbIssues.Location ... locations) { + return DbIssues.Flow.newBuilder() + .addAllLocation(List.of(locations)) + .build(); + } + + private static void assertLocationMatches(Location actualLocation, DbIssues.Location sourceLocation) { + assertThat(actualLocation.getMessage()).isEqualTo(sourceLocation.getMsg()); + DbCommons.TextRange textRange = sourceLocation.getTextRange(); + assertThat(actualLocation.getTextRange().getStartLine()).isEqualTo(textRange.getStartLine()); + assertThat(actualLocation.getTextRange().getEndLine()).isEqualTo(textRange.getEndLine()); + assertThat(actualLocation.getTextRange().getStartLineOffset()).isEqualTo(textRange.getStartOffset()); + assertThat(actualLocation.getTextRange().getEndLineOffset()).isEqualTo(textRange.getEndOffset()); + assertThat(actualLocation.getTextRange().getHash()).isEqualTo(sourceLocation.getChecksum()); + } + +} diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/pushevent/PushEventFactoryTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/pushevent/PushEventFactoryTest.java index 83aa9bf1836..65548362402 100644 --- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/pushevent/PushEventFactoryTest.java +++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/pushevent/PushEventFactoryTest.java @@ -33,6 +33,7 @@ import org.sonar.ce.task.projectanalysis.analysis.TestBranch; import org.sonar.ce.task.projectanalysis.component.Component.Type; import org.sonar.ce.task.projectanalysis.component.MutableTreeRootHolderRule; import org.sonar.ce.task.projectanalysis.component.ReportComponent; +import org.sonar.ce.task.projectanalysis.locations.flow.FlowGenerator; import org.sonar.core.issue.DefaultIssue; import org.sonar.core.issue.FieldDiffs; import org.sonar.db.protobuf.DbCommons; @@ -54,7 +55,8 @@ public class PushEventFactoryTest { public AnalysisMetadataHolderRule analysisMetadataHolder = new AnalysisMetadataHolderRule() .setBranch(new TestBranch("develop")); - private final PushEventFactory underTest = new PushEventFactory(treeRootHolder, analysisMetadataHolder, taintChecker); + private final FlowGenerator flowGenerator = new FlowGenerator(treeRootHolder); + private final PushEventFactory underTest = new PushEventFactory(treeRootHolder, analysisMetadataHolder, taintChecker, flowGenerator); @Before public void setUp() { -- 2.39.5