org.gradle.api.plugins.quality.findbugs.AbstractFindBugsPluginIntegrationTest.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.api.plugins.quality.findbugs
import org.gradle.integtests.fixtures.AbstractIntegrationSpec
import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
import org.gradle.util.Matchers
import org.gradle.util.Resources
import org.hamcrest.Matcher
import org.junit.Rule
import spock.lang.IgnoreIf
import spock.lang.Issue
import static org.gradle.util.Matchers.containsLine
import static org.gradle.util.TextUtil.normaliseFileSeparators
import static org.hamcrest.Matchers.containsString
import static org.hamcrest.Matchers.startsWith
abstract class AbstractFindBugsPluginIntegrationTest extends AbstractIntegrationSpec {
@Rule
public final Resources resources = new Resources()
def setup() {
writeBuildFile()
}
def "default findbugs version can be inspected"() {
expect:
succeeds("dependencies", "--configuration", "findbugs")
output.contains "com.google.code.findbugs:findbugs:"
}
def "analyze good code"() {
goodCode()
expect:
succeeds("check")
file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
}
void "analyze bad code"() {
badCode()
expect:
fails("check")
failure.assertHasDescription("Execution failed for task ':findbugsMain'.")
failure.assertThatCause(startsWith("FindBugs rule violations were found. See the report at:"))
file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.BadClass"))
}
void "can ignore failures"() {
badCode()
buildFile << """
findbugs {
ignoreFailures = true
}
"""
expect:
succeeds("check")
output.contains("FindBugs rule violations were found. See the report at:")
file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.BadClass"))
file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.BadClassTest"))
}
@IgnoreIf({GradleContextualExecuter.parallel})
def "is incremental"() {
given:
goodCode()
expect:
succeeds("findbugsMain") && ":findbugsMain" in nonSkippedTasks
succeeds(":findbugsMain") && ":findbugsMain" in skippedTasks
when:
file("build/reports/findbugs/main.xml").delete()
then:
succeeds("findbugsMain") && ":findbugsMain" in nonSkippedTasks
}
def "cannot generate multiple reports"() {
given:
buildFile << """
findbugsMain.reports {
xml.enabled true
html.enabled true
}
"""
and:
goodCode()
expect:
fails "findbugsMain"
failure.assertHasCause "FindBugs tasks can only have one report enabled"
}
def "can use optional arguments"() {
given:
buildFile << """
findbugs {
effort 'max'
reportLevel 'high'
includeFilterConfig resources.text.fromFile('include.xml')
excludeFilter file('exclude.xml')
visitors = ['FindDeadLocalStores', 'UnreadFields']
omitVisitors = ['WaitInLoop', 'UnnecessaryMath']
}
findbugsMain.reports {
xml.enabled true
}
"""
and:
goodCode()
badCode()
and:
writeFilterFile('include.xml', '.*')
writeFilterFile('exclude.xml', 'org\\.gradle\\.Bad.*')
expect:
succeeds("check")
file("build/reports/findbugs/main.xml").assertContents(containsClass("Class1.java"))
file("build/reports/findbugs/test.xml").assertContents(containsClass("Class1Test.java"))
}
void "excludes baseline bug for matching bug instance"() {
given:
String baselineExcludeFilename = 'baselineExclude.xml'
buildFile << """
findbugs {
excludeBugsFilterConfig resources.text.fromFile('${baselineExcludeFilename}')
}
findbugsMain.reports {
xml.enabled true
}
"""
and:
badCode()
when:
writeBaselineBugsFilterFile(baselineExcludeFilename, new JavaClass('org.gradle', 'BadClass'))
then:
succeeds("check")
file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.BadClass"))
when:
writeBaselineBugsFilterFile(baselineExcludeFilename, new JavaClass('org.gradle', 'SomeOtherClass'))
then:
fails("check")
failure.assertHasDescription("Execution failed for task ':findbugsMain'.")
failure.assertThatCause(startsWith("FindBugs rule violations were found. See the report at:"))
file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.BadClass"))
}
def "can generate html reports"() {
given:
buildFile << """
findbugsMain.reports {
xml.enabled false
html.enabled true
}
"""
and:
goodCode()
when:
run "findbugsMain"
then:
file("build/reports/findbugs/main.html").exists()
}
def "can generate html reports with a custom stylesheet"() {
given:
buildFile << """
findbugsMain.reports {
xml.enabled false
html.enabled true
html.stylesheet resources.text.fromFile('${sampleStylesheet()}')
}
"""
and:
goodCode()
when:
run "findbugsMain"
then:
file("build/reports/findbugs/main.html").exists()
file("build/reports/findbugs/main.html").assertContents(containsString("A custom Findbugs stylesheet"))
}
def "can generate xml with messages reports"() {
given:
buildFile << """
findbugsMain.reports {
xml.enabled true
xml.withMessages true
html.enabled false
}
findbugsMain.ignoreFailures true
"""
and:
badCode()
when:
run "findbugsMain"
then:
file("build/reports/findbugs/main.xml").exists()
containsXmlMessages(file("build/reports/findbugs/main.xml"))
}
def "can generate no reports"() {
given:
buildFile << """
findbugsMain.reports {
xml.enabled false
html.enabled false
}
"""
and:
goodCode()
expect:
succeeds "findbugsMain"
and:
!file("build/reports/findbugs/main.html").exists()
!file("build/reports/findbugs/main.xml").exists()
}
def "can analyze a lot of classes"() {
goodCode(800)
expect:
succeeds("check")
file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class800"))
file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.Class1Test"))
file("build/reports/findbugs/test.xml").assertContents(containsClass("org.gradle.Class800Test"))
}
@IgnoreIf({GradleContextualExecuter.parallel})
def "is incremental for reporting settings"() {
given:
buildFile << """
findbugsMain.reports {
xml.enabled true
}
"""
and:
goodCode()
when:
succeeds "findbugsMain"
then:
file("build/reports/findbugs/main.xml").exists()
":findbugsMain" in nonSkippedTasks
!(":findbugsMain" in skippedTasks)
when:
succeeds "findbugsMain"
then:
file("build/reports/findbugs/main.xml").exists()
!(":findbugsMain" in nonSkippedTasks)
":findbugsMain" in skippedTasks
when:
buildFile << """
findbugsMain.reports {
xml.enabled false
}
"""
succeeds "findbugsMain"
then:
file("build/reports/findbugs/main.xml").exists()
":findbugsMain" in nonSkippedTasks
!(":findbugsMain" in skippedTasks)
}
@IgnoreIf({GradleContextualExecuter.parallel})
def "is incremental for withMessage"() {
given:
buildFile << """
findbugsMain {
reports {
xml.enabled true
xml.withMessages true
}
ignoreFailures true
}
"""
and:
badCode()
when:
succeeds "findbugsMain"
then:
file("build/reports/findbugs/main.xml").exists()
containsXmlMessages(file("build/reports/findbugs/main.xml"))
":findbugsMain" in nonSkippedTasks
!(":findbugsMain" in skippedTasks)
when:
succeeds "findbugsMain"
then:
file("build/reports/findbugs/main.xml").exists()
containsXmlMessages(file("build/reports/findbugs/main.xml"))
!(":findbugsMain" in nonSkippedTasks)
":findbugsMain" in skippedTasks
when:
buildFile << """
findbugsMain {
reports {
xml.enabled true
xml.withMessages false
}
ignoreFailures true
}
"""
succeeds "findbugsMain"
then:
file("build/reports/findbugs/main.xml").exists()
!containsXmlMessages(file("build/reports/findbugs/main.xml"))
":findbugsMain" in nonSkippedTasks
!(":findbugsMain" in skippedTasks)
}
@IgnoreIf({GradleContextualExecuter.parallel})
def "is withMessage ignored for non-XML report setting"() {
given:
buildFile << """
findbugsMain {
reports {
xml.enabled false
xml.withMessages true
html.enabled true
}
}
"""
and:
goodCode()
when:
succeeds "findbugsMain"
then:
!file("build/reports/findbugs/main.xml").exists()
file("build/reports/findbugs/main.html").exists()
when:
buildFile << """
findbugsMain.reports {
xml.withMessages false
}
"""
and:
succeeds "findbugsMain"
then:
!file("build/reports/findbugs/main.xml").exists()
file("build/reports/findbugs/main.html").exists()
!(":findbugsMain" in nonSkippedTasks)
":findbugsMain" in skippedTasks
}
@Issue("https://issues.gradle.org/browse/GRADLE-3214")
def "Thrown java.lang.Error due to missing transitive dependency is handled and fails the build"() {
given:
buildFile << 'configurations.findbugs.transitive = false'
and:
goodCode()
expect:
fails("check")
failure.assertHasDescription "Execution failed for task ':findbugsMain'."
failure.assertHasCause 'Failed to run Gradle FindBugs Worker'
failure.assertThatCause(Matchers.matchesRegexp("org[\\./]apache[\\./]bcel[\\./]classfile[\\./]ClassFormatException"))
}
def "valid adjustPriority extra args"() {
given:
file("src/main/java/org/gradle/ClassUsingCaseConversion.java") <<
'package org.gradle; public class ClassUsingCaseConversion { public boolean useConversion() { return "Hi".toUpperCase().equals("HI"); } }'
expect:
succeeds("findbugsMain")
when:
// Test extraArgs using DM_CONVERT_CASE which FindBugs treats as a LOW confidence warning. We will use
// extraArgs to boost the confidence which should make it be reported
buildFile << """
findbugsMain {
extraArgs '-adjustPriority', 'DM_CONVERT_CASE=raise,DM_CONVERT_CASE=raise'
}
"""
then:
fails("findbugsMain")
failure.assertHasDescription("Execution failed for task ':findbugsMain'.")
failure.assertThatCause(startsWith("FindBugs rule violations were found. See the report at:"))
file("build/reports/findbugs/main.xml").exists()
file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.ClassUsingCaseConversion"))
file("build/reports/findbugs/main.xml").assertContents(containsString("DM_CONVERT_CASE"))
}
def "fails when given invalid extraArgs"() {
given:
goodCode()
and:
buildFile << """
findbugsMain {
extraArgs 'gobbledygook'
}
"""
expect:
fails "check"
failure.assertHasCause 'FindBugs encountered an error.'
failure.assertHasDescription "Execution failed for task ':findbugsMain'."
}
def "valid jvm args"() {
given:
badCode()
buildFile << extensionProgressConfiguration(true)
// We don't have many jvm args to use for FindBugs, see:
// http://findbugs.sourceforge.net/manual/running.html
// https://github.com/findbugsproject/findbugs/blob/master/findbugs/build.xml#L771
buildFile << '''
findbugs {
jvmArgs = ['-Duser.language=fr']
}
findbugsMain.reports {
html.enabled true
xml.enabled false
}
'''
fails('findbugsMain')
expect:
file('build/reports/findbugs/main.html').text.contains('DM_EXIT: La méthode invoque System.exit(...)') // <- this is french
}
def "fails when given invalid jvmArgs"() {
given:
goodCode()
and:
buildFile << """
findbugsMain {
jvmArgs '-XInvalid'
}
"""
// java.io.IOException: The pipe is being closed on windows
executer.withStackTraceChecksDisabled()
expect:
fails("check")
failure.assertHasDescription("Execution failed for task ':findbugsMain'.")
failure.assertHasCause("Failed to run Gradle FindBugs Worker")
failure.assertThatCause(Matchers.matchesRegexp("Process 'Gradle FindBugs Worker [0-9]+' finished with non-zero exit value 1"))
}
@IgnoreIf({GradleContextualExecuter.parallel})
def "out-of-date with mixed Java and Groovy sources"() {
given:
goodCode()
buildFile << """
apply plugin: 'groovy'
dependencies {
compile localGroovy()
}
"""
file("src/main/groovy/org/gradle/Groovy1.groovy") << """
package org.gradle
class Groovy1 {
boolean is() { true }
}
"""
expect:
succeeds("check")
file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Groovy1"))
file("build/reports/findbugs/main.xml").assertContents(containsClass("org.gradle.Class1"))
when:
file("src/main/java/org/gradle/Class1.java").text = """
package org.gradle;
public class Class1 {
public boolean isFoo(Object arg) { return true; }
public boolean isNotFoo(Object arg) { return false; }
}
"""
then:
succeeds("check")
result.assertTaskNotSkipped(":findbugsMain")
when:
file("src/main/groovy/org/gradle/Groovy1.groovy").text = """
package org.gradle
class Groovy1 {
boolean is() { true }
boolean isNot() { false }
}
"""
then:
succeeds("check")
result.assertTaskNotSkipped(":findbugsMain")
}
def "does not fail if resources are generated into classes"() {
given:
goodCode()
buildFile << """
compileJava {
doLast {
def manifest = new File(destinationDir, "META-INF/MANIFEST.MF")
manifest.parentFile.mkdirs()
manifest.text = "manifest"
def properties = new File(destinationDir, "com/example/service.properties")
properties.parentFile.mkdirs()
properties.text = "someProp=value"
}
}
"""
when:
succeeds("check")
then:
result.assertNotOutput("Wrong magic bytes")
}
@Issue("https://github.com/gradle/gradle/issues/1307")
def "does not render progress output by default"() {
given:
goodCode()
when:
run "findbugsMain"
then:
!output.contains("Scanning archives")
!output.contains("Done with analysis")
}
@Issue("https://github.com/gradle/gradle/issues/1307")
def "can disable progress output"() {
given:
buildFile << extensionProgressConfiguration(false)
and:
goodCode()
when:
run "findbugsMain"
then:
!output.contains("Scanning archives")
!output.contains("Done with analysis")
}
@Issue("https://github.com/gradle/gradle/issues/1307")
def "can enable progress output"() {
given:
buildFile << extensionProgressConfiguration(true)
and:
goodCode()
when:
run "findbugsMain"
then:
output.contains("Scanning archives")
output.contains("Done with analysis")
}
@Issue("https://github.com/gradle/gradle/issues/2326")
def "check task should not be up-to-date after clean if it only outputs to console"() {
given:
badCode()
buildFile << """
findbugs {
ignoreFailures true
showProgress true
}
tasks.withType(FindBugs) {
reports {
html.enabled false
xml.enabled false
text.enabled false
}
}
"""
when:
succeeds('check')
succeeds('clean', 'check')
then:
nonSkippedTasks.contains(':findbugsMain')
output.contains("Analyzing classes")
}
private static boolean containsXmlMessages(File xmlReportFile) {
new XmlSlurper().parseText(xmlReportFile.text).BugInstance.children().collect { it.name() }.containsAll(['ShortMessage', 'LongMessage'])
}
private goodCode(int numberOfClasses = 1) {
1.upto(numberOfClasses) {
file("src/main/java/org/gradle/Class${it}.java") << "package org.gradle; public class Class${it} { public boolean isFoo(Object arg) { return true; } }"
file("src/test/java/org/gradle/Class${it}Test.java") << "package org.gradle; public class Class${it}Test { public boolean isFoo(Object arg) { return true; } }"
}
}
private badCode() {
// Has DM_EXIT
file('src/main/java/org/gradle/BadClass.java') << 'package org.gradle; public class BadClass { public boolean isFoo(Object arg) { System.exit(1); return true; } }'
// Has ES_COMPARING_PARAMETER_STRING_WITH_EQ
file('src/test/java/org/gradle/BadClassTest.java') << 'package org.gradle; public class BadClassTest { public boolean isFoo(Object arg) { return "true" == "false"; } }'
}
private sampleStylesheet() {
normaliseFileSeparators(resources.getResource('/findbugs-custom-stylesheet.xsl').absolutePath)
}
private static Matcher containsClass(String className) {
containsLine(containsString(className))
}
private void writeBuildFile() {
buildFile << """
apply plugin: "java"
apply plugin: "findbugs"
${mavenCentralRepository()}
"""
}
private void writeFilterFile(String filename, String className) {
file(filename) << """
"""
}
private void writeBaselineBugsFilterFile(String filename, JavaClass javaClass) {
file(filename) << """
"""
}
private class JavaClass {
String pkg
String className
String fullyQualifiedClassName
String classFilename
String fullyQualifiedClassFilename
JavaClass(String pkg, String className) {
this.pkg = pkg
this.className = className
fullyQualifiedClassName = "${pkg}.${className}"
classFilename = "${className}.java"
fullyQualifiedClassFilename = "${pkg.replaceAll('\\.', '/')}/${classFilename}"
}
}
static String extensionProgressConfiguration(boolean flag) {
"""
findbugs {
showProgress = $flag
}
"""
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy