diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CodeQualityConfigurator.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CodeQualityConfigurator.groovy index d91e157..c9884d8 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CodeQualityConfigurator.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/CodeQualityConfigurator.groovy @@ -12,12 +12,13 @@ abstract class CodeQualityConfigurator excludes = [] + protected final SourceFilter filter protected CodeQualityConfigurator(Project project, Violations violations, EvaluateViolationsTask evaluateViolations) { this.project = project this.violations = violations this.evaluateViolations = evaluateViolations + this.filter = new SourceFilter(project) } void execute() { @@ -25,7 +26,7 @@ abstract class CodeQualityConfigurator excludes.add(pattern) } + ext.exclude = { Object rule -> filter.exclude(rule) } config.delegate = it config() } @@ -60,6 +61,9 @@ abstract class CodeQualityConfigurator getTaskClass() - protected abstract void configureTask(T task) + protected void configureTask(T task) { + task.group = 'verification' + filter.applyTo(task) + } } diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/SourceFilter.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/SourceFilter.groovy new file mode 100644 index 0000000..e9bf1c2 --- /dev/null +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/SourceFilter.groovy @@ -0,0 +1,43 @@ +package com.novoda.staticanalysis.internal + +import org.gradle.api.Project +import org.gradle.api.file.FileCollection +import org.gradle.api.file.FileTree +import org.gradle.api.tasks.SourceTask + +class SourceFilter { + + private final Project project + private final List excludes = [] + + SourceFilter(Project project) { + this.project = project + } + + void exclude(Object exclude) { + excludes.add(exclude) + } + + void applyTo(SourceTask task) { + excludes.each { exclude -> + if (exclude instanceof File) { + apply(task, project.files(exclude)) + } else if (exclude instanceof FileCollection) { + apply(task, exclude) + } else if (exclude instanceof Iterable) { + apply(task, exclude.inject(null, accumulateIntoTree()) as FileTree) + } else { + task.exclude(exclude as String) + } + } + } + + private void apply(SourceTask task, FileCollection excludedFiles) { + task.source = task.source.findAll { !excludedFiles.contains(it) } + } + + private def accumulateIntoTree() { + return { tree, file -> tree?.plus(project.fileTree(file)) ?: project.fileTree(file) } + } + +} diff --git a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleConfigurator.groovy b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleConfigurator.groovy index 52dbda4..b4099c0 100644 --- a/plugin/src/main/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleConfigurator.groovy +++ b/plugin/src/main/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleConfigurator.groovy @@ -65,11 +65,10 @@ class CheckstyleConfigurator extends CodeQualityConfigurator { @Override protected void configureTask(Pmd pmd) { - pmd.group = 'verification' + super.configureTask(pmd) pmd.ignoreFailures = true pmd.metaClass.getLogger = { QuietLogger.INSTANCE } - pmd.exclude(excludes) pmd.doLast { File xmlReportFile = pmd.reports.xml.destination File htmlReportFile = new File(xmlReportFile.absolutePath - '.xml' + '.html') diff --git a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/SourceFilterTest.groovy b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/SourceFilterTest.groovy new file mode 100644 index 0000000..f7ee41e --- /dev/null +++ b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/SourceFilterTest.groovy @@ -0,0 +1,105 @@ +package com.novoda.staticanalysis.internal + +import org.gradle.api.Project +import org.gradle.api.tasks.SourceTask +import org.gradle.testfixtures.ProjectBuilder +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TemporaryFolder + +import static com.google.common.truth.Truth.assertThat +import static com.novoda.test.Fixtures.Checkstyle.SOURCES_WITH_ERRORS +import static com.novoda.test.Fixtures.Checkstyle.SOURCES_WITH_WARNINGS + +class SourceFilterTest { + + @Rule + public TemporaryFolder temporaryFolder = new TemporaryFolder() + + private Project project + private SourceFilter filter + + @Before + public void setUp() { + project = ProjectBuilder.builder() + .withProjectDir(temporaryFolder.newFolder()) + .build() + filter = new SourceFilter(project) + } + + @Test + public void shouldKeepAllSourcesWhenNoExcludeFilterProvided() { + SourceTask task = givenTaskWith(errorsSources + warningsSources) + + filter.applyTo(task) + + assertThat(task.source).containsExactlyElementsIn(errorsSources + warningsSources) + } + + @Test + public void shouldRemoveFilesMatchingThePatternWhenExcludePatternProvided() { + SourceTask task = givenTaskWith(errorsSources + warningsSources) + filter.exclude('**/*.java') + + filter.applyTo(task) + + assertThat(task.source).isEmpty() + } + + @Test + public void shouldRemoveFilesInSpecifiedFileCollectionWhenExcludeFileCollectionProvided() { + SourceTask task = givenTaskWith(errorsSources + warningsSources) + filter.exclude(project.fileTree(SOURCES_WITH_ERRORS)) + + filter.applyTo(task) + + assertThat(task.source).containsExactlyElementsIn(warningsSources) + } + + @Test + public void shouldRemoveFilesInSpecifiedFileCollectionWhenExcludeSourceSetProvided() { + project.apply plugin: 'java' + project.sourceSets.main { + java { + srcDir project.file(SOURCES_WITH_ERRORS) + } + } + SourceTask task = givenTaskWith(errorsSources) + filter.exclude(project.sourceSets.main.java.srcDirs) + + filter.applyTo(task) + + assertThat(task.source).isEmpty() + } + + @Test + public void shouldRemoveFilesInSpecifiedFileCollectionWhenSourceFileExcluded() { + project.apply plugin: 'java' + project.sourceSets.main { + java { + srcDir project.file(SOURCES_WITH_ERRORS) + } + } + SourceTask task = givenTaskWith(errorsSources) + filter.exclude(new File(SOURCES_WITH_ERRORS, 'Greeter.java')) + + filter.applyTo(task) + + assertThat(task.source).isEmpty() + } + + private SourceTask givenTaskWith(Iterable files) { + project.tasks.create('someSourceTask', SourceTask) { + source = project.files(files) + } + } + + private Set getErrorsSources() { + project.fileTree(SOURCES_WITH_ERRORS).files + } + + private Set getWarningsSources() { + project.fileTree(SOURCES_WITH_WARNINGS).files + } +} diff --git a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleIntegrationTest.groovy b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleIntegrationTest.groovy index e34c20e..769ea5a 100644 --- a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleIntegrationTest.groovy +++ b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/checkstyle/CheckstyleIntegrationTest.groovy @@ -108,7 +108,7 @@ public class CheckstyleIntegrationTest { } @Test - public void shouldNotFailBuildWhenCheckstyleConfiguredIgnoreSourceSets() { + public void shouldNotFailBuildWhenCheckstyleConfiguredToExcludePattern() { TestProject.Result result = projectRule.newProject() .withSourceSet('main', Fixtures.Checkstyle.SOURCES_WITH_WARNINGS) .withSourceSet('test', Fixtures.Checkstyle.SOURCES_WITH_ERRORS) @@ -125,6 +125,42 @@ public class CheckstyleIntegrationTest { result.buildFile('reports/checkstyle/main.html')) } + @Test + public void shouldNotFailBuildWhenCheckstyleConfiguredToIgnoreFaultySourceFolder() { + TestProject.Result result = projectRule.newProject() + .withSourceSet('main', Fixtures.Checkstyle.SOURCES_WITH_WARNINGS) + .withSourceSet('test', Fixtures.Checkstyle.SOURCES_WITH_ERRORS) + .withFile(Fixtures.Checkstyle.MODULES, 'config/checkstyle/checkstyle.xml') + .withPenalty('''{ + maxWarnings = 1 + maxErrors = 0 + }''') + .withCheckstyle(checkstyle(DEFAULT_CONFIG, "exclude project.fileTree('${Fixtures.Checkstyle.SOURCES_WITH_ERRORS}')")) + .build('check') + + assertThat(result.logs).doesNotContainLimitExceeded() + assertThat(result.logs).containsCheckstyleViolations(0, 1, + result.buildFile('reports/checkstyle/main.html')) + } + + @Test + public void shouldNotFailBuildWhenCheckstyleConfiguredToIgnoreFaultySourceSet() { + TestProject.Result result = projectRule.newProject() + .withSourceSet('main', Fixtures.Checkstyle.SOURCES_WITH_WARNINGS) + .withSourceSet('test', Fixtures.Checkstyle.SOURCES_WITH_ERRORS) + .withFile(Fixtures.Checkstyle.MODULES, 'config/checkstyle/checkstyle.xml') + .withPenalty('''{ + maxWarnings = 1 + maxErrors = 0 + }''') + .withCheckstyle(checkstyle(DEFAULT_CONFIG, "exclude ${projectRule.printSourceSet('test')}.java.srcDirs")) + .build('check') + + assertThat(result.logs).doesNotContainLimitExceeded() + assertThat(result.logs).containsCheckstyleViolations(0, 1, + result.buildFile('reports/checkstyle/main.html')) + } + @Test public void shouldNotFailWhenCheckstyleNotConfigured() { TestProject.Result result = projectRule.newProject() @@ -170,7 +206,6 @@ public class CheckstyleIntegrationTest { result.buildFile('reports/checkstyle/main.html')) } - private static String checkstyle(String configFile, String... configs) { """checkstyle { ${configFile} diff --git a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsIntegrationTest.groovy b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsIntegrationTest.groovy index b01a16e..6f2979a 100644 --- a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsIntegrationTest.groovy +++ b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/findbugs/FindbugsIntegrationTest.groovy @@ -132,7 +132,7 @@ class FindbugsIntegrationTest { } @Test - public void shouldNotFailBuildWhenFindbugsConfiguredToIgnoreFaultySourceSets() { + public void shouldNotFailBuildWhenFindbugsConfiguredToExcludePattern() { TestProject.Result result = projectRule.newProject() .withSourceSet('debug', SOURCES_WITH_LOW_VIOLATION, SOURCES_WITH_MEDIUM_VIOLATION) .withSourceSet('release', SOURCES_WITH_HIGH_VIOLATION) @@ -148,6 +148,40 @@ class FindbugsIntegrationTest { result.buildFile('reports/findbugs/debug.html')) } + @Test + public void shouldNotFailBuildWhenFindbugsConfiguredToIgnoreFaultySourceFolder() { + TestProject.Result result = projectRule.newProject() + .withSourceSet('debug', SOURCES_WITH_LOW_VIOLATION, SOURCES_WITH_MEDIUM_VIOLATION) + .withSourceSet('release', SOURCES_WITH_HIGH_VIOLATION) + .withPenalty('''{ + maxErrors = 0 + maxWarnings = 10 + }''') + .withFindbugs("findbugs { exclude project.fileTree('${SOURCES_WITH_HIGH_VIOLATION}') }") + .build('check') + + assertThat(result.logs).doesNotContainLimitExceeded() + assertThat(result.logs).containsFindbugsViolations(0, 2, + result.buildFile('reports/findbugs/debug.html')) + } + + @Test + public void shouldNotFailBuildWhenFindbugsConfiguredToIgnoreFaultySourceSet() { + TestProject.Result result = projectRule.newProject() + .withSourceSet('debug', SOURCES_WITH_LOW_VIOLATION, SOURCES_WITH_MEDIUM_VIOLATION) + .withSourceSet('release', SOURCES_WITH_HIGH_VIOLATION) + .withPenalty('''{ + maxErrors = 0 + maxWarnings = 10 + }''') + .withFindbugs("findbugs { exclude ${projectRule.printSourceSet('release')}.java.srcDirs }") + .build('check') + + assertThat(result.logs).doesNotContainLimitExceeded() + assertThat(result.logs).containsFindbugsViolations(0, 2, + result.buildFile('reports/findbugs/debug.html')) + } + @Test public void shouldCollectDuplicatedFindbugsWarningsAndErrorsAcrossAndroidVariantsForSharedSourceSets() { TestProject project = projectRule.newProject() diff --git a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/pmd/PmdIntegrationTest.groovy b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/pmd/PmdIntegrationTest.groovy index 3be3b92..6758848 100644 --- a/plugin/src/test/groovy/com/novoda/staticanalysis/internal/pmd/PmdIntegrationTest.groovy +++ b/plugin/src/test/groovy/com/novoda/staticanalysis/internal/pmd/PmdIntegrationTest.groovy @@ -130,7 +130,7 @@ public class PmdIntegrationTest { } @Test - public void shouldNotFailBuildWhenPmdConfiguredToIgnoreFaultySourceSets() { + public void shouldNotFailBuildWhenPmdConfiguredToExcludePatterns() { TestProject.Result result = projectRule.newProject() .withSourceSet('main', Fixtures.Pmd.SOURCES_WITH_PRIORITY_1_VIOLATION) .withSourceSet('main2', Fixtures.Pmd.SOURCES_WITH_PRIORITY_2_VIOLATION) @@ -145,6 +145,42 @@ public class PmdIntegrationTest { assertThat(result.logs).doesNotContainPmdViolations() } + @Test + public void shouldNotFailBuildWhenPmdConfiguredToIgnoreFaultySourceFolders() { + TestProject.Result result = projectRule.newProject() + .withSourceSet('main', Fixtures.Pmd.SOURCES_WITH_PRIORITY_1_VIOLATION) + .withSourceSet('main2', Fixtures.Pmd.SOURCES_WITH_PRIORITY_2_VIOLATION) + .withPenalty('''{ + maxWarnings = 0 + maxErrors = 0 + }''') + .withPmd(pmd("project.files('${Fixtures.Pmd.RULES.path}')", + "exclude project.fileTree('${Fixtures.Pmd.SOURCES_WITH_PRIORITY_1_VIOLATION}')", + "exclude project.fileTree('${Fixtures.Pmd.SOURCES_WITH_PRIORITY_2_VIOLATION}')")) + .build('check') + + assertThat(result.logs).doesNotContainLimitExceeded() + assertThat(result.logs).doesNotContainPmdViolations() + } + + @Test + public void shouldNotFailBuildWhenPmdConfiguredToIgnoreFaultySourceSets() { + TestProject.Result result = projectRule.newProject() + .withSourceSet('main', Fixtures.Pmd.SOURCES_WITH_PRIORITY_1_VIOLATION) + .withSourceSet('main2', Fixtures.Pmd.SOURCES_WITH_PRIORITY_2_VIOLATION) + .withPenalty('''{ + maxWarnings = 0 + maxErrors = 0 + }''') + .withPmd(pmd("project.files('${Fixtures.Pmd.RULES.path}')", + "exclude ${projectRule.printSourceSet('main')}.java.srcDirs", + "exclude ${projectRule.printSourceSet('main2')}.java.srcDirs")) + .build('check') + + assertThat(result.logs).doesNotContainLimitExceeded() + assertThat(result.logs).doesNotContainPmdViolations() + } + @Test public void shouldNotFailBuildWhenPmdNotConfigured() { TestProject.Result result = projectRule.newProject() diff --git a/plugin/src/test/groovy/com/novoda/test/TestProjectRule.groovy b/plugin/src/test/groovy/com/novoda/test/TestProjectRule.groovy index 460abc9..b8f7a4e 100644 --- a/plugin/src/test/groovy/com/novoda/test/TestProjectRule.groovy +++ b/plugin/src/test/groovy/com/novoda/test/TestProjectRule.groovy @@ -7,18 +7,20 @@ import org.junit.runners.model.Statement final class TestProjectRule implements TestRule { private final Closure projectFactory + private final Closure sourceSetNameFactory private TestProject project static TestProjectRule forJavaProject() { - new TestProjectRule({ new TestJavaProject() }) + new TestProjectRule({ new TestJavaProject() }, { String name -> "project.sourceSets.$name" }) } static TestProjectRule forAndroidProject() { - new TestProjectRule({ new TestAndroidProject() }) + new TestProjectRule({ new TestAndroidProject() }, { String name -> "project.android.sourceSets.$name" }) } - private TestProjectRule(Closure projectFactory) { + private TestProjectRule(Closure projectFactory, Closure sourceSetNameFactory) { this.projectFactory = projectFactory + this.sourceSetNameFactory = sourceSetNameFactory } public TestProject newProject() { @@ -26,6 +28,10 @@ final class TestProjectRule implements TestRule { return project } + public String printSourceSet(String name) { + sourceSetNameFactory.call(name) + } + @Override Statement apply(Statement base, Description description) { return new Statement() {