Skip to content

Commit

Permalink
Merge pull request #118 from khbminus/khbminus/json-coverage
Browse files Browse the repository at this point in the history
Dumping JaCoCo Coverage into the JSON file
  • Loading branch information
AbdullinAM authored Jul 31, 2024
2 parents f91c56f + 293b36f commit f2498a6
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 25 deletions.
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)
}
}

0 comments on commit f2498a6

Please sign in to comment.