/* * SonarQube Java * Copyright (C) 2012-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.java.filters; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import org.apache.commons.io.FileUtils; import org.assertj.core.api.Fail; import org.sonar.api.rule.RuleKey; import org.sonar.api.scan.issue.filter.FilterableIssue; import org.sonar.api.utils.AnnotationUtils; import org.sonar.check.Rule; import org.sonar.java.AnalyzerMessage; import org.sonar.java.JavaConfiguration; import org.sonar.java.ast.JavaAstScanner; import org.sonar.java.ast.visitors.SubscriptionVisitor; import org.sonar.java.model.VisitorsBridgeForTests; import org.sonar.plugins.java.api.JavaCheck; import org.sonar.plugins.java.api.tree.SyntaxTrivia; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.squidbridge.api.CodeVisitor; import java.io.File; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class FilterVerifier { public static void verify(String filename, JavaIssueFilter filter, CodeVisitor... extraCodeVisitors) { // set the component to the filter filter.setComponentKey(filename); IssueCollector issueCollector = new IssueCollector(); ArrayList<CodeVisitor> codeVisitors = Lists.<CodeVisitor>newArrayList(filter, issueCollector); // instantiate the rules filtered by the filter codeVisitors.addAll(instantiateRules(filter.filteredRules())); for (CodeVisitor codeVisitor : extraCodeVisitors) { codeVisitors.add(codeVisitor); } Collection<File> classpath = FileUtils.listFiles(new File("target/test-jars"), new String[] {"jar", "zip"}, true); VisitorsBridgeForTests visitorsBridge = new VisitorsBridgeForTests(codeVisitors, Lists.newArrayList(classpath), null); JavaAstScanner.scanSingleFileForTests(new File(filename), visitorsBridge, new JavaConfiguration(Charset.forName("UTF-8"))); VisitorsBridgeForTests.TestJavaFileScannerContext testJavaFileScannerContext = visitorsBridge.lastCreatedTestContext(); Multimap<Integer, String> issuesByLines = HashMultimap.create(); for (AnalyzerMessage analyzerMessage : testJavaFileScannerContext.getIssues()) { Integer issueLine = analyzerMessage.getLine(); String ruleKey = AnnotationUtils.getAnnotation(analyzerMessage.getCheck().getClass(), Rule.class).key(); FilterableIssue issue = mock(FilterableIssue.class); when(issue.ruleKey()).thenReturn(RuleKey.of("repo", ruleKey)); when(issue.componentKey()).thenReturn(filename); when(issue.line()).thenReturn(issueLine); if (issueCollector.rejectedIssuesLines.contains(issueLine)) { assertThat(filter.accept(issue)) .overridingErrorMessage("Line #" + issueLine + " has been marked with 'NoIssue' but issue of rule '" + ruleKey + "' has been accepted!") .isFalse(); } else if (issueCollector.acceptedIssuesLines.contains(issueLine)) { // force check on accepted issues assertThat(filter.accept(issue)) .overridingErrorMessage("Line #" + issueLine + " has been marked with 'WithIssue' but no issue have been raised!") .isTrue(); } else { issuesByLines.put(issueLine, ruleKey); } } if (!issuesByLines.isEmpty()) { List<Integer> lines = Lists.newArrayList(issuesByLines.keySet()); Collections.sort(lines); StringBuilder builder = new StringBuilder(); for (Integer line : lines) { builder.append("\n#" + line + ": " + issuesByLines.get(line).toString()); } Fail.fail("The following lines have not been marked with 'WithIssue' or 'NoIssue' and raised issues:" + builder.toString()); } } private static Set<JavaCheck> instantiateRules(Set<Class<? extends JavaCheck>> filteredRules) { Set<JavaCheck> rules = new HashSet<>(); for (Class<? extends JavaCheck> rule : filteredRules) { try { rules.add(rule.newInstance()); } catch (InstantiationException | IllegalAccessException e) { Fail.fail("Unable to instantiate rule " + rule.getCanonicalName()); } } return rules; } private static class IssueCollector extends SubscriptionVisitor { private final Set<Integer> rejectedIssuesLines = new HashSet<>(); private final Set<Integer> acceptedIssuesLines = new HashSet<>(); @Override public List<Tree.Kind> nodesToVisit() { return ImmutableList.of(Tree.Kind.TRIVIA); } @Override public void visitTrivia(SyntaxTrivia syntaxTrivia) { String comment = syntaxTrivia.comment().trim(); if (comment.endsWith("NoIssue")) { rejectedIssuesLines.add(syntaxTrivia.startLine()); } else if (comment.endsWith("WithIssue")) { acceptedIssuesLines.add(syntaxTrivia.startLine()); } } } }