issue.setUpdateDate(longToDate(issueUpdateDate));
issue.setSelectedAt(selectedAt);
issue.setLocations(parseLocations());
- issue.setFromExternalRuleEngine(isExternal);
+ issue.setIsFromExternalRuleEngine(isExternal);
return issue;
}
}
@CheckForNull
public Duration calculate(DefaultIssue issue) {
+ if (issue.isFromExternalRuleEngine()) {
+ return issue.effort();
+ }
Rule rule = ruleRepository.getByKey(issue.ruleKey());
DebtRemediationFunction fn = rule.getRemediationFunction();
if (fn != null) {
verifyEffortToFix(issue, fn);
Duration debt = Duration.create(0);
- String gapMultiplier =fn.gapMultiplier();
+ String gapMultiplier = fn.gapMultiplier();
if (fn.type().usesGapMultiplier() && !Strings.isNullOrEmpty(gapMultiplier)) {
- int effortToFixValue = MoreObjects.firstNonNull(issue.effortToFix(), 1).intValue();
+ int effortToFixValue = MoreObjects.firstNonNull(issue.gap(), 1).intValue();
// TODO convert to Duration directly in Rule#remediationFunction -> better performance + error handling
debt = durations.decode(gapMultiplier).multiply(effortToFixValue);
}
- String baseEffort= fn.baseEffort();
+ String baseEffort = fn.baseEffort();
if (fn.type().usesBaseEffort() && !Strings.isNullOrEmpty(baseEffort)) {
// TODO convert to Duration directly in Rule#remediationFunction -> better performance + error handling
debt = debt.add(durations.decode(baseEffort));
}
private static void verifyEffortToFix(DefaultIssue issue, DebtRemediationFunction fn) {
- if (Type.CONSTANT_ISSUE.equals(fn.type()) && issue.effortToFix() != null) {
+ if (Type.CONSTANT_ISSUE.equals(fn.type()) && issue.gap() != null) {
throw new IllegalArgumentException("Rule '" + issue.getRuleKey() + "' can not use 'Constant/issue' remediation function " +
"because this rule does not have a fixed remediation cost.");
}
dbLocationsBuilder.addFlow(dbFlowBuilder);
}
}
- issue.setFromExternalRuleEngine(false);
+ issue.setIsFromExternalRuleEngine(false);
issue.setLocations(dbLocationsBuilder.build());
return issue;
}
dbLocationsBuilder.addFlow(dbFlowBuilder);
}
}
- issue.setFromExternalRuleEngine(true);
+ issue.setIsFromExternalRuleEngine(true);
issue.setLocations(dbLocationsBuilder.build());
issue.setType(toRuleType(reportIssue.getType()));
issue.setSeverity(activeRule.get().getSeverity());
issue.setLine(null);
issue.setChecksum("");
- issue.setFromExternalRuleEngine(false);
+ issue.setIsFromExternalRuleEngine(false);
}
}
return issue;
.setUpdatedAt(system2.now()));
Rule newRule = new RuleImpl(dao.selectOrFailByKey(dbSession, external.getKey()));
- // TODO write rule repository if needed
ruleIndexer.commitAndIndex(dbSession, newRule.getId());
return newRule;
}
import org.junit.Test;
import org.sonar.api.server.debt.DebtRemediationFunction;
import org.sonar.api.server.debt.internal.DefaultDebtRemediationFunction;
+import org.sonar.api.utils.Duration;
import org.sonar.api.utils.Durations;
import org.sonar.core.issue.DefaultIssue;
import org.sonar.db.rule.RuleTesting;
assertThat(underTest.calculate(issue).toMinutes()).isEqualTo((int) (coefficient * effortToFix));
}
+ @Test
+ public void copy_effort_for_external_issues() {
+ issue.setGap(null);
+ issue.setIsFromExternalRuleEngine(true);
+ issue.setEffort(Duration.create(20l));
+ rule.setFunction(null);
+
+ assertThat(underTest.calculate(issue).toMinutes()).isEqualTo(20l);
+ }
+
@Test
public void constant_function() {
int constant = 2;
loginAndAddProjectPermission("john", ISSUE_ADMIN);
context.issue()
- .setFromExternalRuleEngine(true)
+ .setIsFromExternalRuleEngine(true)
.setStatus(STATUS_CLOSED);
action.execute(ImmutableMap.of("transition", "close"), context);
return isFromExternalRuleEngine;
}
- public DefaultIssue setFromExternalRuleEngine(boolean fromExternalRuleEngine) {
- isFromExternalRuleEngine = fromExternalRuleEngine;
+ public DefaultIssue setIsFromExternalRuleEngine(boolean isFromExternalRuleEngine) {
+ this.isFromExternalRuleEngine = isFromExternalRuleEngine;
return this;
}
issue.setCopied(false);
issue.setBeingClosed(false);
issue.setOnDisabledRule(false);
- issue.setFromExternalRuleEngine(isFromExternalRuleEngine);
+ issue.setIsFromExternalRuleEngine(isFromExternalRuleEngine);
return issue;
}
}
InputFile file = findFile(context, location.filePath);
if (file != null) {
newLocation
- .message(location.message)
.on(file);
+ if (location.message != null) {
+ newLocation.message(location.message);
+ }
+
if (location.textRange != null) {
if (location.textRange.startColumn != null) {
TextPointer start = file.newPointer(location.textRange.startLine, location.textRange.startColumn);
System.out.println(Paths.get("org/sonar/scanner/externalissue/report.json").toAbsolutePath());
Report report = parser.parse();
- assertThat(report.issues).hasSize(3);
+ assertThat(report.issues).hasSize(4);
assertThat(report.issues[0].engineId).isEqualTo("eslint");
assertThat(report.issues[0].ruleId).isEqualTo("rule1");
assertThat(report.issues[0].severity).isEqualTo("MAJOR");
assertThat(report.issues[0].primaryLocation.textRange.endColumn).isEqualTo(4);
assertThat(report.issues[0].primaryLocation.textRange.endLine).isEqualTo(3);
assertThat(report.issues[0].secondaryLocations).isNull();
+
+ assertThat(report.issues[3].engineId).isEqualTo("eslint");
+ assertThat(report.issues[3].ruleId).isEqualTo("rule3");
+ assertThat(report.issues[3].severity).isEqualTo("MAJOR");
+ assertThat(report.issues[3].effortMinutes).isNull();
+ assertThat(report.issues[3].type).isEqualTo("BUG");
+ assertThat(report.issues[3].secondaryLocations).hasSize(2);
+ assertThat(report.issues[3].secondaryLocations[0].filePath).isEqualTo("file1.js");
+ assertThat(report.issues[3].secondaryLocations[0].message).isEqualTo("fix the bug here");
+ assertThat(report.issues[3].secondaryLocations[0].textRange.startLine).isEqualTo(1);
+ assertThat(report.issues[3].secondaryLocations[1].filePath).isEqualTo("file2.js");
+ assertThat(report.issues[3].secondaryLocations[1].message).isNull();
+ assertThat(report.issues[3].secondaryLocations[1].textRange.startLine).isEqualTo(2);
}
private Path path(String reportName) {
exception.expectMessage("missing mandatory field 'filePath'");
parser.parse();
}
+
+ @Test
+ public void fail_if_message_not_set_in_primaryLocation() {
+ ReportParser parser = new ReportParser(path("report_missing_message.json"));
+ exception.expect(IllegalStateException.class);
+ exception.expectMessage("missing mandatory field 'message'");
+ parser.parse();
+ }
}
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggersTest;
import org.sonar.scanner.mediumtest.ScannerMediumTester;
import org.sonar.scanner.mediumtest.TaskResult;
import org.sonar.scanner.protocol.Constants.Severity;
assertThat(issue.getTextRange().getStartOffset()).isEqualTo(0);
assertThat(issue.getTextRange().getEndOffset()).isEqualTo(24);
- // One file-level issue in helloscala
+ // One file-level issue in helloscala, with secondary location
List<ExternalIssue> externalIssues2 = result.externalIssuesFor(result.inputFile("xources/hello/helloscala.xoo"));
assertThat(externalIssues2).hasSize(1);
issue = externalIssues2.iterator().next();
- assertThat(issue.getFlowCount()).isZero();
+ assertThat(issue.getFlowCount()).isEqualTo(2);
assertThat(issue.getMsg()).isEqualTo("fix the bug here");
assertThat(issue.getRuleKey()).isEqualTo("rule3");
assertThat(issue.getSeverity()).isEqualTo(Severity.MAJOR);
assertThat(issue.getType()).isEqualTo(IssueType.BUG);
assertThat(issue.hasTextRange()).isFalse();
-
+ assertThat(issue.getFlow(0).getLocationCount()).isOne();
+ assertThat(issue.getFlow(0).getLocation(0).getTextRange().getStartLine()).isOne();
+ assertThat(issue.getFlow(1).getLocationCount()).isOne();
+ assertThat(issue.getFlow(1).getLocation(0).getTextRange().getStartLine()).isEqualTo(3);
+
+
// one issue is located in a non-existing file
assertThat(logs.logs()).contains("External issues ignored for 1 unknown files, including: invalidFile");
"primaryLocation": {
"message": "fix the bug here",
"filePath": "xources/hello/helloscala.xoo"
- }
+ },
+ "secondaryLocations": [
+ {
+ "filePath": "xources/hello/HelloJava.xoo",
+ "textRange": {
+ "startLine": 1
+ }
+ },
+ {
+ "filePath": "xources/hello/HelloJava.xoo",
+ "textRange": {
+ "startLine": 3
+ }
+ }
+ ]
}
]
}
\ No newline at end of file
"message": "fix the bug here",
"filePath": "file3.js"
}
+ },
+ {
+ "engineId": "eslint",
+ "ruleId": "rule3",
+ "severity": "MAJOR",
+ "type": "BUG",
+ "primaryLocation": {
+ "message": "fix the bug here",
+ "filePath": "file3.js"
+ },
+ "secondaryLocations": [
+ {
+ "message": "fix the bug here",
+ "filePath": "file1.js",
+ "textRange": {
+ "startLine": 1
+ }
+ },
+ {
+ "filePath": "file2.js",
+ "textRange": {
+ "startLine": 2
+ }
+ }
+ ]
}
]
}
--- /dev/null
+{
+"issues" : [
+ {
+ "engineId": "eslint",
+ "ruleId": "rule1",
+ "severity": "MAJOR",
+ "type": "CODE_SMELL",
+ "primaryLocation": {
+ "message": "fix the issue here",
+ "filePath": "file1.js",
+ "textRange": {
+ "startLine": 1,
+ "endLine": 2
+ }
+ }
+ },
+ {
+ "engineId": "eslint",
+ "ruleId": "rule2",
+ "severity": "MAJOR",
+ "type": "BUG",
+ "primaryLocation": {
+ "filePath": "file1.js"
+ }
+ }
+]
+}
+
--- /dev/null
+{
+"issues" : [
+ {
+ "engineId": "externalXoo",
+ "ruleId": "rule1",
+ "severity": "MAJOR",
+ "type": "CODE_SMELL",
+ "effortMinutes": 50,
+ "primaryLocation": {
+ "message": "fix the issue here",
+ "filePath": "src/main/xoo/sample/Sample.xoo",
+ "textRange": {
+ "startLine": 5,
+ "startColumn": 2,
+ "endLine": 5,
+ "endColumn": 21
+ }
+ }
+ },
+ {
+ "engineId": "externalXoo",
+ "ruleId": "rule2",
+ "severity": "CRITICAL",
+ "type": "BUG",
+ "primaryLocation": {
+ "message": "fix the bug here",
+ "filePath": "src/main/xoo/sample/Sample.xoo"
+ },
+ "secondaryLocations": [
+ {
+ "filePath": "src/main/xoo/sample/Sample.xoo",
+ "textRange": {
+ "startLine": 1
+ }
+ },
+ {
+ "filePath": "unknown",
+ "textRange": {
+ "startLine": 3
+ }
+ }
+ ]
+ }
+]
+}
\ No newline at end of file
*/
package org.sonarqube.tests.issue;
-import com.sonar.orchestrator.build.SonarScanner;
+import com.sonar.orchestrator.Orchestrator;
+import java.util.Collections;
import java.util.List;
-import java.util.stream.Collectors;
import org.junit.Before;
+import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.sonarqube.qa.util.Tester;
import static org.assertj.core.api.Assertions.assertThat;
-public class ExternalIssueTest extends AbstractIssueTest {
+public class ExternalIssueTest {
private static final String PROJECT_KEY = "project";
+ // This class uses its own instance of the server because it creates external rules in it
+ @ClassRule
+ public static final Orchestrator ORCHESTRATOR = ItUtils.newOrchestratorBuilder()
+ .addPlugin(ItUtils.xooPlugin())
+ .build();
+
@Rule
public Tester tester = new Tester(ORCHESTRATOR);
@Test
public void should_import_external_issues_and_create_external_rules() {
- noExternalRuleAndNoIssues();
+ noIssues();
+ ruleDoesntExist("external_xoo:OneExternalIssuePerLine");
- SonarScanner sonarScanner = ItUtils.runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample",
+ ItUtils.runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample",
"sonar.oneExternalIssuePerLine.activate", "true");
List<Issue> issuesList = tester.wsClient().issues().search(new SearchRequest()).getIssuesList();
assertThat(issuesList).hasSize(17);
assertThat(issuesList).allMatch(issue -> "external_xoo:OneExternalIssuePerLine".equals(issue.getRule()));
assertThat(issuesList).allMatch(issue -> "This issue is generated on each line".equals(issue.getMessage()));
- assertThat(issuesList).allMatch(issue -> "This issue is generated on each line".equals(issue.getMessage()));
assertThat(issuesList).allMatch(issue -> Severity.MAJOR.equals(issue.getSeverity()));
- assertThat(issuesList).allMatch(issue -> RuleType.CODE_SMELL.equals(issue.getType()));
+ assertThat(issuesList).allMatch(issue -> RuleType.BUG.equals(issue.getType()));
assertThat(issuesList).allMatch(issue -> "sample:src/main/xoo/sample/Sample.xoo".equals(issue.getComponent()));
assertThat(issuesList).allMatch(issue -> "OPEN".equals(issue.getStatus()));
assertThat(issuesList).allMatch(issue -> issue.getExternalRuleEngine().equals("xoo"));
- List<org.sonarqube.ws.Rules.Rule> rulesList = tester.wsClient().rules()
- .search(new org.sonarqube.ws.client.rules.SearchRequest().setIsExternal(Boolean.toString(true))).getRulesList();
- List<org.sonarqube.ws.Rules.Rule> externalRules = rulesList.stream().filter(rule -> rule.getIsExternal()).collect(Collectors.toList());
-
- assertThat(externalRules).hasSize(1);
- assertThat(externalRules.get(0).getKey()).isEqualTo("external_xoo:OneExternalIssuePerLine");
- assertThat(externalRules.get(0).getIsTemplate()).isFalse();
- assertThat(externalRules.get(0).getIsExternal()).isTrue();
- assertThat(externalRules.get(0).getTags().getTagsCount()).isEqualTo(0);
- assertThat(externalRules.get(0).getScope()).isEqualTo(RuleScope.ALL);
+ ruleExists("external_xoo:OneExternalIssuePerLine");
// second analysis, issue tracking should work
- sonarScanner = ItUtils.runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample",
+ ItUtils.runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample",
"sonar.oneExternalIssuePerLine.activate", "true");
issuesList = tester.wsClient().issues().search(new SearchRequest()).getIssuesList();
assertThat(issuesList).hasSize(17);
}
- private void noExternalRuleAndNoIssues() {
+ @Test
+ public void should_import_external_issues_from_json_report_and_create_external_rules() {
+ noIssues();
+ ruleDoesntExist("external_externalXoo:rule1");
+ ruleDoesntExist("external_externalXoo:rule2");
+
+ ItUtils.runProjectAnalysis(ORCHESTRATOR, "shared/xoo-sample",
+ "sonar.externalIssuesReportPaths", "externalIssues.json");
+
+ List<Issue> issuesList = tester.wsClient().issues().search(new SearchRequest()
+ .setRules(Collections.singletonList("external_externalXoo:rule1"))).getIssuesList();
+ assertThat(issuesList).hasSize(1);
+
+ assertThat(issuesList.get(0).getRule()).isEqualTo("external_externalXoo:rule1");
+ assertThat(issuesList.get(0).getMessage()).isEqualTo("fix the issue here");
+ assertThat(issuesList.get(0).getSeverity()).isEqualTo(Severity.MAJOR);
+ assertThat(issuesList.get(0).getType()).isEqualTo(RuleType.CODE_SMELL);
+ assertThat(issuesList.get(0).getComponent()).isEqualTo("sample:src/main/xoo/sample/Sample.xoo");
+ assertThat(issuesList.get(0).getStatus()).isEqualTo("OPEN");
+ assertThat(issuesList.get(0).getEffort()).isEqualTo("20min");
+ assertThat(issuesList.get(0).getExternalRuleEngine()).isEqualTo("externalXoo");
+
+ issuesList = tester.wsClient().issues().search(new SearchRequest()
+ .setRules(Collections.singletonList("external_externalXoo:rule2"))).getIssuesList();
+ assertThat(issuesList).hasSize(1);
+
+ assertThat(issuesList.get(0).getRule()).isEqualTo("external_externalXoo:rule2");
+ assertThat(issuesList.get(0).getMessage()).isEqualTo("fix the bug here");
+ assertThat(issuesList.get(0).getSeverity()).isEqualTo(Severity.CRITICAL);
+ assertThat(issuesList.get(0).getType()).isEqualTo(RuleType.BUG);
+ assertThat(issuesList.get(0).getComponent()).isEqualTo("sample:src/main/xoo/sample/Sample.xoo");
+ assertThat(issuesList.get(0).getStatus()).isEqualTo("OPEN");
+ assertThat(issuesList.get(0).getExternalRuleEngine()).isEqualTo("externalXoo");
+
+ ruleExists("external_externalXoo:rule1");
+ ruleExists("external_externalXoo:rule2");
+ }
+
+ private void ruleDoesntExist(String key) {
List<org.sonarqube.ws.Rules.Rule> rulesList = tester.wsClient().rules()
- .search(new org.sonarqube.ws.client.rules.SearchRequest().setIsExternal(Boolean.toString(true))).getRulesList();
- assertThat(rulesList).noneMatch(rule -> rule.getIsExternal());
+ .search(new org.sonarqube.ws.client.rules.SearchRequest()
+ .setRuleKey(key)
+ .setIsExternal(Boolean.toString(true)))
+ .getRulesList();
+ assertThat(rulesList).isEmpty();
+
+ }
+
+ private void ruleExists(String key) {
+ List<org.sonarqube.ws.Rules.Rule> rulesList = tester.wsClient().rules()
+ .search(new org.sonarqube.ws.client.rules.SearchRequest()
+ .setRuleKey(key)
+ .setIsExternal(Boolean.toString(true)))
+ .getRulesList();
+
+ assertThat(rulesList).hasSize(1);
+ assertThat(rulesList.get(0).getKey()).isEqualTo(key);
+ assertThat(rulesList.get(0).getIsTemplate()).isFalse();
+ assertThat(rulesList.get(0).getIsExternal()).isTrue();
+ assertThat(rulesList.get(0).getTags().getTagsCount()).isEqualTo(0);
+ assertThat(rulesList.get(0).getScope()).isEqualTo(RuleScope.ALL);
+ }
+ private void noIssues() {
List<Issue> issuesList = tester.wsClient().issues().search(new SearchRequest()).getIssuesList();
assertThat(issuesList).isEmpty();
}