diff --git a/README.md b/README.md index 7d61fdaad..f45f79ed6 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Docker images with the latest version installed. Example: ```bash docker run -v ~/myproject:/home/myproject -v ~/kex-output:/home/kex-output \ - abdullin/kex-standalone:0.0.7 --classpath /home/myproject/target/myproject.jar \ + abdullin/kex-standalone:0.0.8 --classpath /home/myproject/target/myproject.jar \ --target myproject.\* --output /home/kex-output --mode concolic ``` diff --git a/kex-annotation-processor/pom.xml b/kex-annotation-processor/pom.xml index ff8530ae0..05e33bd47 100644 --- a/kex-annotation-processor/pom.xml +++ b/kex-annotation-processor/pom.xml @@ -5,7 +5,7 @@ kex org.vorpal.research - 0.0.7 + 0.0.8 ../pom.xml jar diff --git a/kex-boolector/pom.xml b/kex-boolector/pom.xml index ee831097d..d12d5445e 100644 --- a/kex-boolector/pom.xml +++ b/kex-boolector/pom.xml @@ -5,7 +5,7 @@ kex org.vorpal.research - 0.0.7 + 0.0.8 4.0.0 diff --git a/kex-core/pom.xml b/kex-core/pom.xml index 057219572..dba1de06f 100644 --- a/kex-core/pom.xml +++ b/kex-core/pom.xml @@ -5,7 +5,7 @@ kex org.vorpal.research - 0.0.7 + 0.0.8 4.0.0 diff --git a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/FinalParameters.kt b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/FinalParameters.kt index 98a3d88c4..96b8345bd 100644 --- a/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/FinalParameters.kt +++ b/kex-core/src/main/kotlin/org/vorpal/research/kex/parameters/FinalParameters.kt @@ -78,11 +78,19 @@ class FinalParameters private constructor( } } -inline fun FinalParameters.map(transform: (F) -> U): FinalParameters = FinalParameters( - if (instance is F) transform(instance) else null, - args.map(transform), - if (returnValueUnsafe is F) transform(returnValueUnsafe) else null, -) +inline fun FinalParameters.map(transform: (F) -> U): FinalParameters = when { + this.isSuccess -> FinalParameters( + if (instance is F) transform(instance) else null, + args.map(transform), + if (returnValueUnsafe is F) transform(returnValueUnsafe) else null, + ) + + else -> FinalParameters( + if (instance is F) transform(instance) else null, + args.map(transform), + exceptionType + ) +} val FinalParameters?.isSuccessOrFalse: Boolean get() = this?.isSuccess ?: false val FinalParameters?.isExceptionOrFalse: Boolean get() = this?.isException ?: false diff --git a/kex-executor/pom.xml b/kex-executor/pom.xml index 3c9ab39ef..e9777d489 100644 --- a/kex-executor/pom.xml +++ b/kex-executor/pom.xml @@ -8,7 +8,7 @@ org.vorpal.research kex - 0.0.7 + 0.0.8 kex-executor jar diff --git a/kex-ksmt/pom.xml b/kex-ksmt/pom.xml index fea7b2502..6fb853982 100644 --- a/kex-ksmt/pom.xml +++ b/kex-ksmt/pom.xml @@ -5,7 +5,7 @@ kex org.vorpal.research - 0.0.7 + 0.0.8 4.0.0 diff --git a/kex-runner/pom.xml b/kex-runner/pom.xml index 71338a15e..1ff2207e7 100644 --- a/kex-runner/pom.xml +++ b/kex-runner/pom.xml @@ -5,7 +5,7 @@ kex org.vorpal.research - 0.0.7 + 0.0.8 4.0.0 diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/CoverageInfo.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/CoverageInfo.kt index e2ae42837..bcb0dd4f9 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/CoverageInfo.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/CoverageInfo.kt @@ -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"), @@ -33,6 +36,7 @@ enum class CoverageUnit(unit: String) { } } +@Serializable enum class AnalysisUnit(unit: String) { METHOD("method"), CLASS("class"), @@ -54,6 +58,8 @@ enum class AnalysisUnit(unit: String) { } } +@Serializable +@SerialName("genericCoverage") data class GenericCoverageInfo( override val covered: Int, override val total: Int, @@ -74,7 +80,8 @@ data class GenericCoverageInfo( } } -abstract class CommonCoverageInfo( +@Serializable +sealed class CommonCoverageInfo( val name: String, val level: AnalysisUnit, val instructionCoverage: CoverageInfo, @@ -103,7 +110,8 @@ abstract class CommonCoverageInfo( return name.hashCode() } } - +@Serializable(with = MethodCoverageInfoSerializer::class) +@SerialName("method") class MethodCoverageInfo( name: String, instructionCoverage: CoverageInfo, @@ -119,6 +127,8 @@ class MethodCoverageInfo( complexityCoverage ) +@Serializable(with = ClassCoverageInfoSerializer::class) +@SerialName("class") class ClassCoverageInfo( name: String, instructionCoverage: CoverageInfo, @@ -146,6 +156,8 @@ class ClassCoverageInfo( } } +@Serializable(with = PackageCoverageInfoSerializer::class) +@SerialName("package") class PackageCoverageInfo( name: String, instructionCoverage: CoverageInfo, diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/CoverageReporter.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/CoverageReporter.kt index 561550297..f87c82e20 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/CoverageReporter.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/CoverageReporter.kt @@ -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 @@ -13,7 +15,6 @@ import org.jacoco.core.instr.Instrumenter import org.jacoco.core.internal.analysis.PackageCoverageImpl import org.jacoco.core.runtime.LoggerRuntime import org.jacoco.core.runtime.RuntimeData -import org.junit.runner.Result import org.vorpal.research.kex.config.kexConfig import org.vorpal.research.kex.jacoco.minimization.GreedyTestReductionImpl import org.vorpal.research.kex.jacoco.minimization.TestwiseCoverageReporter @@ -28,6 +29,7 @@ import org.vorpal.research.kex.util.asArray import org.vorpal.research.kex.util.asmString import org.vorpal.research.kex.util.compiledCodeDirectory import org.vorpal.research.kex.util.deleteOnExit +import org.vorpal.research.kex.util.getFieldByName import org.vorpal.research.kex.util.javaString import org.vorpal.research.kex.util.outputDirectory import org.vorpal.research.kfg.ClassManager @@ -36,6 +38,7 @@ import org.vorpal.research.kfg.container.Container import org.vorpal.research.kfg.ir.Method import org.vorpal.research.kthelper.assert.ktassert import org.vorpal.research.kthelper.assert.unreachable +import org.vorpal.research.kthelper.logging.debug import org.vorpal.research.kthelper.logging.log import org.vorpal.research.kthelper.tryOrNull import java.io.File @@ -43,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 @@ -91,7 +90,7 @@ open class CoverageReporter( }.also { deleteOnExit(it) } - val allTestClasses: Set by lazy { Files.walk(compileDir).filter { it.isClass }.toList().toSet() } + val allTestClasses: Set 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 @@ -107,7 +106,7 @@ open class CoverageReporter( open fun computeCoverage( analysisLevel: AnalysisLevel, testClasses: Set = this.allTestClasses - ): CommonCoverageInfo { + ): Set { ktassert(this.allTestClasses.containsAll(testClasses)) { log.error("Unexpected set of test classes") } @@ -116,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)!!) } } @@ -155,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 -> { @@ -258,10 +268,15 @@ open class CoverageReporter( val returnValue = jcClass.getMethod("run", computerClass, Class::class.java.asArray()) .invoke(jc, computerClass.newInstance(), arrayOf(testClass)) - if (logJUnit && !(returnValue as? Result)?.failures.isNullOrEmpty()) { + val resultClass = classLoader.loadClass("org.junit.runner.Result") + val failures = resultClass.getFieldByName("failures") + .also { it.isAccessible = true } + .get(returnValue) as List<*> + + if (logJUnit && failures.isNotEmpty()) { log.debug("Failures:") - (returnValue as? Result)?.failures?.forEach { - log.debug(it.trace) + failures.forEach { + log.debug(it) } } val executionData = ExecutionDataStore() @@ -383,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() } } diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/Serializers.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/Serializers.kt new file mode 100644 index 000000000..040070294 --- /dev/null +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/jacoco/Serializers.kt @@ -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 +) : 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, +) : 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 { + 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 { + 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 { + 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 + } +} + diff --git a/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt b/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt index 27550ac13..c896565d0 100644 --- a/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt +++ b/kex-runner/src/main/kotlin/org/vorpal/research/kex/trace/runner/SymbolicExternalTracingRunner.kt @@ -43,7 +43,7 @@ internal object ExecutorMasterController : AutoCloseable { controllerSocket = ControllerProtocolSocketHandler(ctx) val outputDir = kexConfig.outputDirectory val executorPath = kexConfig.getPathValue("executor", "executorPath") { - kexConfig.kexHome.resolve("kex-executor/target/kex-executor-0.0.7-jar-with-dependencies.jar") + kexConfig.kexHome.resolve("kex-executor/target/kex-executor-0.0.8-jar-with-dependencies.jar") }.toAbsolutePath() val executorKlass = "org.vorpal.research.kex.launcher.MasterLauncherKt" val executorConfigPath = kexConfig.getPathValue("executor", "executorConfigPath") { diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/ConcolicTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/ConcolicTest.kt index c90ed0af3..77cb89fbf 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/ConcolicTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/concolic/ConcolicTest.kt @@ -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) } } } diff --git a/kex-runner/src/test/kotlin/org/vorpal/research/kex/symbolic/SymbolicTest.kt b/kex-runner/src/test/kotlin/org/vorpal/research/kex/symbolic/SymbolicTest.kt index aec99e3a5..3c92e02d7 100644 --- a/kex-runner/src/test/kotlin/org/vorpal/research/kex/symbolic/SymbolicTest.kt +++ b/kex-runner/src/test/kotlin/org/vorpal/research/kex/symbolic/SymbolicTest.kt @@ -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) } } diff --git a/kex-test.ini b/kex-test.ini index 096aed9f8..e823a6d4b 100644 --- a/kex-test.ini +++ b/kex-test.ini @@ -68,7 +68,7 @@ maxStringLength = 100 generationAttempts = 100 [executor] -executorPath = ../kex-executor/target/kex-executor-0.0.7-jar-with-dependencies.jar +executorPath = ../kex-executor/target/kex-executor-0.0.8-jar-with-dependencies.jar executorConfigPath = ../kex-test.ini executorPolicyPath = ../kex.policy numberOfWorkers = 1 diff --git a/kex-test/pom.xml b/kex-test/pom.xml index 1218b7620..cf0222e6c 100644 --- a/kex-test/pom.xml +++ b/kex-test/pom.xml @@ -5,7 +5,7 @@ 4.0.0 org.vorpal.research - 0.0.7 + 0.0.8 kex-test diff --git a/kex-z3/pom.xml b/kex-z3/pom.xml index bf46fbd46..2079dd933 100644 --- a/kex-z3/pom.xml +++ b/kex-z3/pom.xml @@ -5,7 +5,7 @@ kex org.vorpal.research - 0.0.7 + 0.0.8 4.0.0 diff --git a/kex.ini b/kex.ini index ab63db863..4c350f564 100644 --- a/kex.ini +++ b/kex.ini @@ -25,7 +25,7 @@ accessLevel = private testCaseLanguage = java generateSetup = true generateAssertions = true -logJUnit = false +logJUnit = true testTimeout = 10 surroundInTryCatch = false diff --git a/kex.py b/kex.py index 446da3dbb..81dc06639 100755 --- a/kex.py +++ b/kex.py @@ -5,7 +5,7 @@ import subprocess import sys -KEX_VERSION = "0.0.7" +KEX_VERSION = "0.0.8" HEAP_MEMORY_SIZE = "8g" STACK_MEMORY_SIZE = "1g" diff --git a/pom.xml b/pom.xml index 1a06194fb..e5bd78114 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ org.vorpal.research kex pom - 0.0.7 + 0.0.8 kex-test kex-core