diff options
author | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-05-16 18:08:30 +0200 |
---|---|---|
committer | Julien Lancelot <julien.lancelot@sonarsource.com> | 2016-05-18 14:29:40 +0200 |
commit | 51fe1d71e419026cf331f6f0ddac3cfac2f6b0cb (patch) | |
tree | 2c67424f8207c1aec37c359d6ec7bd0a555f626a /server | |
parent | 236a6146ae770e74044337462132865490cb8365 (diff) | |
download | sonarqube-51fe1d71e419026cf331f6f0ddac3cfac2f6b0cb.tar.gz sonarqube-51fe1d71e419026cf331f6f0ddac3cfac2f6b0cb.zip |
SONAR-7009 Add issue exclusions in compute engine
Diffstat (limited to 'server')
9 files changed, 557 insertions, 6 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java index e78c44c8e79..b0e9666e7ef 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/container/ReportComputeEngineContainerPopulator.java @@ -70,6 +70,7 @@ import org.sonar.server.computation.issue.commonrule.DuplicatedBlockRule; import org.sonar.server.computation.issue.commonrule.LineCoverageRule; import org.sonar.server.computation.issue.commonrule.SkippedTestRule; import org.sonar.server.computation.issue.commonrule.TestErrorRule; +import org.sonar.server.computation.issue.filter.IssueFilter; import org.sonar.server.computation.language.LanguageRepositoryImpl; import org.sonar.server.computation.measure.MeasureComputersHolderImpl; import org.sonar.server.computation.measure.MeasureComputersVisitor; @@ -172,6 +173,7 @@ public final class ReportComputeEngineContainerPopulator implements ContainerPop IssueLifecycle.class, ComponentsWithUnprocessedIssues.class, ComponentIssuesRepositoryImpl.class, + IssueFilter.class, // common rules CommonRuleEngineImpl.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java index 75cdfe284af..d692dfd0cd3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/TrackerRawInputFactory.java @@ -38,6 +38,7 @@ import org.sonar.server.computation.batch.BatchReportReader; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.component.TreeRootHolder; import org.sonar.server.computation.issue.commonrule.CommonRuleEngine; +import org.sonar.server.computation.issue.filter.IssueFilter; import org.sonar.server.computation.source.SourceLinesRepository; import org.sonar.server.rule.CommonRuleKeys; @@ -50,13 +51,15 @@ public class TrackerRawInputFactory { private final BatchReportReader reportReader; private final SourceLinesRepository sourceLinesRepository; private final CommonRuleEngine commonRuleEngine; + private final IssueFilter issueFilter; public TrackerRawInputFactory(TreeRootHolder treeRootHolder, BatchReportReader reportReader, - SourceLinesRepository sourceLinesRepository, CommonRuleEngine commonRuleEngine) { + SourceLinesRepository sourceLinesRepository, CommonRuleEngine commonRuleEngine, IssueFilter issueFilter) { this.treeRootHolder = treeRootHolder; this.reportReader = reportReader; this.sourceLinesRepository = sourceLinesRepository; this.commonRuleEngine = commonRuleEngine; + this.issueFilter = issueFilter; } public Input<DefaultIssue> create(Component component) { @@ -86,7 +89,9 @@ public class TrackerRawInputFactory { List<DefaultIssue> result = new ArrayList<>(); for (DefaultIssue commonRuleIssue : commonRuleEngine.process(component)) { - result.add(init(commonRuleIssue)); + if (issueFilter.accept(commonRuleIssue, component)) { + result.add(init(commonRuleIssue)); + } } try (CloseableIterator<ScannerReport.Issue> reportIssues = reportReader.readComponentIssues(component.getReportAttributes().getRef())) { // optimization - do not load line hashes if there are no issues -> getLineHashSequence() is executed @@ -95,7 +100,9 @@ public class TrackerRawInputFactory { ScannerReport.Issue reportIssue = reportIssues.next(); if (isIssueOnUnsupportedCommonRule(reportIssue)) { DefaultIssue issue = toIssue(getLineHashSequence(), reportIssue); - result.add(issue); + if (issueFilter.accept(issue, component)) { + result.add(issue); + } } else { Loggers.get(getClass()).debug("Ignored issue from analysis report on rule {}:{}", reportIssue.getRuleRepository(), reportIssue.getRuleKey()); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/filter/IssueFilter.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/filter/IssueFilter.java new file mode 100644 index 00000000000..38232233069 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/filter/IssueFilter.java @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.server.computation.issue.filter; + +import com.google.common.base.Splitter; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.sonar.api.ce.ComputeEngineSide; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.SettingsRepository; +import org.sonar.server.computation.component.TreeRootHolder; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Strings.isNullOrEmpty; +import static java.lang.String.format; +import static org.apache.commons.lang.StringUtils.defaultIfBlank; +import static org.sonar.core.config.IssueExclusionProperties.PATTERNS_MULTICRITERIA_EXCLUSION_KEY; +import static org.sonar.core.config.IssueExclusionProperties.PATTERNS_MULTICRITERIA_INCLUSION_KEY; +import static org.sonar.core.config.IssueExclusionProperties.RESOURCE_KEY; +import static org.sonar.core.config.IssueExclusionProperties.RULE_KEY; +import static org.sonar.server.computation.component.Component.Type.FILE; + +@ComputeEngineSide +public class IssueFilter { + + private static final Logger LOG = Loggers.get(IssueFilter.class); + + private final List<IssuePattern> exclusionPatterns; + private final List<IssuePattern> inclusionPatterns; + + public IssueFilter(TreeRootHolder treeRootHolder, SettingsRepository settingsRepository) { + Settings settings = settingsRepository.getSettings(treeRootHolder.getRoot()); + this.exclusionPatterns = loadPatterns(PATTERNS_MULTICRITERIA_EXCLUSION_KEY, settings); + this.inclusionPatterns = loadPatterns(PATTERNS_MULTICRITERIA_INCLUSION_KEY, settings); + } + + public boolean accept(DefaultIssue issue, Component component) { + if (component.getType() != FILE || (exclusionPatterns.isEmpty() && inclusionPatterns.isEmpty())) { + return true; + } + if (isExclude(issue, component)) { + return false; + } + return isInclude(issue, component); + } + + private boolean isExclude(DefaultIssue issue, Component component) { + IssuePattern matchingPattern = null; + Iterator<IssuePattern> patternIterator = exclusionPatterns.iterator(); + while (matchingPattern == null && patternIterator.hasNext()) { + IssuePattern nextPattern = patternIterator.next(); + if (nextPattern.match(issue, component)) { + matchingPattern = nextPattern; + } + } + if (matchingPattern != null) { + LOG.debug("Issue {} ignored by exclusion pattern {}", issue, matchingPattern); + return true; + } + return false; + } + + private boolean isInclude(DefaultIssue issue, Component component) { + boolean atLeastOneRuleMatched = false; + boolean atLeastOnePatternFullyMatched = false; + IssuePattern matchingPattern = null; + + for (IssuePattern pattern : inclusionPatterns) { + if (pattern.getRulePattern().match(issue.ruleKey().toString())) { + atLeastOneRuleMatched = true; + String componentPath = component.getReportAttributes().getPath(); + if (componentPath != null && pattern.getComponentPattern().match(componentPath)) { + atLeastOnePatternFullyMatched = true; + matchingPattern = pattern; + } + } + } + + if (atLeastOneRuleMatched) { + if (atLeastOnePatternFullyMatched) { + LOG.debug("Issue {} enforced by pattern {}", issue, matchingPattern); + } + return atLeastOnePatternFullyMatched; + } else { + return true; + } + } + + private static List<IssuePattern> loadPatterns(String propertyKey, Settings settings) { + List<IssuePattern> patterns = new ArrayList<>(); + String patternConf = defaultIfBlank(settings.getString(propertyKey), ""); + for (String id : Splitter.on(",").omitEmptyStrings().split(patternConf)) { + String propPrefix = propertyKey + "." + id + "."; + String componentPathPattern = settings.getString(propPrefix + RESOURCE_KEY); + checkArgument(!isNullOrEmpty(componentPathPattern), format("File path pattern cannot be empty. Please check '%s' settings", propertyKey)); + String ruleKeyPattern = settings.getString(propPrefix + RULE_KEY); + checkArgument(!isNullOrEmpty(ruleKeyPattern), format("Rule key pattern cannot be empty. Please check '%s' settings", propertyKey)); + patterns.add(new IssuePattern(componentPathPattern, ruleKeyPattern)); + } + return patterns; + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/filter/IssuePattern.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/filter/IssuePattern.java new file mode 100644 index 00000000000..86d7b420f24 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/filter/IssuePattern.java @@ -0,0 +1,65 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.server.computation.issue.filter; + +import javax.annotation.Nullable; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.utils.WildcardPattern; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.server.computation.component.Component; + +public class IssuePattern { + + private WildcardPattern componentPattern; + private WildcardPattern rulePattern; + + public IssuePattern(String componentPattern, String rulePattern) { + this.componentPattern = WildcardPattern.create(componentPattern); + this.rulePattern = WildcardPattern.create(rulePattern); + } + + public WildcardPattern getComponentPattern() { + return componentPattern; + } + + public WildcardPattern getRulePattern() { + return rulePattern; + } + + boolean match(DefaultIssue issue, Component component) { + return matchComponent(component.getReportAttributes().getPath()) && matchRule(issue.ruleKey()); + } + + boolean matchRule(RuleKey rule) { + return rulePattern.match(rule.toString()); + } + + boolean matchComponent(@Nullable String path) { + return path != null && componentPattern.match(path); + } + + @Override + public String toString() { + return "IssuePattern{" + + "componentPattern=" + componentPattern + + ", rulePattern=" + rulePattern + + '}'; + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/issue/filter/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/filter/package-info.java new file mode 100644 index 00000000000..08a42d67f03 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/issue/filter/package-info.java @@ -0,0 +1,23 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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. + */ +@ParametersAreNonnullByDefault +package org.sonar.server.computation.issue.filter; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IntegrateIssuesVisitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IntegrateIssuesVisitorTest.java index 8e4f582da52..ea94c07be31 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IntegrateIssuesVisitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/IntegrateIssuesVisitorTest.java @@ -45,6 +45,7 @@ import org.sonar.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.component.TypeAwareVisitor; import org.sonar.server.computation.issue.commonrule.CommonRuleEngineImpl; +import org.sonar.server.computation.issue.filter.IssueFilter; import org.sonar.server.computation.qualityprofile.ActiveRulesHolderRule; import org.sonar.server.computation.source.SourceLinesRepositoryRule; import org.sonar.server.issue.IssueTesting; @@ -53,9 +54,11 @@ import static com.google.common.collect.Lists.newArrayList; import static com.google.common.collect.Sets.newHashSet; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; import static org.sonar.server.computation.component.ReportComponent.builder; public class IntegrateIssuesVisitorTest { @@ -103,9 +106,11 @@ public class IntegrateIssuesVisitorTest { ArgumentCaptor<DefaultIssue> defaultIssueCaptor = ArgumentCaptor.forClass(DefaultIssue.class); + IssueFilter issueFilter = mock(IssueFilter.class); + BaseIssuesLoader baseIssuesLoader = new BaseIssuesLoader(treeRootHolder, dbTester.getDbClient(), ruleRepositoryRule, activeRulesHolderRule); TrackerExecution tracker = new TrackerExecution(new TrackerBaseInputFactory(baseIssuesLoader, dbTester.getDbClient()), new TrackerRawInputFactory(treeRootHolder, reportReader, - fileSourceRepository, new CommonRuleEngineImpl()), new Tracker<DefaultIssue, DefaultIssue>()); + fileSourceRepository, new CommonRuleEngineImpl(), issueFilter), new Tracker<DefaultIssue, DefaultIssue>()); IssueCache issueCache; IssueLifecycle issueLifecycle = mock(IssueLifecycle.class); @@ -119,6 +124,7 @@ public class IntegrateIssuesVisitorTest { public void setUp() throws Exception { treeRootHolder.setRoot(PROJECT); issueCache = new IssueCache(temp.newFile(), System2.INSTANCE); + when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true); underTest = new IntegrateIssuesVisitor(tracker, issueCache, issueLifecycle, issueVisitors, componentsWithUnprocessedIssues, componentIssuesRepository); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/TrackerRawInputFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/TrackerRawInputFactoryTest.java index 240c547dc05..0d3491925d7 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/TrackerRawInputFactoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/TrackerRawInputFactoryTest.java @@ -36,11 +36,14 @@ import org.sonar.server.computation.batch.TreeRootHolderRule; import org.sonar.server.computation.component.Component; import org.sonar.server.computation.component.ReportComponent; import org.sonar.server.computation.issue.commonrule.CommonRuleEngine; +import org.sonar.server.computation.issue.filter.IssueFilter; import org.sonar.server.computation.source.SourceLinesRepositoryRule; import org.sonar.server.rule.CommonRuleKeys; import static java.util.Arrays.asList; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -62,7 +65,9 @@ public class TrackerRawInputFactoryTest { CommonRuleEngine commonRuleEngine = mock(CommonRuleEngine.class); - TrackerRawInputFactory underTest = new TrackerRawInputFactory(treeRootHolder, reportReader, fileSourceRepository, commonRuleEngine); + IssueFilter issueFilter = mock(IssueFilter.class); + + TrackerRawInputFactory underTest = new TrackerRawInputFactory(treeRootHolder, reportReader, fileSourceRepository, commonRuleEngine, issueFilter); @Test public void load_source_hash_sequences() throws Exception { @@ -86,7 +91,8 @@ public class TrackerRawInputFactoryTest { } @Test - public void load_issues() throws Exception { + public void load_issues_from_report() throws Exception { + when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true); fileSourceRepository.addLines(FILE_REF, "line 1;", "line 2;"); ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder() .setTextRange(TextRange.newBuilder().setStartLine(2).build()) @@ -118,6 +124,25 @@ public class TrackerRawInputFactoryTest { } @Test + public void ignore_issue_from_report() throws Exception { + when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(false); + fileSourceRepository.addLines(FILE_REF, "line 1;", "line 2;"); + ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder() + .setTextRange(TextRange.newBuilder().setStartLine(2).build()) + .setMsg("the message") + .setRuleRepository("java") + .setRuleKey("S001") + .setSeverity(Constants.Severity.BLOCKER) + .setGap(3.14) + .build(); + reportReader.putIssues(FILE.getReportAttributes().getRef(), asList(reportIssue)); + Input<DefaultIssue> input = underTest.create(FILE); + + Collection<DefaultIssue> issues = input.getIssues(); + assertThat(issues).isEmpty(); + } + + @Test public void ignore_report_issues_on_common_rules() throws Exception { fileSourceRepository.addLines(FILE_REF, "line 1;", "line 2;"); ScannerReport.Issue reportIssue = ScannerReport.Issue.newBuilder() @@ -135,6 +160,7 @@ public class TrackerRawInputFactoryTest { @Test public void load_issues_of_compute_engine_common_rules() throws Exception { + when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(true); fileSourceRepository.addLines(FILE_REF, "line 1;", "line 2;"); DefaultIssue ceIssue = new DefaultIssue() .setRuleKey(RuleKey.of(CommonRuleKeys.commonRepositoryForLang("java"), "InsufficientCoverage")) @@ -148,6 +174,21 @@ public class TrackerRawInputFactoryTest { assertInitializedIssue(input.getIssues().iterator().next()); } + @Test + public void ignore_issue_from_common_rule() throws Exception { + when(issueFilter.accept(any(DefaultIssue.class), eq(FILE))).thenReturn(false); + fileSourceRepository.addLines(FILE_REF, "line 1;", "line 2;"); + DefaultIssue ceIssue = new DefaultIssue() + .setRuleKey(RuleKey.of(CommonRuleKeys.commonRepositoryForLang("java"), "InsufficientCoverage")) + .setMessage("not enough coverage") + .setGap(10.0); + when(commonRuleEngine.process(FILE)).thenReturn(asList(ceIssue)); + + Input<DefaultIssue> input = underTest.create(FILE); + + assertThat(input.getIssues()).isEmpty(); + } + private void assertInitializedIssue(DefaultIssue issue) { assertThat(issue.componentKey()).isEqualTo(FILE.getKey()); assertThat(issue.componentUuid()).isEqualTo(FILE.getUuid()); diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/filter/IssueFilterTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/filter/IssueFilterTest.java new file mode 100644 index 00000000000..e4fae8291ce --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/filter/IssueFilterTest.java @@ -0,0 +1,215 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.server.computation.issue.filter; + +import com.google.common.base.Joiner; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.api.config.Settings; +import org.sonar.api.rule.RuleKey; +import org.sonar.core.issue.DefaultIssue; +import org.sonar.server.computation.batch.TreeRootHolderRule; +import org.sonar.server.computation.component.Component; +import org.sonar.server.computation.component.SettingsRepository; + +import static java.util.Arrays.asList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; +import static org.sonar.server.computation.component.Component.Type.FILE; +import static org.sonar.server.computation.component.ReportComponent.builder; + +public class IssueFilterTest { + + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + static final RuleKey XOO_X1 = RuleKey.of("xoo", "x1"); + static final RuleKey XOO_X2 = RuleKey.of("xoo", "x2"); + static final RuleKey XOO_X3 = RuleKey.of("xoo", "x3"); + + static final String PATH1 = "src/main/xoo/File1.xoo"; + static final String PATH2 = "src/main/xoo/File2.xoo"; + static final String PATH3 = "src/main/xoo/File3.xoo"; + + static final Component PROJECT = builder(Component.Type.PROJECT, 10).build(); + + static final Component COMPONENT_1 = builder(FILE, 1).setKey("File1").setPath(PATH1).build(); + static final Component COMPONENT_2 = builder(FILE, 2).setKey("File2").setPath(PATH2).build(); + static final Component COMPONENT_3 = builder(FILE, 3).setKey("File3").setPath(PATH3).build(); + + static final DefaultIssue ISSUE_1 = new DefaultIssue().setRuleKey(XOO_X1); + static final DefaultIssue ISSUE_2 = new DefaultIssue().setRuleKey(XOO_X2); + static final DefaultIssue ISSUE_3 = new DefaultIssue().setRuleKey(XOO_X3); + + @Rule + public TreeRootHolderRule treeRootHolder = new TreeRootHolderRule().setRoot(PROJECT); + + SettingsRepository settingsRepository = mock(SettingsRepository.class); + + @Test + public void accept_everything_when_no_filter_properties() throws Exception { + IssueFilter underTest = newIssueFilter(new Settings()); + + assertThat(underTest.accept(ISSUE_1, COMPONENT_1)).isTrue(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_2)).isTrue(); + assertThat(underTest.accept(ISSUE_3, COMPONENT_3)).isTrue(); + } + + @Test + public void ignore_all() throws Exception { + IssueFilter underTest = newIssueFilter(newSettings(asList("*", "**"), Collections.<String>emptyList())); + + assertThat(underTest.accept(ISSUE_1, COMPONENT_1)).isFalse(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_1)).isFalse(); + assertThat(underTest.accept(ISSUE_3, COMPONENT_1)).isFalse(); + } + + @Test + public void ignore_some_rule_and_component() throws Exception { + IssueFilter underTest = newIssueFilter(newSettings(asList("xoo:x1", "**/xoo/File1*"), Collections.<String>emptyList())); + + assertThat(underTest.accept(ISSUE_1, COMPONENT_1)).isFalse(); + assertThat(underTest.accept(ISSUE_1, COMPONENT_2)).isTrue(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_1)).isTrue(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_2)).isTrue(); + } + + @Test + public void ignore_many_rules() throws Exception { + IssueFilter underTest = newIssueFilter(newSettings( + asList("xoo:x1", "**/xoo/File1*", "xoo:x2", "**/xoo/File1*"), + Collections.<String>emptyList()) + ); + + assertThat(underTest.accept(ISSUE_1, COMPONENT_1)).isFalse(); + assertThat(underTest.accept(ISSUE_1, COMPONENT_2)).isTrue(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_1)).isFalse(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_2)).isTrue(); + } + + @Test + public void include_all() throws Exception { + IssueFilter underTest = newIssueFilter(newSettings(Collections.<String>emptyList(), asList("*", "**"))); + + assertThat(underTest.accept(ISSUE_1, COMPONENT_1)).isTrue(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_1)).isTrue(); + assertThat(underTest.accept(ISSUE_3, COMPONENT_1)).isTrue(); + } + + @Test + public void include_some_rule_and_component() throws Exception { + IssueFilter underTest = newIssueFilter(newSettings(Collections.<String>emptyList(), asList("xoo:x1", "**/xoo/File1*"))); + + assertThat(underTest.accept(ISSUE_1, COMPONENT_1)).isTrue(); + assertThat(underTest.accept(ISSUE_1, COMPONENT_2)).isFalse(); + // Issues on other rule are accepted + assertThat(underTest.accept(ISSUE_2, COMPONENT_1)).isTrue(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_2)).isTrue(); + } + + @Test + public void ignore_and_include_same_rule_and_component() throws Exception { + IssueFilter underTest = newIssueFilter(newSettings( + asList("xoo:x1", "**/xoo/File1*"), + asList("xoo:x1", "**/xoo/File1*"))); + + assertThat(underTest.accept(ISSUE_1, COMPONENT_1)).isFalse(); + assertThat(underTest.accept(ISSUE_1, COMPONENT_2)).isFalse(); + // Issues on other rule are accepted + assertThat(underTest.accept(ISSUE_2, COMPONENT_1)).isTrue(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_2)).isTrue(); + } + + @Test + public void include_many_rules() throws Exception { + IssueFilter underTest = newIssueFilter(newSettings( + Collections.<String>emptyList(), + asList("xoo:x1", "**/xoo/File1*", "xoo:x2", "**/xoo/File1*") + )); + + assertThat(underTest.accept(ISSUE_1, COMPONENT_1)).isTrue(); + assertThat(underTest.accept(ISSUE_1, COMPONENT_2)).isFalse(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_1)).isTrue(); + assertThat(underTest.accept(ISSUE_2, COMPONENT_2)).isFalse(); + } + + @Test + public void accept_project_issues() throws Exception { + IssueFilter underTest = newIssueFilter(newSettings( + asList("xoo:x1", "**/xoo/File1*"), + asList("xoo:x1", "**/xoo/File1*"))); + + assertThat(underTest.accept(ISSUE_1, PROJECT)).isTrue(); + assertThat(underTest.accept(ISSUE_2, PROJECT)).isTrue(); + } + + @Test + public void fail_when_only_rule_key_parameter() throws Exception { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("File path pattern cannot be empty. Please check 'sonar.issue.ignore.multicriteria' settings"); + + newIssueFilter(newSettings(asList("xoo:x1", ""), Collections.<String>emptyList())); + } + + @Test + public void fail_when_only_path_parameter() throws Exception { + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Rule key pattern cannot be empty. Please check 'sonar.issue.enforce.multicriteria' settings"); + + newIssueFilter(newSettings(Collections.<String>emptyList(), asList("", "**"))); + } + + private IssueFilter newIssueFilter(Settings settings) { + when(settingsRepository.getSettings(PROJECT)).thenReturn(settings); + return new IssueFilter(treeRootHolder, settingsRepository); + } + + private static Settings newSettings(List<String> exclusionsProperties, List<String> inclusionsProperties) { + Settings settings = new Settings(); + if (!exclusionsProperties.isEmpty()) { + addProperties(exclusionsProperties, "ignore", settings); + } + if (!inclusionsProperties.isEmpty()) { + addProperties(inclusionsProperties, "enforce", settings); + } + return settings; + } + + private static void addProperties(List<String> properties, String property, Settings settings) { + if (!properties.isEmpty()) { + List<Integer> indexes = new ArrayList<>(); + int index = 1; + for (int i = 0; i < properties.size(); i += 2) { + settings.setProperty("sonar.issue." + property + ".multicriteria." + index + ".ruleKey", properties.get(i)); + settings.setProperty("sonar.issue." + property + ".multicriteria." + index + ".resourceKey", properties.get(i + 1)); + indexes.add(index); + index++; + } + settings.setProperty("sonar.issue." + property + ".multicriteria", Joiner.on(",").join(indexes)); + } + } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/computation/issue/filter/IssuePatternTest.java b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/filter/IssuePatternTest.java new file mode 100644 index 00000000000..e0ace6afa66 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/computation/issue/filter/IssuePatternTest.java @@ -0,0 +1,67 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact 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.server.computation.issue.filter; + +import org.junit.Test; +import org.sonar.api.rule.RuleKey; +import org.sonar.api.rules.Rule; + +import static org.assertj.core.api.Assertions.assertThat; + +public class IssuePatternTest { + + @Test + public void match_file() { + String javaFile = "org/foo/Bar.xoo"; + assertThat(new IssuePattern("org/foo/Bar*", "*").matchComponent(javaFile)).isTrue(); + assertThat(new IssuePattern("org/foo/*", "*").matchComponent(javaFile)).isTrue(); + assertThat(new IssuePattern("**/*ar*", "*").matchComponent(javaFile)).isTrue(); + assertThat(new IssuePattern("org/**/?ar.xoo", "*").matchComponent(javaFile)).isTrue(); + assertThat(new IssuePattern("**", "*").matchComponent(javaFile)).isTrue(); + + assertThat(new IssuePattern("org/other/Hello", "*").matchComponent(javaFile)).isFalse(); + assertThat(new IssuePattern("org/foo/Hello", "*").matchComponent(javaFile)).isFalse(); + assertThat(new IssuePattern("org/**/??ar.xoo", "*").matchComponent(javaFile)).isFalse(); + assertThat(new IssuePattern("org/**/??ar.xoo", "*").matchComponent(null)).isFalse(); + assertThat(new IssuePattern("org/**/??ar.xoo", "*").matchComponent("plop")).isFalse(); + } + + @Test + public void match_rule() { + RuleKey rule = Rule.create("checkstyle", "IllegalRegexp", "").ruleKey(); + assertThat(new IssuePattern("*", "*").matchRule(rule)).isTrue(); + assertThat(new IssuePattern("*", "checkstyle:*").matchRule(rule)).isTrue(); + assertThat(new IssuePattern("*", "checkstyle:IllegalRegexp").matchRule(rule)).isTrue(); + assertThat(new IssuePattern("*", "checkstyle:Illegal*").matchRule(rule)).isTrue(); + assertThat(new IssuePattern("*", "*:*Illegal*").matchRule(rule)).isTrue(); + + assertThat(new IssuePattern("*", "pmd:IllegalRegexp").matchRule(rule)).isFalse(); + assertThat(new IssuePattern("*", "pmd:*").matchRule(rule)).isFalse(); + assertThat(new IssuePattern("*", "*:Foo*IllegalRegexp").matchRule(rule)).isFalse(); + } + + @Test + public void test_to_string() { + IssuePattern pattern = new IssuePattern("*", "checkstyle:*"); + + assertThat(pattern.toString()).isEqualTo( + "IssuePattern{componentPattern=*, rulePattern=checkstyle:*}"); + } +} |