All Downloads are FREE. Search and download functionalities are using the official Maven repository.

name.remal.gradle_plugins.toolkit.build_logic.jacoco.gradle Maven / Gradle / Ivy

import static java.nio.file.StandardOpenOption.READ
import static java.nio.file.StandardOpenOption.WRITE
import static org.gradle.api.reporting.Report.OutputType

import java.nio.channels.FileChannel
import java.nio.channels.OverlappingFileLockException
import java.time.Duration
import javax.annotation.Nullable

if (project.isBuildSrcProject) return

allprojects {
    pluginManager.withPlugin('java') {
        apply plugin: 'jacoco'
    }

    pluginManager.withPlugin('jacoco') {
        jacoco {
            toolVersion = project.configurations.projectDependencyConstraints
                .allDependencyConstraints
                .matching { it.group == 'org.jacoco' && it.name == 'org.jacoco.agent' }
                .matching { it.version != null && !it.version.isEmpty() }
                .first()
                .version
        }

        tasks.withType(Test).configureEach { Test task ->
            task.onlyIf {
                JacocoTaskExtension jacocoTaskExtension = task.extensions.getByType(JacocoTaskExtension)
                jacocoTaskExtension.excludes.add('org.gradle.*')
                return true
            }
        }

        tasks.withType(JacocoReport).configureEach {
            reports.html.required = true
            reports.xml.required = true
        }

        tasks.withType(Test).all { Test testTask ->
            TaskCollection reportTasks = tasks.withType(JacocoReport).matching { it.name == "jacoco${name.capitalize()}Report" }
            testTask.finalizedBy(reportTasks)
            reportTasks.all { JacocoReport reportTask ->
                reportTask.dependsOn(testTask)
                reportTask.dependsOn(sourceSets.main.allJava)
                reportTask.dependsOn(tasks.matching { ['generateJava', 'generateGroovy', 'generateKotlin', 'generateResources'].contains(it.name) })
            }
        }

        tasks.withType(Test).matching { it.name == 'functionalTest' || it.name == 'testFunctional' }.configureEach { Test task ->
            task.ignoreFailures = true

            FailsCounterTestListener failsCounterTestListener = new FailsCounterTestListener()
            task.addTestListener(failsCounterTestListener)

            task.doLast {
                JacocoTaskExtension jacocoTaskExtension = task.extensions.getByType(JacocoTaskExtension)
                File execFile = jacocoTaskExtension.destinationFile?.absoluteFile
                if (execFile == null) return

                long startTime = System.nanoTime()
                long maxTimeout = Duration.ofSeconds(10).toNanos()
                long timeoutTime = startTime + maxTimeout
                for (int attempt = 1; System.nanoTime() < timeoutTime; ++attempt) {
                    if (attempt > 1) Thread.sleep(1_000)

                    if (!execFile.exists()) continue

                    try {
                        FileChannel.open(execFile.toPath(), READ, WRITE).withCloseable { FileChannel channel ->
                            channel.lock(0, Long.MAX_VALUE, true).close()
                        }

                    } catch (IOException | OverlappingFileLockException ignored) {
                        continue
                    }

                    break
                }
            }

            task.doLast {
                Throwable exception = failsCounterTestListener.exception
                if (exception != null) {
                    throw exception
                }

                String exceptionMessage = failsCounterTestListener.createExceptionMessage()
                if (exceptionMessage != null) {
                    throw new GradleException(exceptionMessage)
                }
            }
        }
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

JacocoReport jacocoReportMerged = tasks.create('jacocoReportMerged', JacocoReport) { JacocoReport task ->
    apply plugin: 'jacoco'

    task.group = 'verification'
    task.reports.all { ConfigurableReport report ->
        DirectoryProperty reportsDir = project.jacoco.reportsDirectory
        if (report.getOutputType() == OutputType.DIRECTORY) {
            report.outputLocation.convention(reportsDir.dir("${task.name}/${report.name}"))
        } else {
            report.outputLocation.convention(reportsDir.file("${task.name}/${task.name}.${report.name}"))
        }
    }

    allprojects {
        pluginManager.withPlugin('jacoco') {
            TaskCollection testTasks = tasks.withType(Test)
            task.dependsOn(testTasks)

            TaskCollection reportTasks = tasks.withType(JacocoReport).matching { it.name.matches(/jacoco[A-Z].*Report/) }
            task.dependsOn(reportTasks)

            task.executionData(testTasks)
            pluginManager.withPlugin('java') {
                task.sourceSets(sourceSets.main)
            }
        }
    }
}

tasks.register('displayTotalCodeCoverage') { Task task ->
    task.dependsOn('jacocoReportMerged')
    task.group = 'verification'

    doLast {
        File xmlReportFile = jacocoReportMerged.reports.xml.outputLocation.asFile.get()

        Class XmlParser = Class.forName('groovy.xml.XmlParser')
        def xmlParser = XmlParser.getConstructor(Boolean.TYPE, Boolean.TYPE, Boolean.TYPE).newInstance(false, false, true)
        Closure setFeatureQuietly = { String feature, Object value ->
            try {
                xmlParser.setFeature(feature, value)
            } catch (Exception ignored) {
                // do nothing
            }
        }
        setFeatureQuietly('http://apache.org/xml/features/disallow-doctype-decl', false)
        setFeatureQuietly('http://apache.org/xml/features/nonvalidating/load-external-dtd', false)
        setFeatureQuietly('http://javax.xml.XMLConstants/feature/secure-processing', true)

        Node reportNode = xmlParser.parse(xmlReportFile)
        reportNode.counter?.forEach { Node counterNode ->
            String type = counterNode['@type']
            long missed = (counterNode['@missed'] ?: '0').toString().toLong()
            long covered = (counterNode['@covered'] ?: '0').toString().toLong()
            long total = missed + covered
            double coverage = ((double) covered) / total
            if ('LINE'.equalsIgnoreCase(type)) {
                project.logger.quiet(String.format('Total code coverage: %.2f%% lines', coverage * 100.0))
            }
        }
    }
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

class FailsCounterTestListener implements TestListener {

    private boolean failed = false

    private Throwable exception

    boolean isFailed() {
        return failed
    }

    private void processTestResult(TestResult testResult) {
        if (testResult.exception != null
            || !testResult.exceptions.isEmpty()
            || testResult.failedTestCount > 0
        ) {
            failed = true
        }

        if (testResult.exceptions.size() == 1) {
            exception = testResult.exceptions.get(0)
        }
    }

    @Nullable
    String createExceptionMessage() {
        if (!failed) {
            return null
        }

        return "Some tests failed"
    }

    @Nullable
    Throwable getException() {
        return exception
    }

    @Override
    void beforeSuite(TestDescriptor testDescriptor) {
        // do nothing
    }

    @Override
    void afterSuite(TestDescriptor testDescriptor, TestResult testResult) {
        processTestResult(testResult)
    }

    @Override
    void beforeTest(TestDescriptor testDescriptor) {
        // do nothing
    }

    @Override
    void afterTest(TestDescriptor testDescriptor, TestResult testResult) {
        processTestResult(testResult)
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy