Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 11 additions & 55 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -567,75 +567,31 @@ jacoco {
}

jacocoTestReport {
dependsOn test
dependsOn test, testng
reports {
xml.required = true
html.required = true
csv.required = false
}

// Exclude generated ANTLR code from coverage
// Include execution data from both the JUnit (test) and TestNG (testng) tasks
// so that coverage from TestNG tests is not missing from the report.
executionData.setFrom(fileTree(layout.buildDirectory.dir('jacoco')).include('*.exec'))

// Use the original compiled classes (not a modified copy) so that JaCoCo's
// CRC64 checksums match the execution data recorded during testing.
// Exclude generated ANTLR parser code and shaded dependencies from coverage.
afterEvaluate {
classDirectories.setFrom(files(classDirectories.files.collect {
fileTree(dir: it, exclude: [
classDirectories.setFrom(files(
fileTree(dir: layout.buildDirectory.dir('classes/java/main'), exclude: [
'graphql/parser/antlr/**',
'graphql/com/google/**',
'graphql/org/antlr/**'
])
}))
}
}

// ---------------------------------------------------------------------------
// Mark identity equals(Object)/hashCode() with a @Generated annotation so
// JaCoCo's AnnotationGeneratedFilter excludes them from coverage.
// The annotation class need not exist — JaCoCo only inspects the descriptor
// string in the bytecode, and the JVM ignores unknown CLASS-retention
// annotations.
// ---------------------------------------------------------------------------
tasks.register('markGeneratedEqualsHashCode') {
description = 'Add @Generated annotation to equals/hashCode so JaCoCo ignores them'
dependsOn classes

doLast {
def dir = layout.buildDirectory.dir('classes/java/main').get().asFile
if (!dir.exists()) return

def ANNOTATION = 'Lgraphql/coverage/Generated;'

dir.eachFileRecurse(groovy.io.FileType.FILES) { file ->
if (!file.name.endsWith('.class')) return

def bytes = file.bytes
def classNode = new org.objectweb.asm.tree.ClassNode()
new org.objectweb.asm.ClassReader(bytes).accept(classNode, 0)

boolean modified = false
for (method in classNode.methods) {
if ((method.name == 'equals' && method.desc == '(Ljava/lang/Object;)Z') ||
(method.name == 'hashCode' && method.desc == '()I')) {
if (method.invisibleAnnotations == null) {
method.invisibleAnnotations = []
}
method.invisibleAnnotations.add(new org.objectweb.asm.tree.AnnotationNode(ANNOTATION))
modified = true
}
}

if (modified) {
def writer = new org.objectweb.asm.ClassWriter(0)
classNode.accept(writer)
file.bytes = writer.toByteArray()
}
}
))
}
}

// Ensure the annotation task runs before anything that reads the main class files
tasks.named('test') { dependsOn markGeneratedEqualsHashCode }
tasks.named('compileTestJava') { dependsOn markGeneratedEqualsHashCode }
tasks.named('jar') { dependsOn markGeneratedEqualsHashCode }

/*
* The gradle.buildFinished callback is deprecated BUT there does not seem to be a decent alternative in gradle 7
* So progress over perfection here
Expand Down
Loading