diff --git a/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoCoverageVerification.xml b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoCoverageVerification.xml new file mode 100644 index 000000000000..54cc660f3e4c --- /dev/null +++ b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoCoverageVerification.xml @@ -0,0 +1,43 @@ + + +
+
+ Properties + + + + + + + + + + +
NameDefault with jacoco plugin
violationRules +
+
+
+ Methods + + + + + + +
Name
+
+
\ No newline at end of file diff --git a/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoReport.xml b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoReport.xml index 82e45484d7b8..d4caabc4c2dd 100644 --- a/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoReport.xml +++ b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoReport.xml @@ -21,32 +21,9 @@ Name - Default with - jacoco - plugin - + Default with jacoco plugin - - executionData - - - - sourceDirectories - - - - classDirectories - - - - additionalClassDirs - - - - additionalSourceDirs - - reports @@ -61,9 +38,6 @@ Name - - executionData - \ No newline at end of file diff --git a/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoReportBase.xml b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoReportBase.xml new file mode 100644 index 000000000000..9ff5b8f8eb00 --- /dev/null +++ b/subprojects/docs/src/docs/dsl/org.gradle.testing.jacoco.tasks.JacocoReportBase.xml @@ -0,0 +1,62 @@ + + +
+
+ Properties + + + + + + + + + + + + + + + + + + + + + + +
NameDefault with jacoco plugin
executionData +
sourceDirectories +
classDirectories +
additionalClassDirs +
additionalSourceDirs +
+
+
+ Methods + + + + + + + + + +
Name
executionData
+
+
\ No newline at end of file diff --git a/subprojects/docs/src/docs/userguide/jacocoPlugin.xml b/subprojects/docs/src/docs/userguide/jacocoPlugin.xml index 8b1fd38fabae..d1a24cc0173e 100644 --- a/subprojects/docs/src/docs/userguide/jacocoPlugin.xml +++ b/subprojects/docs/src/docs/userguide/jacocoPlugin.xml @@ -81,6 +81,32 @@ +
+ Enforcing code coverage metrics + + + This feature requires the use of JaCoCo version 0.6.3 or higher. + + + + The task can be used to verify if code coverage + metrics are met based on configured rules. Its API exposes two methods, + and + , + the main entry point for configuring rules. + Invoking any of those methods returns an instance of providing + extensive configuration options. The build fails if any of the configured rules are not met. JaCoCo only reports the first violated rule. + + + Code coverage requirements can be specified for a project as a whole, for individual files, and for + particular JaCoCo-specific types of coverage, e.g., lines covered or branches covered. The following example + describes the syntax. + + + + +
+
JaCoCo specific task configuration The JaCoCo plugin adds a @@ -215,6 +241,16 @@ Generates code coverage report for the test task. + + + jacocoTestCoverageVerification + + - + + + + Verifies code coverage metrics based on specified rules for the test task. +
diff --git a/subprojects/docs/src/samples/testing/jacoco/quickstart/build.gradle b/subprojects/docs/src/samples/testing/jacoco/quickstart/build.gradle index 29919b774be7..879ce3b74aa7 100644 --- a/subprojects/docs/src/samples/testing/jacoco/quickstart/build.gradle +++ b/subprojects/docs/src/samples/testing/jacoco/quickstart/build.gradle @@ -56,3 +56,27 @@ jacocoTestReport { } } // END SNIPPET report-configuration + +// START SNIPPET violation-rules-configuration +jacocoTestCoverageVerification { + violationRules { + rule { + limit { + minimum = 0.5 + } + } + + rule { + enabled = false + element = 'CLASS' + includes = ['org.gradle.*'] + + limit { + counter = 'LINE' + value = 'TOTALCOUNT' + maximum = 0.3 + } + } + } +} +// END SNIPPET violation-rules-configuration diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoCachingIntegrationTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoCachingIntegrationTest.groovy index 3ec917fc84ac..d1c4044aecc6 100644 --- a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoCachingIntegrationTest.groovy +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoCachingIntegrationTest.groovy @@ -18,6 +18,7 @@ package org.gradle.testing.jacoco.plugins import org.gradle.integtests.fixtures.AbstractIntegrationSpec import org.gradle.integtests.fixtures.LocalBuildCacheFixture +import org.gradle.testing.jacoco.plugins.fixtures.JavaProjectUnderTest import org.gradle.util.Requires import static org.gradle.util.TestPrecondition.FIX_TO_WORK_ON_JAVA9 @@ -25,24 +26,13 @@ import static org.gradle.util.TestPrecondition.FIX_TO_WORK_ON_JAVA9 @Requires(FIX_TO_WORK_ON_JAVA9) class JacocoCachingIntegrationTest extends AbstractIntegrationSpec implements LocalBuildCacheFixture { + private final JavaProjectUnderTest javaProjectUnderTest = new JavaProjectUnderTest(testDirectory) + def "jacoco file results are cached"() { - file("src/main/java/org/gradle/Class1.java") << - "package org.gradle; public class Class1 { public boolean isFoo(Object arg) { return true; } }" - file("src/test/java/org/gradle/Class1Test.java") << - "package org.gradle; import org.junit.Test; public class Class1Test { @Test public void someTest() { new Class1().isFoo(\"test\"); } }" + javaProjectUnderTest.writeBuildScript().writeSourceFiles() def reportFile = file("build/reports/jacoco/test/html/index.html") buildFile << """ - apply plugin: "java" - apply plugin: "jacoco" - - repositories { - mavenCentral() - } - dependencies { - testCompile 'junit:junit:4.12' - } - jacocoTestReport.dependsOn test sourceSets.test.output.classesDir = file("build/classes/test") diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginIntegrationTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginIntegrationTest.groovy index 6d20df7c9a29..c30198973951 100644 --- a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginIntegrationTest.groovy +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginIntegrationTest.groovy @@ -20,6 +20,7 @@ import org.gradle.api.Project import org.gradle.api.reporting.ReportingExtension import org.gradle.integtests.fixtures.AbstractIntegrationSpec import org.gradle.integtests.fixtures.executer.GradleContextualExecuter +import org.gradle.testing.jacoco.plugins.fixtures.JavaProjectUnderTest import org.gradle.util.Requires import spock.lang.IgnoreIf import spock.lang.Issue @@ -28,24 +29,14 @@ import static org.gradle.util.TestPrecondition.FIX_TO_WORK_ON_JAVA9 class JacocoPluginIntegrationTest extends AbstractIntegrationSpec { + private final JavaProjectUnderTest javaProjectUnderTest = new JavaProjectUnderTest(testDirectory) private static final String REPORTING_BASE = "${Project.DEFAULT_BUILD_DIR_NAME}/${ReportingExtension.DEFAULT_REPORTS_DIR_NAME}" private static final String REPORT_HTML_DEFAULT_PATH = "${REPORTING_BASE}/jacoco/test/html/index.html" private static final String REPORT_XML_DEFAULT_PATH = "${REPORTING_BASE}/jacoco/test/jacocoTestReport.xml" private static final String REPORT_CSV_DEFAULT_REPORT = "${REPORTING_BASE}/jacoco/test/jacocoTestReport.csv" def setup() { - buildFile << """ - apply plugin: "java" - apply plugin: "jacoco" - - repositories { - mavenCentral() - } - dependencies { - testCompile 'junit:junit:4.12' - } - """ - createTestFiles() + javaProjectUnderTest.writeBuildScript().writeSourceFiles() } def "jacoco plugin adds coverage report for test task when java plugin applied"() { @@ -317,15 +308,42 @@ public class ThingTest { ':jacocoTestReport' in nonSkippedTasks } - private JacocoReportFixture htmlReport(String basedir = "${REPORTING_BASE}/jacoco/test/html") { - return new JacocoReportFixture(file(basedir)) + def "skips report task if none of the execution data files does not exist"() { + given: + buildFile << """ + jacocoTestReport { + executionData = files('unknown.exec', 'data/test.exec') + } + """ + + when: + succeeds 'test', 'jacocoTestReport' + + then: + ':test' in nonSkippedTasks + ':jacocoTestReport' in skippedTasks } - private void createTestFiles() { - file("src/main/java/org/gradle/Class1.java") << - "package org.gradle; public class Class1 { public boolean isFoo(Object arg) { return true; } }" - file("src/test/java/org/gradle/Class1Test.java") << - "package org.gradle; import org.junit.Test; public class Class1Test { @Test public void someTest() { new Class1().isFoo(\"test\"); } }" + def "fails report task if only some of the execution data files do not exist"() { + given: + def execFileName = 'unknown.exec' + buildFile << """ + jacocoTestReport { + executionData(files('$execFileName')) + } + """ + + when: + fails 'test', 'jacocoTestReport' + + then: + ':test' in nonSkippedTasks + ':jacocoTestReport' in executedTasks + errorOutput.contains("Unable to read execution data file ${new File(testDirectory, execFileName)}") + } + + private JacocoReportFixture htmlReport(String basedir = "${REPORTING_BASE}/jacoco/test/html") { + return new JacocoReportFixture(file(basedir)) } } diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoReportRelocationIntegrationTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoReportRelocationIntegrationTest.groovy index 60a84f645640..061c5ab1438a 100644 --- a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoReportRelocationIntegrationTest.groovy +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoReportRelocationIntegrationTest.groovy @@ -17,12 +17,16 @@ package org.gradle.testing.jacoco.plugins import org.gradle.integtests.fixtures.AbstractTaskRelocationIntegrationTest +import org.gradle.testing.jacoco.plugins.fixtures.JavaProjectUnderTest import org.gradle.util.Requires import static org.gradle.util.TestPrecondition.FIX_TO_WORK_ON_JAVA9 @Requires(FIX_TO_WORK_ON_JAVA9) class JacocoReportRelocationIntegrationTest extends AbstractTaskRelocationIntegrationTest { + + private final JavaProjectUnderTest javaProjectUnderTest = new JavaProjectUnderTest(testDirectory) + @Override protected String getTaskName() { return ":jacocoTestReport" @@ -30,22 +34,9 @@ class JacocoReportRelocationIntegrationTest extends AbstractTaskRelocationIntegr @Override protected void setupProjectInOriginalLocation() { - file("src/main/java/org/gradle/Class1.java") << - "package org.gradle; public class Class1 { public boolean isFoo(Object arg) { return true; } }" - file("src/test/java/org/gradle/Class1Test.java") << - "package org.gradle; import org.junit.Test; public class Class1Test { @Test public void someTest() { new Class1().isFoo(\"test\"); } }" + javaProjectUnderTest.writeBuildScript().writeSourceFiles() buildFile << """ - apply plugin: "java" - apply plugin: "jacoco" - - repositories { - mavenCentral() - } - dependencies { - testCompile 'junit:junit:4.12' - } - sourceSets.test.output.classesDir = file("build/classes/test") """ diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoTestRelocationIntegrationTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoTestRelocationIntegrationTest.groovy index 0962fb7a2347..545e76da1291 100644 --- a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoTestRelocationIntegrationTest.groovy +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoTestRelocationIntegrationTest.groovy @@ -17,6 +17,7 @@ package org.gradle.testing.jacoco.plugins import org.gradle.integtests.fixtures.AbstractTaskRelocationIntegrationTest +import org.gradle.testing.jacoco.plugins.fixtures.JavaProjectUnderTest import org.gradle.util.Requires import static org.gradle.util.BinaryDiffUtils.levenshteinDistance @@ -25,6 +26,9 @@ import static org.gradle.util.TestPrecondition.FIX_TO_WORK_ON_JAVA9 @Requires(FIX_TO_WORK_ON_JAVA9) class JacocoTestRelocationIntegrationTest extends AbstractTaskRelocationIntegrationTest { + + private final JavaProjectUnderTest javaProjectUnderTest = new JavaProjectUnderTest(testDirectory) + @Override protected String getTaskName() { return ":test" @@ -32,22 +36,7 @@ class JacocoTestRelocationIntegrationTest extends AbstractTaskRelocationIntegrat @Override protected void setupProjectInOriginalLocation() { - file("src/main/java/org/gradle/Class1.java") << - "package org.gradle; public class Class1 { public boolean isFoo(Object arg) { return true; } }" - file("src/test/java/org/gradle/Class1Test.java") << - "package org.gradle; import org.junit.Test; public class Class1Test { @Test public void someTest() { new Class1().isFoo(\"test\"); } }" - - buildFile << """ - apply plugin: "java" - apply plugin: "jacoco" - - repositories { - mavenCentral() - } - dependencies { - testCompile 'junit:junit:4.12' - } - """ + javaProjectUnderTest.writeBuildScript().writeSourceFiles() } @Override diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoVersionIntegTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoVersionIntegTest.groovy index aea9bbab79fd..3d9f54c9d54d 100644 --- a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoVersionIntegTest.groovy +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/JacocoVersionIntegTest.groovy @@ -16,34 +16,26 @@ package org.gradle.testing.jacoco.plugins import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec -import org.gradle.integtests.fixtures.TargetVersions +import org.gradle.integtests.fixtures.TargetCoverage +import org.gradle.testing.jacoco.plugins.fixtures.JacocoCoverage +import org.gradle.testing.jacoco.plugins.fixtures.JavaProjectUnderTest import org.gradle.util.Requires import org.gradle.util.TestPrecondition -import org.junit.Test @Requires(TestPrecondition.JDK7_OR_EARLIER) -@TargetVersions(['0.6.0.201210061924', '0.6.2.201302030002', '0.7.1.201405082137', '0.7.6.201602180812']) +@TargetCoverage({ JacocoCoverage.ALL }) class JacocoVersionIntegTest extends MultiVersionIntegrationSpec { - @Test - public void canRunVersions() { + private final JavaProjectUnderTest javaProjectUnderTest = new JavaProjectUnderTest(testDirectory) + + def "can run versions"() { given: + javaProjectUnderTest.writeBuildScript().writeSourceFiles() buildFile << """ - apply plugin: "java" - apply plugin: "jacoco" - - repositories { - mavenCentral() - } - - dependencies { - testCompile 'junit:junit:4.12' - } - jacoco { - toolVersion = '$version' - } + jacoco { + toolVersion = '$version' + } """ - createTestFiles(); when: succeeds('test', 'jacocoTestReport') @@ -57,11 +49,4 @@ class JacocoVersionIntegTest extends MultiVersionIntegrationSpec { private JacocoReportFixture htmlReport(String basedir = "build/reports/jacoco/test/html") { return new JacocoReportFixture(file(basedir)) } - - private void createTestFiles() { - file("src/main/java/org/gradle/Class1.java") << - "package org.gradle; public class Class1 { public boolean isFoo(Object arg) { return true; } }" - file("src/test/java/org/gradle/Class1Test.java") << - "package org.gradle; import org.junit.Test; public class Class1Test { @Test public void someTest() { new Class1().isFoo(\"test\"); } }" - } } diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/fixtures/JacocoCoverage.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/fixtures/JacocoCoverage.groovy new file mode 100644 index 000000000000..2c6973053c1d --- /dev/null +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/fixtures/JacocoCoverage.groovy @@ -0,0 +1,82 @@ +/* + * Copyright 2016 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.testing.jacoco.plugins.fixtures + +final class JacocoCoverage { + + JacocoCoverage() {} + + final static String[] ALL = ['0.6.0.201210061924', '0.6.2.201302030002', '0.6.3.201306030806', '0.7.1.201405082137', '0.7.6.201602180812'].asImmutable() + + final static List COVERAGE_CHECK_SUPPORTED = ALL.findAll { + def jacocoVersion = new JacocoVersion(it) + def supportedJacocoVersion = JacocoVersion.CHECK_INTRODUCED + jacocoVersion.compareTo(supportedJacocoVersion) >= 0 + }.asImmutable() + + final static List COVERAGE_CHECK_UNSUPPORTED = ALL.findAll { + def jacocoVersion = new JacocoVersion(it) + def supportedJacocoVersion = JacocoVersion.CHECK_INTRODUCED + jacocoVersion.compareTo(supportedJacocoVersion) == -1 + }.asImmutable() + + private static class JacocoVersion implements Comparable { + final static CHECK_INTRODUCED = new JacocoVersion(0, 6, 3) + private final Integer major + private final Integer minor + private final Integer patch + + JacocoVersion(String version) { + def versionParts = version.split('\\.') + major = versionParts[0].toInteger() + minor = versionParts[1].toInteger() + patch = versionParts[2].toInteger() + } + + JacocoVersion(Integer major, Integer minor, Integer patch) { + this.major = major + this.minor = minor + this.patch = patch + } + + @Override + int compareTo(JacocoVersion o) { + if (major > o.major) { + return 1 + } + if (major < o.major) { + return -1 + } + + if (minor > o.minor) { + return 1 + } + if (minor < o.minor) { + return -1 + } + + if (patch > o.patch) { + return 1 + } + if (patch < o.patch) { + return -1 + } + + 0 + } + } +} diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/fixtures/JavaProjectUnderTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/fixtures/JavaProjectUnderTest.groovy new file mode 100644 index 000000000000..ba18f67960ab --- /dev/null +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/fixtures/JavaProjectUnderTest.groovy @@ -0,0 +1,115 @@ +/* + * Copyright 2016 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.testing.jacoco.plugins.fixtures + +import org.gradle.test.fixtures.file.TestFile + +class JavaProjectUnderTest { + private final TestFile projectDir + private final TestFile buildFile + + JavaProjectUnderTest(TestFile projectDir) { + this.projectDir = projectDir + buildFile = projectDir.file('build.gradle') + } + + private TestFile file(Object... path) { + projectDir.file(path) + } + + JavaProjectUnderTest writeBuildScript() { + buildFile << """ + apply plugin: 'java' + apply plugin: 'jacoco' + + repositories { + mavenCentral() + } + + dependencies { + testCompile 'junit:junit:4.12' + } + """ + this + } + + JavaProjectUnderTest writeSourceFiles() { + writeProductionSourceFile() + writeTestSourceFile('src/test/java') + this + } + + JavaProjectUnderTest writeIntegrationTestSourceFiles() { + String testSrcDir = 'src/integTest/java' + buildFile << """ + sourceSets { + integrationTest { + java { + srcDir file('$testSrcDir') + } + compileClasspath += sourceSets.main.output + configurations.testRuntime + runtimeClasspath += output + compileClasspath + } + } + + task integrationTest(type: Test) { + testClassesDir = sourceSets.integrationTest.output.classesDir + classpath = sourceSets.integrationTest.runtimeClasspath + } + + task jacocoIntegrationTestReport(type: JacocoReport) { + executionData integrationTest + sourceSets sourceSets.main + } + + task jacocoIntegrationTestCoverageVerification(type: JacocoCoverageVerification) { + executionData integrationTest + sourceSets sourceSets.main + } + """ + + writeTestSourceFile(testSrcDir) + this + } + + private void writeProductionSourceFile() { + file('src/main/java/org/gradle/Class1.java') << """ + package org.gradle; + + public class Class1 { + public boolean isFoo(Object arg) { + return true; + } + } + """ + } + + private void writeTestSourceFile(String baseDir) { + file("$baseDir/org/gradle/Class1Test.java") << """ + package org.gradle; + + import org.junit.Test; + + public class Class1Test { + @Test + public void someTest() { + new Class1().isFoo("test"); + } + } + """ + } +} diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/AbstractJacocoPluginCoverageVerificationVersionIntegrationTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/AbstractJacocoPluginCoverageVerificationVersionIntegrationTest.groovy new file mode 100644 index 000000000000..35b783d60e4c --- /dev/null +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/AbstractJacocoPluginCoverageVerificationVersionIntegrationTest.groovy @@ -0,0 +1,49 @@ +/* + * Copyright 2016 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.testing.jacoco.plugins.rules + +import org.gradle.integtests.fixtures.MultiVersionIntegrationSpec +import org.gradle.testing.jacoco.plugins.fixtures.JavaProjectUnderTest +import org.gradle.util.Requires +import org.gradle.util.TestPrecondition + +import static JacocoViolationRulesLimit.Sufficient + +@Requires(TestPrecondition.JDK7_OR_EARLIER) +abstract class AbstractJacocoPluginCoverageVerificationVersionIntegrationTest extends MultiVersionIntegrationSpec { + + private final JavaProjectUnderTest javaProjectUnderTest = new JavaProjectUnderTest(testDirectory) + protected final static String[] TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS = [':test', ':jacocoTestCoverageVerification'] as String[] + + def setup() { + javaProjectUnderTest.writeBuildScript().writeSourceFiles() + + buildFile << """ + jacoco { + toolVersion = '$version' + } + + jacocoTestCoverageVerification { + violationRules { + rule { + $Sufficient.LINE_METRIC_COVERED_RATIO + } + } + } + """ + } +} diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoPluginCoverageVerificationCompatibleVersionIntegrationTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoPluginCoverageVerificationCompatibleVersionIntegrationTest.groovy new file mode 100644 index 000000000000..7388d564572d --- /dev/null +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoPluginCoverageVerificationCompatibleVersionIntegrationTest.groovy @@ -0,0 +1,32 @@ +/* + * Copyright 2016 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.testing.jacoco.plugins.rules + +import org.gradle.integtests.fixtures.TargetCoverage +import org.gradle.testing.jacoco.plugins.fixtures.JacocoCoverage + +@TargetCoverage({ JacocoCoverage.COVERAGE_CHECK_SUPPORTED }) +class JacocoPluginCoverageVerificationCompatibleVersionIntegrationTest extends AbstractJacocoPluginCoverageVerificationVersionIntegrationTest { + + def "can verify code coverage metrics for compatible versions"() { + when: + succeeds TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + } +} diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoPluginCoverageVerificationIncompatibleVersionIntegrationTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoPluginCoverageVerificationIncompatibleVersionIntegrationTest.groovy new file mode 100644 index 000000000000..789bc5f451e5 --- /dev/null +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoPluginCoverageVerificationIncompatibleVersionIntegrationTest.groovy @@ -0,0 +1,33 @@ +/* + * Copyright 2016 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.testing.jacoco.plugins.rules + +import org.gradle.integtests.fixtures.TargetCoverage +import org.gradle.testing.jacoco.plugins.fixtures.JacocoCoverage + +@TargetCoverage({ JacocoCoverage.COVERAGE_CHECK_UNSUPPORTED }) +class JacocoPluginCoverageVerificationIncompatibleVersionIntegrationTest extends AbstractJacocoPluginCoverageVerificationVersionIntegrationTest { + + def "fails to verify code coverage metrics"() { + when: + fails TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + errorOutput.contains("jacocoReport doesn't support the nested \"check\" element.") + } +} \ No newline at end of file diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoPluginCoverageVerificationIntegrationTest.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoPluginCoverageVerificationIntegrationTest.groovy new file mode 100644 index 000000000000..8b1aa4a889a6 --- /dev/null +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoPluginCoverageVerificationIntegrationTest.groovy @@ -0,0 +1,352 @@ +/* + * Copyright 2016 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.testing.jacoco.plugins.rules + +import org.gradle.integtests.fixtures.AbstractIntegrationSpec +import org.gradle.testing.jacoco.plugins.fixtures.JavaProjectUnderTest +import org.gradle.util.Requires +import spock.lang.Unroll + +import static JacocoViolationRulesLimit.Insufficient +import static JacocoViolationRulesLimit.Sufficient +import static org.gradle.util.TestPrecondition.FIX_TO_WORK_ON_JAVA9 + +@Requires(FIX_TO_WORK_ON_JAVA9) +class JacocoPluginCoverageVerificationIntegrationTest extends AbstractIntegrationSpec { + + private final JavaProjectUnderTest javaProjectUnderTest = new JavaProjectUnderTest(testDirectory) + private final static String[] TEST_TASK_PATH = [':test'] as String[] + private final static String[] JACOCO_COVERAGE_VERIFICATION_TASK_PATH = [':jacocoTestCoverageVerification'] as String[] + private final static String[] TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS = TEST_TASK_PATH + JACOCO_COVERAGE_VERIFICATION_TASK_PATH + private final static String[] INTEG_TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS = [':integrationTest', ':jacocoIntegrationTestCoverageVerification'] as String[] + + def setup() { + javaProjectUnderTest.writeBuildScript().writeSourceFiles() + } + + def "can define no rules"() { + given: + buildFile << """ + jacocoTestCoverageVerification { + violationRules {} + } + """ + + when: + succeeds TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + } + + def "can define single rule without limits"() { + given: + buildFile << """ + jacocoTestCoverageVerification { + violationRules { + rule {} + } + } + """ + + when: + succeeds TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + } + + def "Ant task reports error for unknown field value"() { + given: + buildFile << """ + jacocoTestCoverageVerification { + violationRules { + rule { + element = 'UNKNOWN' + } + } + } + """ + + when: + fails TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + errorOutput.contains("'UNKNOWN' is not a permitted value for org.jacoco.core.analysis.ICoverageNode\$ElementType") + } + + def "can define includes for single rule"() { + given: + buildFile << """ + jacocoTestCoverageVerification { + violationRules { + rule { + element = 'CLASS' + includes = ['com.company.*', 'org.gradle.*'] + $Insufficient.LINE_METRIC_COVERED_RATIO + } + } + } + """ + + when: + fails TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + errorOutput.contains("Rule violated for class org.gradle.Class1: lines covered ratio is 1.0, but expected maximum is 0.5") + } + + def "can define excludes for single rule"() { + given: + buildFile << """ + jacocoTestCoverageVerification { + violationRules { + rule { + excludes = ['company', '$testDirectory.name'] + $Insufficient.LINE_METRIC_COVERED_RATIO + } + } + } + """ + + when: + succeeds TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + } + + @Unroll + def "can define rule with sufficient coverage for #description"() { + given: + buildFile << """ + jacocoTestCoverageVerification { + violationRules { + rule { + ${limits.join('\n')} + } + } + } + """ + + when: + succeeds TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + + where: + limits | description + [Sufficient.LINE_METRIC_COVERED_RATIO] | 'line metric with covered ratio' + [Sufficient.CLASS_METRIC_MISSED_COUNT] | 'class metric with missed count' + [Sufficient.LINE_METRIC_COVERED_RATIO, + Sufficient.CLASS_METRIC_MISSED_COUNT] | 'line and class metric' + + } + + @Unroll + def "can define rule with insufficient coverage for #description"() { + given: + buildFile << """ + jacocoTestCoverageVerification { + violationRules { + rule { + ${limits.join('\n')} + } + } + } + """ + + when: + fails TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + errorOutput.contains("Rule violated for bundle $testDirectory.name: $errorMessage") + + where: + limits | description | errorMessage + [Insufficient.LINE_METRIC_COVERED_RATIO] | 'line metric with covered ratio' | 'lines covered ratio is 1.0, but expected maximum is 0.5' + [Insufficient.CLASS_METRIC_MISSED_COUNT] | 'class metric with missed count' | 'classes missed count is 0.0, but expected minimum is 0.5' + [Insufficient.LINE_METRIC_COVERED_RATIO, + Insufficient.CLASS_METRIC_MISSED_COUNT] | 'first of multiple insufficient limits fails' | 'lines covered ratio is 1.0, but expected maximum is 0.5' + [Sufficient.LINE_METRIC_COVERED_RATIO, + Insufficient.CLASS_METRIC_MISSED_COUNT, + Sufficient.CLASS_METRIC_MISSED_COUNT] | 'first insufficient limits fails' | 'classes missed count is 0.0, but expected minimum is 0.5' + } + + def "can define multiple rules"() { + given: + buildFile << """ + jacocoTestCoverageVerification { + violationRules { + rule { + $Sufficient.LINE_METRIC_COVERED_RATIO + } + rule { + $Insufficient.CLASS_METRIC_MISSED_COUNT + } + } + } + """ + + when: + fails TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + errorOutput.contains("Rule violated for bundle $testDirectory.name: classes missed count is 0.0, but expected minimum is 0.5") + } + + def "can disable rules"() { + given: + buildFile << """ + jacocoTestCoverageVerification { + violationRules { + rule { + $Sufficient.LINE_METRIC_COVERED_RATIO + } + rule { + enabled = false + $Insufficient.CLASS_METRIC_MISSED_COUNT + } + } + } + """ + + when: + succeeds TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + } + + def "can ignore failures"() { + given: + buildFile << """ + jacocoTestCoverageVerification { + violationRules { + failOnViolation = true + + rule { + $Insufficient.LINE_METRIC_COVERED_RATIO + } + } + } + """ + + when: + succeeds TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + errorOutput.contains("Rule violated for bundle $testDirectory.name: lines covered ratio is 1.0, but expected maximum is 0.5") + } + + @Unroll + def "can define same rules for multiple report tasks #tasksPaths"() { + given: + javaProjectUnderTest.writeIntegrationTestSourceFiles() + + buildFile << """ + tasks.withType(JacocoCoverageVerification) { + violationRules { + rule { + $Insufficient.LINE_METRIC_COVERED_RATIO + } + } + } + """ + + when: + fails tasksPaths + + then: + executedAndNotSkipped(tasksPaths) + errorOutput.contains("Rule violated for bundle $testDirectory.name: lines covered ratio is 1.0, but expected maximum is 0.5") + + where: + tasksPaths << [TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS, INTEG_TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS] + } + + @Unroll + def "can define different rules for multiple report tasks #tasksPaths"() { + given: + javaProjectUnderTest.writeIntegrationTestSourceFiles() + + buildFile << """ + $reportTaskName { + violationRules { + rule { + $limit + } + } + } + """ + + when: + fails tasksPaths + + then: + executedAndNotSkipped(tasksPaths) + errorOutput.contains("Rule violated for bundle $testDirectory.name: $errorMessage") + + where: + tasksPaths | reportTaskName | limit | errorMessage + TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS | 'jacocoTestCoverageVerification' | Insufficient.LINE_METRIC_COVERED_RATIO | 'lines covered ratio is 1.0, but expected maximum is 0.5' + INTEG_TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS | 'jacocoIntegrationTestCoverageVerification' | Insufficient.CLASS_METRIC_MISSED_COUNT | 'classes missed count is 0.0, but expected minimum is 0.5' + } + + def "task is never UP-TO-DATE as it does not define any outputs"() { + buildFile << """ + jacocoTestCoverageVerification { + violationRules { + rule { + $Sufficient.LINE_METRIC_COVERED_RATIO + } + } + } + """ + + when: + succeeds TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executedAndNotSkipped(TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS) + + when: + succeeds TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executed(JACOCO_COVERAGE_VERIFICATION_TASK_PATH) + skipped(TEST_TASK_PATH) + + when: + buildFile << """ + jacocoTestCoverageVerification.violationRules.rules[0].limits[0].maximum = 0.5 + """ + + fails TEST_AND_JACOCO_COVERAGE_VERIFICATION_TASK_PATHS + + then: + executed(JACOCO_COVERAGE_VERIFICATION_TASK_PATH) + skipped(TEST_TASK_PATH) + errorOutput.contains("Rule violated for bundle $testDirectory.name: lines covered ratio is 1.0, but expected maximum is 0.5") + } +} diff --git a/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoViolationRulesLimit.groovy b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoViolationRulesLimit.groovy new file mode 100644 index 000000000000..eda00266a378 --- /dev/null +++ b/subprojects/jacoco/src/integTest/groovy/org/gradle/testing/jacoco/plugins/rules/JacocoViolationRulesLimit.groovy @@ -0,0 +1,53 @@ +/* + * Copyright 2016 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.testing.jacoco.plugins.rules + +final class JacocoViolationRulesLimit { + + private JacocoViolationRulesLimit() {} + + static class Sufficient { + static final String LINE_METRIC_COVERED_RATIO = JacocoViolationRulesLimit.create('LINE', 'COVEREDRATIO', '0.0', '1.0') + static final String CLASS_METRIC_MISSED_COUNT = JacocoViolationRulesLimit.create('CLASS', 'MISSEDCOUNT', null, '0') + } + + static class Insufficient { + static final String LINE_METRIC_COVERED_RATIO = JacocoViolationRulesLimit.create('LINE', 'COVEREDRATIO', '0.0', '0.5') + static final String CLASS_METRIC_MISSED_COUNT = JacocoViolationRulesLimit.create('CLASS', 'MISSEDCOUNT', '0.5', null) + } + + private static String create(String counter, String value, String minimum, String maximum) { + StringBuilder limit = new StringBuilder() + limit <<= 'limit {\n' + + if (counter) { + limit <<= " counter = '${counter}'\n" + } + if (value) { + limit <<= " value = '${value}'\n" + } + if (minimum) { + limit <<= " minimum = $minimum\n" + } + if (maximum) { + limit <<= " maximum = $maximum\n" + } + + limit <<= '}' + limit.toString() + } +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/AbstractAntJacocoReport.java b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/AbstractAntJacocoReport.java new file mode 100644 index 000000000000..35e52b4f3d64 --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/AbstractAntJacocoReport.java @@ -0,0 +1,85 @@ +/* + * Copyright 2016 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.internal.jacoco; + +import com.google.common.collect.ImmutableMap; +import groovy.lang.Closure; +import groovy.lang.GroovyObjectSupport; +import org.gradle.api.file.FileCollection; +import org.gradle.api.internal.project.IsolatedAntBuilder; + +import java.util.Collections; +import java.util.Map; + +public abstract class AbstractAntJacocoReport { + + private final IsolatedAntBuilder ant; + + public AbstractAntJacocoReport(IsolatedAntBuilder ant) { + this.ant = ant; + } + + public void execute(FileCollection classpath, final String projectName, + final FileCollection allClassesDirs, final FileCollection allSourcesDirs, + final FileCollection executionData, + final T t) { + ant.withClasspath(classpath).execute(new Closure(this, this) { + @SuppressWarnings("UnusedDeclaration") + public Object doCall(Object it) { + final GroovyObjectSupport antBuilder = (GroovyObjectSupport) it; + antBuilder.invokeMethod("taskdef", ImmutableMap.of( + "name", "jacocoReport", + "classname", "org.jacoco.ant.ReportTask" + )); + final Map emptyArgs = Collections.emptyMap(); + antBuilder.invokeMethod("jacocoReport", new Object[]{emptyArgs, new Closure(this, this) { + public Object doCall(Object ignore) { + antBuilder.invokeMethod("executiondata", new Object[]{emptyArgs, new Closure(this, this) { + public Object doCall(Object ignore) { + executionData.addToAntBuilder(antBuilder, "resources"); + return null; + } + }}); + Map structureArgs = ImmutableMap.of("name", projectName); + antBuilder.invokeMethod("structure", new Object[]{structureArgs, new Closure(this, this) { + public Object doCall(Object ignore) { + antBuilder.invokeMethod("classfiles", new Object[]{emptyArgs, new Closure(this, this) { + public Object doCall(Object ignore) { + allClassesDirs.addToAntBuilder(antBuilder, "resources"); + return null; + } + }}); + antBuilder.invokeMethod("sourcefiles", new Object[]{emptyArgs, new Closure(this, this) { + public Object doCall(Object ignore) { + allSourcesDirs.addToAntBuilder(antBuilder, "resources"); + return null; + } + }}); + return null; + } + }}); + configureReport(antBuilder, t); + return null; + } + }}); + return null; + } + }); + } + + protected abstract void configureReport(GroovyObjectSupport antBuilder, T t); +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/AntJacocoCheck.java b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/AntJacocoCheck.java new file mode 100644 index 000000000000..d5690a954f63 --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/AntJacocoCheck.java @@ -0,0 +1,82 @@ +/* + * Copyright 2016 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.internal.jacoco; + +import com.google.common.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableMap; +import groovy.lang.Closure; +import groovy.lang.GroovyObjectSupport; +import org.gradle.api.internal.project.IsolatedAntBuilder; +import org.gradle.testing.jacoco.tasks.rules.JacocoLimit; +import org.gradle.testing.jacoco.tasks.rules.JacocoViolationRule; +import org.gradle.testing.jacoco.tasks.rules.JacocoViolationRulesContainer; + +import java.util.HashMap; +import java.util.Map; + +import static com.google.common.collect.Iterables.filter; + +public class AntJacocoCheck extends AbstractAntJacocoReport { + + private static final Predicate RULE_ENABLED_PREDICATE = new Predicate() { + @Override + public boolean apply(JacocoViolationRule rule) { + return rule.isEnabled(); + } + }; + + public AntJacocoCheck(IsolatedAntBuilder ant) { + super(ant); + } + + @Override + protected void configureReport(final GroovyObjectSupport antBuilder, final JacocoViolationRulesContainer violationRules) { + if (!violationRules.getRules().isEmpty()) { + Map checkArgs = ImmutableMap.of("failonviolation", !violationRules.isFailOnViolation()); + antBuilder.invokeMethod("check", new Object[] {checkArgs, new Closure(this, this) { + @SuppressWarnings("UnusedDeclaration") + public Object doCall(Object ignore) { + for (final JacocoViolationRule rule : filter(violationRules.getRules(), RULE_ENABLED_PREDICATE)) { + Map ruleArgs = ImmutableMap.of("element", rule.getElement(), "includes", Joiner.on(':').join(rule.getIncludes()), "excludes", Joiner.on(':').join(rule.getExcludes())); + antBuilder.invokeMethod("rule", new Object[] {ruleArgs, new Closure(this, this) { + @SuppressWarnings("UnusedDeclaration") + public Object doCall(Object ignore) { + for (JacocoLimit limit : rule.getLimits()) { + Map limitArgs = new HashMap(); + limitArgs.put("counter", limit.getCounter()); + limitArgs.put("value", limit.getValue()); + + if (limit.getMinimum() != null) { + limitArgs.put("minimum", limit.getMinimum()); + } + if (limit.getMaximum() != null) { + limitArgs.put("maximum", limit.getMaximum()); + } + + antBuilder.invokeMethod("limit", new Object[] {ImmutableMap.copyOf(limitArgs) }); + } + return null; + } + }}); + } + return null; + } + }}); + } + } +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/AntJacocoReport.java b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/AntJacocoReport.java index 5c9d53ec4672..111db45599b4 100644 --- a/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/AntJacocoReport.java +++ b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/AntJacocoReport.java @@ -17,82 +17,32 @@ package org.gradle.internal.jacoco; import com.google.common.collect.ImmutableMap; -import groovy.lang.Closure; import groovy.lang.GroovyObjectSupport; -import org.gradle.api.file.FileCollection; import org.gradle.api.internal.project.IsolatedAntBuilder; import org.gradle.testing.jacoco.tasks.JacocoReportsContainer; -import java.util.Collections; -import java.util.Map; - -public class AntJacocoReport { - - private final IsolatedAntBuilder ant; +public class AntJacocoReport extends AbstractAntJacocoReport { public AntJacocoReport(IsolatedAntBuilder ant) { - this.ant = ant; + super(ant); } - public void execute(FileCollection classpath, final String projectName, - final FileCollection allClassesDirs, final FileCollection allSourcesDirs, - final FileCollection executionData, - final JacocoReportsContainer reports) { - ant.withClasspath(classpath).execute(new Closure(this, this) { - @SuppressWarnings("UnusedDeclaration") - public Object doCall(Object it) { - final GroovyObjectSupport antBuilder = (GroovyObjectSupport) it; - antBuilder.invokeMethod("taskdef", ImmutableMap.of( - "name", "jacocoReport", - "classname", "org.jacoco.ant.ReportTask" - )); - final Map emptyArgs = Collections.emptyMap(); - antBuilder.invokeMethod("jacocoReport", new Object[]{emptyArgs, new Closure(this, this) { - public Object doCall(Object ignore) { - antBuilder.invokeMethod("executiondata", new Object[]{emptyArgs, new Closure(this, this) { - public Object doCall(Object ignore) { - executionData.addToAntBuilder(antBuilder, "resources"); - return null; - } - }}); - Map structureArgs = ImmutableMap.of("name", projectName); - antBuilder.invokeMethod("structure", new Object[]{structureArgs, new Closure(this, this) { - public Object doCall(Object ignore) { - antBuilder.invokeMethod("classfiles", new Object[]{emptyArgs, new Closure(this, this) { - public Object doCall(Object ignore) { - allClassesDirs.addToAntBuilder(antBuilder, "resources"); - return null; - } - }}); - antBuilder.invokeMethod("sourcefiles", new Object[]{emptyArgs, new Closure(this, this) { - public Object doCall(Object ignore) { - allSourcesDirs.addToAntBuilder(antBuilder, "resources"); - return null; - } - }}); - return null; - } - }}); - if (reports.getHtml().isEnabled()) { - antBuilder.invokeMethod("html", new Object[]{ - ImmutableMap.of("destdir", reports.getHtml().getDestination()) - }); - } - if (reports.getXml().isEnabled()) { - antBuilder.invokeMethod("xml", new Object[]{ - ImmutableMap.of("destfile", reports.getXml().getDestination()) - }); - } - if (reports.getCsv().isEnabled()) { - antBuilder.invokeMethod("csv", new Object[]{ - ImmutableMap.of("destfile", reports.getCsv().getDestination()) - }); - } - return null; - } - }}); - return null; - } - }); + @Override + protected void configureReport(GroovyObjectSupport antBuilder, JacocoReportsContainer reports) { + if (reports.getHtml().isEnabled()) { + antBuilder.invokeMethod("html", new Object[]{ + ImmutableMap.of("destdir", reports.getHtml().getDestination()) + }); + } + if (reports.getXml().isEnabled()) { + antBuilder.invokeMethod("xml", new Object[]{ + ImmutableMap.of("destfile", reports.getXml().getDestination()) + }); + } + if (reports.getCsv().isEnabled()) { + antBuilder.invokeMethod("csv", new Object[]{ + ImmutableMap.of("destfile", reports.getCsv().getDestination()) + }); + } } } diff --git a/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/rules/JacocoLimitImpl.java b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/rules/JacocoLimitImpl.java new file mode 100644 index 000000000000..1a8fa5e6b844 --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/rules/JacocoLimitImpl.java @@ -0,0 +1,99 @@ +/* + * Copyright 2016 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.internal.jacoco.rules; + +import org.gradle.testing.jacoco.tasks.rules.JacocoLimit; + +public class JacocoLimitImpl implements JacocoLimit { + + private String counter = "INSTRUCTION"; + private String value = "COVEREDRATIO"; + private Double minimum; + private Double maximum; + + @Override + public String getCounter() { + return counter; + } + + @Override + public void setCounter(String counter) { + this.counter = counter; + } + + @Override + public String getValue() { + return value; + } + + @Override + public void setValue(String value) { + this.value = value; + } + + @Override + public Double getMinimum() { + return minimum; + } + + @Override + public void setMinimum(Double minimum) { + this.minimum = minimum; + } + + @Override + public Double getMaximum() { + return maximum; + } + + @Override + public void setMaximum(Double maximum) { + this.maximum = maximum; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + JacocoLimitImpl that = (JacocoLimitImpl) o; + + if (counter != that.counter) { + return false; + } + if (value != that.value) { + return false; + } + if (minimum != null ? !minimum.equals(that.minimum) : that.minimum != null) { + return false; + } + return maximum != null ? maximum.equals(that.maximum) : that.maximum == null; + } + + @Override + public int hashCode() { + int result = counter != null ? counter.hashCode() : 0; + result = 31 * result + (value != null ? value.hashCode() : 0); + result = 31 * result + (minimum != null ? minimum.hashCode() : 0); + result = 31 * result + (maximum != null ? maximum.hashCode() : 0); + return result; + } +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/rules/JacocoViolationRuleImpl.java b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/rules/JacocoViolationRuleImpl.java new file mode 100644 index 000000000000..80bc725f0b3c --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/rules/JacocoViolationRuleImpl.java @@ -0,0 +1,131 @@ +/* + * Copyright 2016 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.internal.jacoco.rules; + +import com.google.common.collect.ImmutableList; +import groovy.lang.Closure; +import org.gradle.api.Action; +import org.gradle.api.internal.ClosureBackedAction; +import org.gradle.testing.jacoco.tasks.rules.JacocoLimit; +import org.gradle.testing.jacoco.tasks.rules.JacocoViolationRule; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class JacocoViolationRuleImpl implements JacocoViolationRule { + + private boolean enabled = true; + private String scope = "BUNDLE"; + private List includes = ImmutableList.of("*"); + private List excludes = ImmutableList.of(); + private final List limits = new ArrayList(); + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + @Override + public void setElement(String element) { + this.scope = element; + } + + @Override + public String getElement() { + return scope; + } + + @Override + public void setIncludes(List includes) { + this.includes = includes; + } + + @Override + public List getIncludes() { + return Collections.unmodifiableList(includes); + } + + @Override + public void setExcludes(List excludes) { + this.excludes = excludes; + } + + @Override + public List getExcludes() { + return Collections.unmodifiableList(excludes); + } + + @Override + public List getLimits() { + return Collections.unmodifiableList(limits); + } + + @Override + public JacocoLimit limit(Closure configureClosure) { + return limit(ClosureBackedAction.of(configureClosure)); + } + + @Override + public JacocoLimit limit(Action configureAction) { + JacocoLimit limit = new JacocoLimitImpl(); + configureAction.execute(limit); + limits.add(limit); + return limit; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + JacocoViolationRuleImpl that = (JacocoViolationRuleImpl) o; + + if (enabled != that.enabled) { + return false; + } + if (scope != that.scope) { + return false; + } + if (includes != null ? !includes.equals(that.includes) : that.includes != null) { + return false; + } + if (excludes != null ? !excludes.equals(that.excludes) : that.excludes != null) { + return false; + } + return limits != null ? limits.equals(that.limits) : that.limits == null; + } + + @Override + public int hashCode() { + int result = enabled ? 1 : 0; + result = 31 * result + (scope != null ? scope.hashCode() : 0); + result = 31 * result + (includes != null ? includes.hashCode() : 0); + result = 31 * result + (excludes != null ? excludes.hashCode() : 0); + result = 31 * result + (limits != null ? limits.hashCode() : 0); + return result; + } +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/rules/JacocoViolationRulesContainerImpl.java b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/rules/JacocoViolationRulesContainerImpl.java new file mode 100644 index 000000000000..86c3f74432de --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/internal/jacoco/rules/JacocoViolationRulesContainerImpl.java @@ -0,0 +1,59 @@ +/* + * Copyright 2016 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.internal.jacoco.rules; + +import groovy.lang.Closure; +import org.gradle.api.Action; +import org.gradle.api.internal.ClosureBackedAction; +import org.gradle.testing.jacoco.tasks.rules.JacocoViolationRule; +import org.gradle.testing.jacoco.tasks.rules.JacocoViolationRulesContainer; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class JacocoViolationRulesContainerImpl implements JacocoViolationRulesContainer { + + private boolean failOnViolation; + private final List rules = new ArrayList(); + + public void setFailOnViolation(boolean failOnViolation) { + this.failOnViolation = failOnViolation; + } + + public boolean isFailOnViolation() { + return failOnViolation; + } + + @Override + public List getRules() { + return Collections.unmodifiableList(rules); + } + + @Override + public JacocoViolationRule rule(Closure configureClosure) { + return rule(ClosureBackedAction.of(configureClosure)); + } + + @Override + public JacocoViolationRule rule(Action configureAction) { + JacocoViolationRule validationRule = new JacocoViolationRuleImpl(); + configureAction.execute(validationRule); + rules.add(validationRule); + return validationRule; + } +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/plugins/JacocoPlugin.java b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/plugins/JacocoPlugin.java index 0a445c4998d7..b0fe7ad10d89 100644 --- a/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/plugins/JacocoPlugin.java +++ b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/plugins/JacocoPlugin.java @@ -34,7 +34,9 @@ import org.gradle.api.tasks.testing.Test; import org.gradle.internal.jacoco.JacocoAgentJar; import org.gradle.internal.reflect.Instantiator; +import org.gradle.language.base.plugins.LifecycleBasePlugin; import org.gradle.testing.jacoco.tasks.JacocoBase; +import org.gradle.testing.jacoco.tasks.JacocoCoverageVerification; import org.gradle.testing.jacoco.tasks.JacocoMerge; import org.gradle.testing.jacoco.tasks.JacocoReport; @@ -78,7 +80,7 @@ public File call() { applyToDefaultTasks(extension); configureDefaultOutputPathForJacocoMerge(); configureJacocoReportsDefaults(extension); - addDefaultReportTasks(extension); + addDefaultReportAndCheckTasks(extension); } /** @@ -199,11 +201,11 @@ public File call() { } /** - * Adds report tasks for specific default test tasks. + * Adds report and check tasks for specific default test tasks. * * @param extension the extension describing the test task names */ - private void addDefaultReportTasks(final JacocoPluginExtension extension) { + private void addDefaultReportAndCheckTasks(final JacocoPluginExtension extension) { project.getPlugins().withType(JavaPlugin.class, new Action() { @Override public void execute(JavaPlugin javaPlugin) { @@ -212,6 +214,7 @@ public void execute(JavaPlugin javaPlugin) { public void execute(Test task) { if (task.getName().equals(JavaPlugin.TEST_TASK_NAME)) { addDefaultReportTask(extension, task); + addDefaultCoverageVerificationTask(task); } } }); @@ -221,6 +224,8 @@ public void execute(Test task) { private void addDefaultReportTask(final JacocoPluginExtension extension, final Test task) { final JacocoReport reportTask = project.getTasks().create("jacoco" + StringUtils.capitalise(task.getName()) + "Report", JacocoReport.class); + reportTask.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); + reportTask.setDescription(String.format("Generates code coverage report for the %s task.", task.getName())); reportTask.executionData(task); reportTask.sourceSets(project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main")); ConventionMapping taskMapping = ((IConventionAware) reportTask).getConventionMapping(); @@ -247,4 +252,12 @@ public File call() { } }); } + + private void addDefaultCoverageVerificationTask(final Test task) { + final JacocoCoverageVerification coverageVerificationTask = project.getTasks().create("jacoco" + StringUtils.capitalise(task.getName()) + "CoverageVerification", JacocoCoverageVerification.class); + coverageVerificationTask.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP); + coverageVerificationTask.setDescription(String.format("Verifies code coverage metrics based on specified rules for the %s task.", task.getName())); + coverageVerificationTask.executionData(task); + coverageVerificationTask.sourceSets(project.getConvention().getPlugin(JavaPluginConvention.class).getSourceSets().getByName("main")); + } } diff --git a/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/JacocoCoverageVerification.java b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/JacocoCoverageVerification.java new file mode 100644 index 000000000000..e3dce19532ce --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/JacocoCoverageVerification.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016 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.testing.jacoco.tasks; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import org.gradle.api.Action; +import org.gradle.api.Incubating; +import org.gradle.api.internal.ClosureBackedAction; +import org.gradle.api.tasks.Nested; +import org.gradle.api.tasks.TaskAction; +import org.gradle.internal.jacoco.AntJacocoCheck; +import org.gradle.internal.jacoco.rules.JacocoViolationRulesContainerImpl; +import org.gradle.testing.jacoco.tasks.rules.JacocoViolationRulesContainer; + +import static groovy.lang.Closure.DELEGATE_FIRST; + +/** + * Task for verifying code coverage metrics. Fails the task if violations are detected based on specified rules. + *

