Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dumping JaCoCo Coverage into the JSON file #118

Merged
merged 4 commits into from
Jul 31, 2024
Merged
Show file tree
Hide file tree
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
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package org.vorpal.research.kex.jacoco

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import org.vorpal.research.kthelper.assert.unreachable
import org.vorpal.research.kthelper.logging.log


interface CoverageInfo {
@Serializable
sealed interface CoverageInfo {
val covered: Int
val total: Int
val ratio: Double
}

@Serializable
enum class CoverageUnit(unit: String) {
INSTRUCTION("instructions"),
BRANCH("branches"),
Expand All @@ -33,6 +36,7 @@ enum class CoverageUnit(unit: String) {
}
}

@Serializable
enum class AnalysisUnit(unit: String) {
METHOD("method"),
CLASS("class"),
Expand All @@ -54,6 +58,8 @@ enum class AnalysisUnit(unit: String) {
}
}

@Serializable
@SerialName("genericCoverage")
data class GenericCoverageInfo(
override val covered: Int,
override val total: Int,
Expand All @@ -74,7 +80,8 @@ data class GenericCoverageInfo(
}
}

abstract class CommonCoverageInfo(
@Serializable
sealed class CommonCoverageInfo(
val name: String,
val level: AnalysisUnit,
val instructionCoverage: CoverageInfo,
Expand Down Expand Up @@ -103,7 +110,8 @@ abstract class CommonCoverageInfo(
return name.hashCode()
}
}

@Serializable(with = MethodCoverageInfoSerializer::class)
@SerialName("method")
class MethodCoverageInfo(
name: String,
instructionCoverage: CoverageInfo,
Expand All @@ -119,6 +127,8 @@ class MethodCoverageInfo(
complexityCoverage
)

@Serializable(with = ClassCoverageInfoSerializer::class)
@SerialName("class")
class ClassCoverageInfo(
name: String,
instructionCoverage: CoverageInfo,
Expand Down Expand Up @@ -146,6 +156,8 @@ class ClassCoverageInfo(
}
}

@Serializable(with = PackageCoverageInfoSerializer::class)
@SerialName("package")
class PackageCoverageInfo(
name: String,
instructionCoverage: CoverageInfo,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

package org.vorpal.research.kex.jacoco

import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import org.jacoco.core.analysis.Analyzer
import org.jacoco.core.analysis.CoverageBuilder
import org.jacoco.core.analysis.ICounter
Expand Down Expand Up @@ -44,12 +46,8 @@ import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.attribute.BasicFileAttributes
import java.util.*
import kotlin.io.path.inputStream
import kotlin.io.path.name
import kotlin.io.path.readBytes
import kotlin.io.path.relativeTo
import kotlin.io.path.writeBytes
import kotlin.streams.toList
import java.util.stream.Collectors
import kotlin.io.path.*
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
Expand Down Expand Up @@ -92,7 +90,7 @@ open class CoverageReporter(
}.also {
deleteOnExit(it)
}
val allTestClasses: Set<Path> by lazy { Files.walk(compileDir).filter { it.isClass }.toList().toSet() }
val allTestClasses: Set<Path> by lazy { Files.walk(compileDir).filter { it.isClass }.collect(Collectors.toSet()) }
protected val compileDir = kexConfig.compiledCodeDirectory
protected lateinit var coverageContext: CoverageContext
protected lateinit var executionData: Map<Path, ExecutionDataStore>
Expand All @@ -108,7 +106,7 @@ open class CoverageReporter(
open fun computeCoverage(
analysisLevel: AnalysisLevel,
testClasses: Set<Path> = this.allTestClasses
): CommonCoverageInfo {
): Set<CommonCoverageInfo> {
ktassert(this.allTestClasses.containsAll(testClasses)) {
log.error("Unexpected set of test classes")
}
Expand All @@ -117,9 +115,9 @@ open class CoverageReporter(
val coverageBuilder = getCoverageBuilderAndCleanup(classes, testClasses)

return when (analysisLevel) {
is PackageLevel -> getPackageCoverage(analysisLevel.pkg, cm, coverageBuilder)
is ClassLevel -> getClassCoverage(cm, coverageBuilder).first()
is MethodLevel -> getMethodCoverage(coverageBuilder, analysisLevel.method)!!
is PackageLevel -> setOf(getPackageCoverage(analysisLevel.pkg, cm, coverageBuilder))
is ClassLevel -> getClassCoverage(cm, coverageBuilder)
is MethodLevel -> setOf(getMethodCoverage(coverageBuilder, analysisLevel.method)!!)
}
}

Expand Down Expand Up @@ -156,11 +154,22 @@ open class CoverageReporter(
is PackageLevel -> Files.walk(jacocoInstrumentedDir)
.filter { it.isClass }
.filter { analysisLevel.pkg.isParent(it.fullyQualifiedName(jacocoInstrumentedDir).asmString) }
.toList()
.collect(Collectors.toList())

is ClassLevel -> {
val klass = analysisLevel.klass.fullName.replace(Package.SEPARATOR, File.separatorChar)
listOf(jacocoInstrumentedDir.resolve("$klass.class"))
val additionalValues = kexConfig
.getStringValue("kex", "collectAdditionalCoverage")
?.split(",")
?.map { Package.parse(it.trim().asmString) }
?.toSet()
?: emptySet()
val targetKlass = analysisLevel.klass.fullName.replace(Package.SEPARATOR, File.separatorChar)
val targetFiles = listOf(jacocoInstrumentedDir.resolve("$targetKlass.class"))
val additionalFiles = Files.walk(jacocoInstrumentedDir)
.filter { it.isClass }
.filter { additionalValues.any { value -> value.isParent(it.fullyQualifiedName(jacocoInstrumentedDir).asmString) } }
.collect(Collectors.toList())
targetFiles + additionalFiles
}

is MethodLevel -> {
Expand Down Expand Up @@ -389,17 +398,21 @@ fun reportCoverage(
coverageSaturation.toList()
)
PermanentSaturationCoverageInfo.emit()
coverageSaturation[coverageSaturation.lastKey()]!!
setOf(coverageSaturation[coverageSaturation.lastKey()]!!)
}

else -> coverageReporter.computeCoverage(analysisLevel, testClasses)
}
kexConfig.getPathValue("kex", "coverageJsonLocation")
?.writeText(Json.encodeToString(coverageInfo))

log.info(
coverageInfo.print(kexConfig.getBooleanValue("kex", "printDetailedCoverage", false))
coverageInfo.joinToString(System.lineSeparator()) {
it.print(kexConfig.getBooleanValue("kex", "printDetailedCoverage", false))
}
)

PermanentCoverageInfo.putNewInfo(mode, analysisLevel.toString(), coverageInfo)
PermanentCoverageInfo.putNewInfo(mode, analysisLevel.toString(), coverageInfo.first())
PermanentCoverageInfo.emit()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package org.vorpal.research.kex.jacoco

import kotlinx.serialization.KSerializer
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder

/**
* A surrogate class used to simplify the code of the [SurrogateCoverageInfo]
* by providing basic functionality of the decoding/encoding of the [CommonCoverageInfo]. The trick is found in
* [kotlinx serialization guide](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/serializers.md#composite-serializer-via-surrogate).
*
*
* The abstract class cannot be used in the first way to provide the serializer to abstract class
*/
@Serializable
private sealed class SurrogateCoverageInfo {
abstract val name: String
abstract val level: AnalysisUnit
abstract val instructionCoverage: CoverageInfo
abstract val branchCoverage: CoverageInfo
abstract val linesCoverage: CoverageInfo
abstract val complexityCoverage: CoverageInfo
}

@Serializable
@SerialName("method")
private data class SurrogateMethodCoverageInfo(
override val name: String,
override val level: AnalysisUnit,
override val instructionCoverage: CoverageInfo,
override val branchCoverage: CoverageInfo,
override val linesCoverage: CoverageInfo,
override val complexityCoverage: CoverageInfo
) : SurrogateCoverageInfo() {
val value: MethodCoverageInfo
get() = MethodCoverageInfo(name, instructionCoverage, branchCoverage, linesCoverage, complexityCoverage)

companion object {
fun fromValue(value: MethodCoverageInfo) = SurrogateMethodCoverageInfo(
value.name,
value.level,
value.instructionCoverage,
value.branchCoverage,
value.linesCoverage,
value.complexityCoverage,
)
}
}

@Serializable
@SerialName("class")
private data class SurrogateClassCoverageInfo(
override val name: String,
override val level: AnalysisUnit,
override val instructionCoverage: CoverageInfo,
override val branchCoverage: CoverageInfo,
override val linesCoverage: CoverageInfo,
override val complexityCoverage: CoverageInfo,
val methods: List<SurrogateMethodCoverageInfo>
) : SurrogateCoverageInfo() {
val value: ClassCoverageInfo
get() {
val result = ClassCoverageInfo(name, instructionCoverage, branchCoverage, linesCoverage, complexityCoverage)
result.methods.addAll(methods.map { it.value })
return result
}

companion object {
fun fromValue(value: ClassCoverageInfo) = SurrogateClassCoverageInfo(
value.name,
value.level,
value.instructionCoverage,
value.branchCoverage,
value.linesCoverage,
value.complexityCoverage,
value.methods.map { SurrogateMethodCoverageInfo.fromValue(it) }
)
}
}

@Serializable
@SerialName("package")
private data class SurrogatePackageCoverageInfo(
override val name: String,
override val level: AnalysisUnit,
override val instructionCoverage: CoverageInfo,
override val branchCoverage: CoverageInfo,
override val linesCoverage: CoverageInfo,
override val complexityCoverage: CoverageInfo,
val classes: List<SurrogateClassCoverageInfo>,
) : SurrogateCoverageInfo() {
val value: PackageCoverageInfo
get() {
val result =
PackageCoverageInfo(name, instructionCoverage, branchCoverage, linesCoverage, complexityCoverage)
result.classes.addAll(classes.map { it.value })
return result
}

companion object {
fun fromValue(value: PackageCoverageInfo) = SurrogatePackageCoverageInfo(
value.name,
value.level,
value.instructionCoverage,
value.branchCoverage,
value.linesCoverage,
value.complexityCoverage,
value.classes.map { SurrogateClassCoverageInfo.fromValue(it) }
)
}
}

object MethodCoverageInfoSerializer : KSerializer<MethodCoverageInfo> {
override val descriptor: SerialDescriptor = SurrogateMethodCoverageInfo.serializer().descriptor
override fun serialize(encoder: Encoder, value: MethodCoverageInfo) {
val surrogate = SurrogateMethodCoverageInfo.fromValue(value)
encoder.encodeSerializableValue(SurrogateMethodCoverageInfo.serializer(), surrogate)
}

override fun deserialize(decoder: Decoder): MethodCoverageInfo {
val surrogate = decoder.decodeSerializableValue(SurrogateMethodCoverageInfo.serializer())
return surrogate.value
}
}

object ClassCoverageInfoSerializer : KSerializer<ClassCoverageInfo> {
override val descriptor: SerialDescriptor = SurrogateClassCoverageInfo.serializer().descriptor
override fun serialize(encoder: Encoder, value: ClassCoverageInfo) {
val surrogate = SurrogateClassCoverageInfo.fromValue(value)
encoder.encodeSerializableValue(SurrogateClassCoverageInfo.serializer(), surrogate)
}
override fun deserialize(decoder: Decoder): ClassCoverageInfo {
val surrogate = decoder.decodeSerializableValue(SurrogateClassCoverageInfo.serializer())
return surrogate.value
}
}

object PackageCoverageInfoSerializer : KSerializer<PackageCoverageInfo> {
override val descriptor: SerialDescriptor = SurrogatePackageCoverageInfo.serializer().descriptor
override fun serialize(encoder: Encoder, value: PackageCoverageInfo) {
val surrogate = SurrogatePackageCoverageInfo.fromValue(value)
encoder.encodeSerializableValue(SurrogatePackageCoverageInfo.serializer(), surrogate)
}

override fun deserialize(decoder: Decoder): PackageCoverageInfo {
val surrogate = decoder.decodeSerializableValue(SurrogatePackageCoverageInfo.serializer())
return surrogate.value
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ abstract class ConcolicTest(testDirectoryName: String) : KexRunnerTest(testDirec
}

val coverage = CoverageReporter(klass.cm, listOf(jar)).computeCoverage(ClassLevel(klass))
log.debug(coverage.print(true))
assertEquals(expectedCoverage, coverage.instructionCoverage.ratio, eps)
log.debug(coverage.first().print(true))
assertEquals(expectedCoverage, coverage.first().instructionCoverage.ratio, eps)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ abstract class SymbolicTest(
}

val coverage = CoverageReporter(klass.cm, listOf(jar)).computeCoverage(ClassLevel(klass))
log.debug(coverage.print(true))
assertEquals(expectedCoverage, coverage.instructionCoverage.ratio, eps)
log.debug(coverage.first().print(true))
assertEquals(expectedCoverage, coverage.first().instructionCoverage.ratio, eps)
}
}