diff --git a/build.gradle b/build.gradle index 63f1e283b..562142947 100644 --- a/build.gradle +++ b/build.gradle @@ -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