+ * Requires JaCoCo version >= 0.6.3. + * + * @since 3.4 + */ +@Incubating +public class JacocoCoverageVerification extends JacocoReportBase { + + private final JacocoViolationRulesContainer violationRules; + + public JacocoCoverageVerification() { + super(); + violationRules = getInstantiator().newInstance(JacocoViolationRulesContainerImpl.class); + } + + /** + * Returns the violation rules set for this task. + * + * @return Violation rules container + */ + @Nested + public JacocoViolationRulesContainer getViolationRules() { + return violationRules; + } + + /** + * Configures the violation rules for this task. + */ + @Incubating + public JacocoViolationRulesContainer violationRules(@DelegatesTo(value = JacocoViolationRulesContainer.class, strategy = DELEGATE_FIRST) Closure closure) { + return violationRules(new ClosureBackedAction(closure)); + } + + /** + * Configures the violation rules for this task. + */ + @Incubating + public JacocoViolationRulesContainer violationRules(Action configureAction) { + configureAction.execute(violationRules); + return violationRules; + } + + @TaskAction + public void check() { + new AntJacocoCheck(getAntBuilder()).execute( + getJacocoClasspath(), + getProject().getName(), + getAllClassDirs().filter(fileExistsSpec), + getAllSourceDirs().filter(fileExistsSpec), + getExecutionData(), + getViolationRules() + ); + } +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/JacocoReport.java b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/JacocoReport.java index 3818b9923766..db2117e0f082 100644 --- a/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/JacocoReport.java +++ b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/JacocoReport.java @@ -15,79 +15,29 @@ */ package org.gradle.testing.jacoco.tasks; -import com.google.common.base.Predicate; -import com.google.common.collect.Iterables; import groovy.lang.Closure; import org.gradle.api.Action; import org.gradle.api.Incubating; -import org.gradle.api.Project; -import org.gradle.api.Task; -import org.gradle.api.file.FileCollection; import org.gradle.api.internal.ClosureBackedAction; -import org.gradle.api.internal.project.IsolatedAntBuilder; import org.gradle.api.reporting.Reporting; -import org.gradle.api.specs.Spec; import org.gradle.api.tasks.CacheableTask; -import org.gradle.api.tasks.InputFiles; -import org.gradle.api.tasks.Internal; import org.gradle.api.tasks.Nested; -import org.gradle.api.tasks.Optional; -import org.gradle.api.tasks.PathSensitive; -import org.gradle.api.tasks.PathSensitivity; -import org.gradle.api.tasks.SourceSet; import org.gradle.api.tasks.TaskAction; -import org.gradle.api.tasks.TaskCollection; import org.gradle.internal.jacoco.AntJacocoReport; import org.gradle.internal.jacoco.JacocoReportsContainerImpl; -import org.gradle.internal.reflect.Instantiator; -import org.gradle.testing.jacoco.plugins.JacocoTaskExtension; - -import javax.inject.Inject; -import java.io.File; -import java.util.Arrays; -import java.util.concurrent.Callable; /** * Task to generate HTML, Xml and CSV reports of Jacoco coverage data. */ @CacheableTask @Incubating -public class JacocoReport extends JacocoBase implements Reporting { +public class JacocoReport extends JacocoReportBase implements Reporting { private final JacocoReportsContainer reports; - private FileCollection executionData; - private FileCollection sourceDirectories; - private FileCollection classDirectories; - private FileCollection additionalClassDirs; - private FileCollection additionalSourceDirs; - public JacocoReport() { + super(); reports = getInstantiator().newInstance(JacocoReportsContainerImpl.class, this); - onlyIf(new Spec() { - @Override - public boolean isSatisfiedBy(Task element) { - //TODO SF it should be 'any' instead of 'all' - return Iterables.all(getExecutionData(), new Predicate() { - @Override - public boolean apply(File file) { - return file.exists(); - } - - }); - } - - }); - } - - @Inject - protected Instantiator getInstantiator() { - throw new UnsupportedOperationException(); - } - - @Inject - protected IsolatedAntBuilder getAntBuilder() { - throw new UnsupportedOperationException(); } /** @@ -113,81 +63,8 @@ public JacocoReportsContainer reports(Action con return reports; } - /** - * Collection of execution data files to analyze. - */ - @PathSensitive(PathSensitivity.NONE) - @InputFiles - public FileCollection getExecutionData() { - return executionData; - } - - public void setExecutionData(FileCollection executionData) { - this.executionData = executionData; - } - - /** - * Source sets that coverage should be reported for. - */ - @PathSensitive(PathSensitivity.RELATIVE) - @InputFiles - public FileCollection getSourceDirectories() { - return sourceDirectories; - } - - public void setSourceDirectories(FileCollection sourceDirectories) { - this.sourceDirectories = sourceDirectories; - } - - /** - * Source sets that coverage should be reported for. - */ - @PathSensitive(PathSensitivity.RELATIVE) - @InputFiles - public FileCollection getClassDirectories() { - return classDirectories; - } - - public void setClassDirectories(FileCollection classDirectories) { - this.classDirectories = classDirectories; - } - - /** - * Additional class dirs that coverage data should be reported for. - */ - @Optional - @PathSensitive(PathSensitivity.RELATIVE) - @InputFiles - public FileCollection getAdditionalClassDirs() { - return additionalClassDirs; - } - - public void setAdditionalClassDirs(FileCollection additionalClassDirs) { - this.additionalClassDirs = additionalClassDirs; - } - - /** - * Additional source dirs for the classes coverage data is being reported for. - */ - @Optional - @PathSensitive(PathSensitivity.RELATIVE) - @InputFiles - public FileCollection getAdditionalSourceDirs() { - return additionalSourceDirs; - } - - public void setAdditionalSourceDirs(FileCollection additionalSourceDirs) { - this.additionalSourceDirs = additionalSourceDirs; - } - @TaskAction public void generate() { - Spec fileExistsSpec = new Spec() { - @Override - public boolean isSatisfiedBy(File file) { - return file.exists(); - } - }; new AntJacocoReport(getAntBuilder()).execute( getJacocoClasspath(), getProject().getName(), @@ -197,150 +74,4 @@ public boolean isSatisfiedBy(File file) { getReports() ); } - - /** - * Adds execution data files to be used during coverage analysis. - * - * @param files one or more files to add - */ - public void executionData(Object... files) { - if (executionData == null) { - executionData = getProject().files(files); - } else { - executionData = executionData.plus(getProject().files(files)); - } - } - - /** - * Adds execution data generated by a task to the list of those used during coverage analysis. Only tasks with a {@link JacocoTaskExtension} will be included; all others will be ignored. - * - * @param tasks one or more tasks to add - */ - public void executionData(Task... tasks) { - for (Task task : tasks) { - final JacocoTaskExtension extension = task.getExtensions().findByType(JacocoTaskExtension.class); - if (extension != null) { - executionData(new Callable() { - @Override - public File call() { - return extension.getDestinationFile(); - } - }); - mustRunAfter(task); - } - } - } - - /** - * Adds execution data generated by the given tasks to the list of those used during coverage analysis. Only tasks with a {@link JacocoTaskExtension} will be included; all others will be ignored. - * - * @param tasks one or more tasks to add - */ - public void executionData(TaskCollection tasks) { - tasks.all(new Action() { - @Override - public void execute(Task task) { - executionData(task); - } - }); - } - - /** - * Gets the class directories that coverage will be reported for. All classes in these directories will be included in the report. - * - * @return class dirs to report coverage of - */ - @Internal - public FileCollection getAllClassDirs() { - FileCollection additionalDirs = getAdditionalClassDirs(); - if (additionalDirs == null) { - return classDirectories; - } - return classDirectories.plus(getAdditionalClassDirs()); - } - - /** - * Gets the source directories for the classes that will be reported on. Source will be obtained from these directories only for the classes included in the report. - * - * @return source directories for the classes reported on - * @see #getAllClassDirs() - */ - @Internal - public FileCollection getAllSourceDirs() { - FileCollection additionalDirs = getAdditionalSourceDirs(); - if (additionalDirs == null) { - return sourceDirectories; - } - return sourceDirectories.plus(getAdditionalSourceDirs()); - } - - /** - * Adds a source set to the list to be reported on. The output of this source set will be used as classes to include in the report. The source for this source set will be used for any classes - * included in the report. - * - * @param sourceSets one or more source sets to report on - */ - public void sourceSets(final SourceSet... sourceSets) { - getProject().afterEvaluate(new Action() { - @Override - public void execute(Project project) { - for (SourceSet sourceSet : sourceSets) { - if (getSourceDirectories() == null) { - setSourceDirectories(getProject().files(sourceSet.getAllJava().getSrcDirs())); - } else { - setSourceDirectories(getSourceDirectories().plus(getProject().files(sourceSet.getAllJava().getSrcDirs()))); - } - if (getClassDirectories() == null) { - setClassDirectories(sourceSet.getOutput()); - } else { - setClassDirectories(getClassDirectories().plus(sourceSet.getOutput())); - } - } - } - }); - } - - /** - * Adds additional class directories to those that will be included in the report. - * - * @param dirs one or more directories containing classes to report coverage of - */ - public void additionalClassDirs(File... dirs) { - additionalClassDirs(getProject().files(Arrays.asList(dirs))); - } - - /** - * Adds additional class directories to those that will be included in the report. - * - * @param dirs a {@code FileCollection} of directories containing classes to report coverage of - */ - public void additionalClassDirs(FileCollection dirs) { - if (additionalClassDirs == null) { - additionalClassDirs = dirs; - } else { - additionalClassDirs = additionalClassDirs.plus(dirs); - } - } - - /** - * Adds additional source directories to be used for any classes included in the report. - * - * @param dirs one or more directories containing source files for the classes included in the report - */ - public void additionalSourceDirs(File... dirs) { - additionalSourceDirs(getProject().files(Arrays.asList(dirs))); - } - - /** - * Adds additional source directories to be used for any classes included in the report. - * - * @param dirs a {@code FileCollection} of directories containing source files for the classes included in the report - */ - public void additionalSourceDirs(FileCollection dirs) { - if (additionalSourceDirs == null) { - additionalSourceDirs = dirs; - } else { - additionalSourceDirs = additionalSourceDirs.plus(dirs); - } - } } diff --git a/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/JacocoReportBase.java b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/JacocoReportBase.java new file mode 100644 index 000000000000..09f2eea09d80 --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/JacocoReportBase.java @@ -0,0 +1,298 @@ +/* + * Copyright 2016 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.testing.jacoco.tasks; + +import com.google.common.base.Predicate; +import com.google.common.collect.Iterables; +import org.gradle.api.Action; +import org.gradle.api.Incubating; +import org.gradle.api.Project; +import org.gradle.api.Task; +import org.gradle.api.file.FileCollection; +import org.gradle.api.internal.project.IsolatedAntBuilder; +import org.gradle.api.specs.Spec; +import org.gradle.api.tasks.InputFiles; +import org.gradle.api.tasks.Internal; +import org.gradle.api.tasks.Optional; +import org.gradle.api.tasks.PathSensitive; +import org.gradle.api.tasks.PathSensitivity; +import org.gradle.api.tasks.SourceSet; +import org.gradle.api.tasks.TaskCollection; +import org.gradle.internal.reflect.Instantiator; +import org.gradle.testing.jacoco.plugins.JacocoTaskExtension; + +import javax.inject.Inject; +import java.io.File; +import java.util.Arrays; +import java.util.concurrent.Callable; + +/** + * Base class for Jacoco report tasks. + */ +@Incubating +public abstract class JacocoReportBase extends JacocoBase { + + protected final Spec fileExistsSpec = new Spec() { + @Override + public boolean isSatisfiedBy(File file) { + return file.exists(); + } + }; + + private FileCollection executionData; + private FileCollection sourceDirectories; + private FileCollection classDirectories; + private FileCollection additionalClassDirs; + private FileCollection additionalSourceDirs; + + public JacocoReportBase() { + onlyIf(new Spec() { + @Override + public boolean isSatisfiedBy(Task element) { + return Iterables.any(getExecutionData(), new Predicate() { + @Override + public boolean apply(File file) { + return file.exists(); + } + }); + } + }); + } + + @Inject + protected Instantiator getInstantiator() { + throw new UnsupportedOperationException(); + } + + @Inject + protected IsolatedAntBuilder getAntBuilder() { + throw new UnsupportedOperationException(); + } + + /** + * Collection of execution data files to analyze. + */ + @PathSensitive(PathSensitivity.NONE) + @InputFiles + public FileCollection getExecutionData() { + return executionData; + } + + public void setExecutionData(FileCollection executionData) { + this.executionData = executionData; + } + + /** + * Source sets that coverage should be reported for. + */ + @PathSensitive(PathSensitivity.RELATIVE) + @InputFiles + public FileCollection getSourceDirectories() { + return sourceDirectories; + } + + public void setSourceDirectories(FileCollection sourceDirectories) { + this.sourceDirectories = sourceDirectories; + } + + /** + * Source sets that coverage should be reported for. + */ + @PathSensitive(PathSensitivity.RELATIVE) + @InputFiles + public FileCollection getClassDirectories() { + return classDirectories; + } + + public void setClassDirectories(FileCollection classDirectories) { + this.classDirectories = classDirectories; + } + + /** + * Additional class dirs that coverage data should be reported for. + */ + @Optional + @PathSensitive(PathSensitivity.RELATIVE) + @InputFiles + public FileCollection getAdditionalClassDirs() { + return additionalClassDirs; + } + + public void setAdditionalClassDirs(FileCollection additionalClassDirs) { + this.additionalClassDirs = additionalClassDirs; + } + + /** + * Additional source dirs for the classes coverage data is being reported for. + */ + @Optional + @PathSensitive(PathSensitivity.RELATIVE) + @InputFiles + public FileCollection getAdditionalSourceDirs() { + return additionalSourceDirs; + } + + public void setAdditionalSourceDirs(FileCollection additionalSourceDirs) { + this.additionalSourceDirs = additionalSourceDirs; + } + + /** + * Adds execution data files to be used during coverage analysis. + * + * @param files one or more files to add + */ + public void executionData(Object... files) { + if (executionData == null) { + executionData = getProject().files(files); + } else { + executionData = executionData.plus(getProject().files(files)); + } + } + + /** + * Adds execution data generated by a task to the list of those used during coverage analysis. Only tasks with a {@link JacocoTaskExtension} will be included; all others will be ignored. + * + * @param tasks one or more tasks to add + */ + public void executionData(Task... tasks) { + for (Task task : tasks) { + final JacocoTaskExtension extension = task.getExtensions().findByType(JacocoTaskExtension.class); + if (extension != null) { + executionData(new Callable() { + @Override + public File call() { + return extension.getDestinationFile(); + } + }); + mustRunAfter(task); + } + } + } + + /** + * Adds execution data generated by the given tasks to the list of those used during coverage analysis. Only tasks with a {@link JacocoTaskExtension} will be included; all others will be ignored. + * + * @param tasks one or more tasks to add + */ + public void executionData(TaskCollection tasks) { + tasks.all(new Action() { + @Override + public void execute(Task task) { + executionData(task); + } + }); + } + + /** + * Gets the class directories that coverage will be reported for. All classes in these directories will be included in the report. + * + * @return class dirs to report coverage of + */ + @Internal + public FileCollection getAllClassDirs() { + FileCollection additionalDirs = getAdditionalClassDirs(); + if (additionalDirs == null) { + return classDirectories; + } + return classDirectories.plus(getAdditionalClassDirs()); + } + + /** + * Gets the source directories for the classes that will be reported on. Source will be obtained from these directories only for the classes included in the report. + * + * @return source directories for the classes reported on + * @see #getAllClassDirs() + */ + @Internal + public FileCollection getAllSourceDirs() { + FileCollection additionalDirs = getAdditionalSourceDirs(); + if (additionalDirs == null) { + return sourceDirectories; + } + return sourceDirectories.plus(getAdditionalSourceDirs()); + } + + /** + * Adds a source set to the list to be reported on. The output of this source set will be used as classes to include in the report. The source for this source set will be used for any classes + * included in the report. + * + * @param sourceSets one or more source sets to report on + */ + public void sourceSets(final SourceSet... sourceSets) { + getProject().afterEvaluate(new Action() { + @Override + public void execute(Project project) { + for (SourceSet sourceSet : sourceSets) { + if (getSourceDirectories() == null) { + setSourceDirectories(getProject().files(sourceSet.getAllJava().getSrcDirs())); + } else { + setSourceDirectories(getSourceDirectories().plus(getProject().files(sourceSet.getAllJava().getSrcDirs()))); + } + if (getClassDirectories() == null) { + setClassDirectories(sourceSet.getOutput()); + } else { + setClassDirectories(getClassDirectories().plus(sourceSet.getOutput())); + } + } + } + }); + } + + /** + * Adds additional class directories to those that will be included in the report. + * + * @param dirs one or more directories containing classes to report coverage of + */ + public void additionalClassDirs(File... dirs) { + additionalClassDirs(getProject().files(Arrays.asList(dirs))); + } + + /** + * Adds additional class directories to those that will be included in the report. + * + * @param dirs a {@code FileCollection} of directories containing classes to report coverage of + */ + public void additionalClassDirs(FileCollection dirs) { + if (additionalClassDirs == null) { + additionalClassDirs = dirs; + } else { + additionalClassDirs = additionalClassDirs.plus(dirs); + } + } + + /** + * Adds additional source directories to be used for any classes included in the report. + * + * @param dirs one or more directories containing source files for the classes included in the report + */ + public void additionalSourceDirs(File... dirs) { + additionalSourceDirs(getProject().files(Arrays.asList(dirs))); + } + + /** + * Adds additional source directories to be used for any classes included in the report. + * + * @param dirs a {@code FileCollection} of directories containing source files for the classes included in the report + */ + public void additionalSourceDirs(FileCollection dirs) { + if (additionalSourceDirs == null) { + additionalSourceDirs = dirs; + } else { + additionalSourceDirs = additionalSourceDirs.plus(dirs); + } + } +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/JacocoLimit.java b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/JacocoLimit.java new file mode 100644 index 000000000000..035c33f68f47 --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/JacocoLimit.java @@ -0,0 +1,70 @@ +/* + * Copyright 2016 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.testing.jacoco.tasks.rules; + +import org.gradle.api.Incubating; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Optional; + +import java.io.Serializable; + +/** + * Defines a Jacoco rule limit. + * + * @since 3.4 + */ +@Incubating +public interface JacocoLimit extends Serializable { + + /** + * The counter that applies to the limit as defined by + * org.jacoco.core.analysis.ICoverageNode.CounterEntity. + * Valid values are INSTRUCTION, LINE, BRANCH, COMPLEXITY, METHOD and CLASS. Defaults to INSTRUCTION. + */ + @Input + String getCounter(); + + void setCounter(String counter); + + /** + * The value that applies to the limit as defined by + * org.jacoco.core.analysis.ICounter.CounterValue. + * Valid values are TOTALCOUNT, MISSEDCOUNT, COVEREDCOUNT, MISSEDRATIO and COVEREDRATIO. Defaults to COVEREDRATIO. + */ + @Input + String getValue(); + + void setValue(String value); + + /** + * Gets the minimum expected value for limit. Default to null. + */ + @Input + @Optional + Double getMinimum(); + + void setMinimum(Double minimum); + + /** + * Gets the maximum expected value for limit. Default to null. + */ + @Input + @Optional + Double getMaximum(); + + void setMaximum(Double maximum); +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/JacocoViolationRule.java b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/JacocoViolationRule.java new file mode 100644 index 000000000000..5b6803907155 --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/JacocoViolationRule.java @@ -0,0 +1,87 @@ +/* + * Copyright 2016 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.testing.jacoco.tasks.rules; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import org.gradle.api.Action; +import org.gradle.api.Incubating; +import org.gradle.api.tasks.Input; +import org.gradle.api.tasks.Nested; + +import java.io.Serializable; +import java.util.List; + +import static groovy.lang.Closure.DELEGATE_FIRST; + +/** + * Defines a Jacoco violation rule. + * + * @since 3.4 + */ +@Incubating +public interface JacocoViolationRule extends Serializable { + + void setEnabled(boolean enabled); + + /** + * Indicates if the rule should be used when checking generated coverage metrics. Defaults to true. + */ + @Input + boolean isEnabled(); + + void setElement(String element); + + /** + * Gets the element for the rule as defined by + * org.jacoco.core.analysis.ICoverageNode.ElementType. + * Valid scope values are BUNDLE, PACKAGE, CLASS, SOURCEFILE and METHOD. Defaults to BUNDLE. + */ + @Input + String getElement(); + + void setIncludes(List includes); + + /** + * List of elements that should be included in check. Names can use wildcards (* and ?). + * If left empty, all elements will be included. Defaults to [*]. + */ + @Input + List getIncludes(); + + void setExcludes(List excludes); + + /** + * List of elements that should be excluded from check. Names can use wildcards (* and ?). + * If left empty, no elements will be excluded. Defaults to an empty list. + */ + @Input + List getExcludes(); + + /** + * Gets all limits defined for this rule. Defaults to an empty list. + */ + @Nested + List getLimits(); + + /** + * Adds a limit for this rule. Any number of limits can be added. + */ + JacocoLimit limit(@DelegatesTo(value = JacocoLimit.class, strategy = DELEGATE_FIRST) Closure configureClosure); + + JacocoLimit limit(Action configureAction); +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/JacocoViolationRulesContainer.java b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/JacocoViolationRulesContainer.java new file mode 100644 index 000000000000..fe60db042e11 --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/JacocoViolationRulesContainer.java @@ -0,0 +1,57 @@ +/* + * Copyright 2016 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.testing.jacoco.tasks.rules; + +import groovy.lang.Closure; +import groovy.lang.DelegatesTo; +import org.gradle.api.Action; +import org.gradle.api.Incubating; +import org.gradle.api.tasks.Input; + +import java.util.List; + +import static groovy.lang.Closure.DELEGATE_FIRST; + +/** + * The violation rules configuration for the {@link org.gradle.testing.jacoco.tasks.JacocoReport} task. + * + * @since 3.4 + */ +@Incubating +public interface JacocoViolationRulesContainer { + + void setFailOnViolation(boolean ignore); + + /** + * Specifies whether build should fail in case of rule violations. Defaults to true. + */ + @Input + boolean isFailOnViolation(); + + /** + * Gets all violation rules. Defaults to an empty list. + */ + @Input + List getRules(); + + /** + * Adds a violation rule. Any number of rules can be added. + */ + JacocoViolationRule rule(@DelegatesTo(value = JacocoViolationRule.class, strategy = DELEGATE_FIRST) Closure configureClosure); + + JacocoViolationRule rule(Action configureAction); +} diff --git a/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/package-info.java b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/package-info.java new file mode 100644 index 000000000000..983b97986fff --- /dev/null +++ b/subprojects/jacoco/src/main/java/org/gradle/testing/jacoco/tasks/rules/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2016 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. + */ + +/** + * Implementations for Jacoco code coverage rules. + */ +@org.gradle.api.Incubating +package org.gradle.testing.jacoco.tasks.rules; \ No newline at end of file diff --git a/subprojects/jacoco/src/test/groovy/org/gradle/internal/jacoco/rules/JacocoLimitImplTest.groovy b/subprojects/jacoco/src/test/groovy/org/gradle/internal/jacoco/rules/JacocoLimitImplTest.groovy new file mode 100644 index 000000000000..5d431c6079c7 --- /dev/null +++ b/subprojects/jacoco/src/test/groovy/org/gradle/internal/jacoco/rules/JacocoLimitImplTest.groovy @@ -0,0 +1,32 @@ +/* + * Copyright 2016 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.internal.jacoco.rules + +import spock.lang.Specification + +class JacocoLimitImplTest extends Specification { + + JacocoLimitImpl limit = new JacocoLimitImpl() + + def "provides expected default field values"() { + expect: + limit.counter == 'INSTRUCTION' + limit.value == 'COVEREDRATIO' + !limit.minimum + !limit.maximum + } +} diff --git a/subprojects/jacoco/src/test/groovy/org/gradle/internal/jacoco/rules/JacocoViolationRuleImplTest.groovy b/subprojects/jacoco/src/test/groovy/org/gradle/internal/jacoco/rules/JacocoViolationRuleImplTest.groovy new file mode 100644 index 000000000000..d15aa0aace4d --- /dev/null +++ b/subprojects/jacoco/src/test/groovy/org/gradle/internal/jacoco/rules/JacocoViolationRuleImplTest.groovy @@ -0,0 +1,85 @@ +/* + * Copyright 2016 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.internal.jacoco.rules + +import org.gradle.api.Action +import org.gradle.testing.jacoco.tasks.rules.JacocoLimit +import spock.lang.Specification + +class JacocoViolationRuleImplTest extends Specification { + + JacocoViolationRuleImpl rule = new JacocoViolationRuleImpl() + + def "provides expected default field values"() { + expect: + rule.enabled + rule.element == 'BUNDLE' + rule.includes == ['*'] + rule.excludes == [] + } + + def "can add limits"() { + when: + def limit = rule.limit { + counter = 'CLASS' + value = 'TOTALCOUNT' + minimum = 0.0 + maximum = 1.0 + } + + then: + rule.limits.size() == 1 + rule.limits[0] == limit + + when: + limit = rule.limit(new Action() { + @Override + void execute(JacocoLimit jacocoLimit) { + jacocoLimit.with { + counter = 'COMPLEXITY' + value = 'MISSEDCOUNT' + minimum = 0.2 + maximum = 0.6 + } + } + }) + + then: + rule.limits.size() == 2 + rule.limits[1] == limit + } + + def "returned includes, excludes and limits are unmodifiable"() { + when: + rule.includes << ['*'] + + then: + thrown(UnsupportedOperationException) + + when: + rule.excludes << ['*'] + + then: + thrown(UnsupportedOperationException) + + when: + rule.limits << new JacocoLimitImpl() + + then: + thrown(UnsupportedOperationException) + } +} diff --git a/subprojects/jacoco/src/test/groovy/org/gradle/internal/jacoco/rules/JacocoViolationRulesContainerImplTest.groovy b/subprojects/jacoco/src/test/groovy/org/gradle/internal/jacoco/rules/JacocoViolationRulesContainerImplTest.groovy new file mode 100644 index 000000000000..11933cb83a64 --- /dev/null +++ b/subprojects/jacoco/src/test/groovy/org/gradle/internal/jacoco/rules/JacocoViolationRulesContainerImplTest.groovy @@ -0,0 +1,71 @@ +/* + * Copyright 2016 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.internal.jacoco.rules + +import org.gradle.api.Action +import org.gradle.testing.jacoco.tasks.rules.JacocoViolationRule +import spock.lang.Specification + +class JacocoViolationRulesContainerImplTest extends Specification { + + JacocoViolationRulesContainerImpl violationRulesContainer = new JacocoViolationRulesContainerImpl() + + def "provides expected default field values"() { + expect: + !violationRulesContainer.failOnViolation + violationRulesContainer.rules.empty + } + + def "can add rules"() { + when: + def rule = violationRulesContainer.rule { + enabled = false + element = 'CLASS' + includes = ['**/*.class'] + excludes = ['*Special*.class'] + } + + then: + violationRulesContainer.rules.size() == 1 + violationRulesContainer.rules[0] == rule + + when: + rule = violationRulesContainer.rule(new Action() { + @Override + void execute(JacocoViolationRule jacocoValidationRule) { + jacocoValidationRule.with { + enabled = true + element = 'PACKAGE' + includes = ['**/*'] + excludes = ['**/special/*'] + } + } + }) + + then: + violationRulesContainer.rules.size() == 2 + violationRulesContainer.rules[1] == rule + } + + def "returned rules are unmodifiable"() { + when: + violationRulesContainer.rules << new JacocoViolationRuleImpl() + + then: + thrown(UnsupportedOperationException) + } +} diff --git a/subprojects/jacoco/src/test/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginSpec.groovy b/subprojects/jacoco/src/test/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginSpec.groovy index bfbd171f0a46..eb20dc8b5961 100644 --- a/subprojects/jacoco/src/test/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginSpec.groovy +++ b/subprojects/jacoco/src/test/groovy/org/gradle/testing/jacoco/plugins/JacocoPluginSpec.groovy @@ -17,6 +17,7 @@ package org.gradle.testing.jacoco.plugins import org.gradle.api.tasks.JavaExec import org.gradle.api.tasks.testing.Test +import org.gradle.language.base.plugins.LifecycleBasePlugin import org.gradle.test.fixtures.AbstractProjectBuilderSpec import spock.lang.Issue import spock.lang.Unroll @@ -92,4 +93,17 @@ class JacocoPluginSpec extends AbstractProjectBuilderSpec { where: includeNoLocationClassesValue << [true, false] } + + def "declares task property values for group and description"() { + given: + project.apply plugin: 'java' + + expect: + def jacocoTestReportTask = project.tasks.getByName('jacocoTestReport') + def jacocoTestCoverageVerificationTask = project.tasks.getByName('jacocoTestCoverageVerification') + jacocoTestReportTask.group == LifecycleBasePlugin.VERIFICATION_GROUP + jacocoTestCoverageVerificationTask.group == LifecycleBasePlugin.VERIFICATION_GROUP + jacocoTestReportTask.description == 'Generates code coverage report for the test task.' + jacocoTestCoverageVerificationTask.description == 'Verifies code coverage metrics based on specified rules for the test task.' + } }