diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt index dc6aa019d1..1b94267218 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/ContestEstimator.kt @@ -26,7 +26,7 @@ import org.utbot.contest.Paths.evosuiteReportFile import org.utbot.contest.Paths.jarsDir import org.utbot.contest.Paths.moduleTestDir import org.utbot.contest.Paths.outputDir -import org.utbot.contest.usvm.executor.UTestRunner +import org.utbot.contest.usvm.jc.JcContainer import org.utbot.contest.usvm.runUsvmGeneration import org.utbot.features.FeatureExtractorFactoryImpl import org.utbot.features.FeatureProcessorWithStatesRepetitionFactory @@ -266,8 +266,7 @@ interface Tool { ) override fun close() { - if (UTestRunner.isInitialized()) - UTestRunner.runner.close() + JcContainer.close() } } diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/ContestUsvm.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/ContestUsvm.kt index 53805bcdf3..8364cfaac3 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/ContestUsvm.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/ContestUsvm.kt @@ -2,6 +2,9 @@ package org.utbot.contest.usvm import kotlinx.coroutines.* import mu.KotlinLogging +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcMethod +import org.jacodb.api.JcTypedMethod import org.jacodb.api.ext.findClass import org.jacodb.api.ext.jcdbSignature import org.jacodb.api.ext.toType @@ -10,17 +13,18 @@ import org.jacodb.impl.features.InMemoryHierarchy import org.objectweb.asm.Type import org.usvm.UMachineOptions import org.usvm.instrumentation.util.jcdbSignature -import org.usvm.machine.JcMachine import org.usvm.machine.state.JcState import org.utbot.common.ThreadBasedExecutor -import org.utbot.common.debug import org.utbot.common.info import org.utbot.common.measureTime import org.utbot.contest.* import org.utbot.contest.junitVersion -import org.utbot.contest.testMethodName -import org.utbot.contest.usvm.executor.JcTestExecutor -import org.utbot.contest.usvm.executor.UTestRunner +import org.utbot.contest.usvm.converter.JcToUtExecutionConverter +import org.utbot.contest.usvm.converter.SimpleInstructionIdProvider +import org.utbot.contest.usvm.converter.toExecutableId +import org.utbot.contest.usvm.jc.JcContainer +import org.utbot.contest.usvm.jc.JcContainer.Companion.CONTEST_TEST_EXECUTION_TIMEOUT +import org.utbot.contest.usvm.jc.JcTestExecutor import org.utbot.framework.codegen.domain.ProjectType import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour import org.utbot.framework.codegen.domain.junitByVersion @@ -38,8 +42,8 @@ import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.summary.usvm.summarizeAll import java.io.File import java.net.URLClassLoader -import java.util.* import kotlin.math.max +import kotlin.time.Duration.Companion.milliseconds private val logger = KotlinLogging.logger {} @@ -58,20 +62,25 @@ fun runUsvmGeneration( val testsByMethod: MutableMap> = mutableMapOf() val timeBudgetMs = timeLimitSec * 1000 - val generationTimeoutMillisWithoutCodegen: Long = timeBudgetMs - timeBudgetMs * 15 / 100 // 15% to terminate all activities and finalize code in file + + // 15% to terminate all activities and finalize code in file + val generationTimeoutMillisWithoutCodegen: Long = timeBudgetMs - timeBudgetMs * 15 / 100 + // 15% for instrumentation + // TODO usvm-sbft: when `jcMachine.analyzeAsync(): Flow` is added run `analyze` and `execute` under common timeout + val jcMachineTimeoutMillis: Long = generationTimeoutMillisWithoutCodegen - timeBudgetMs * 15 / 100 logger.debug { "-----------------------------------------------------------------------------" } logger.info( - "Contest.runGeneration: Time budget: $timeBudgetMs ms, Generation timeout=$generationTimeoutMillisWithoutCodegen ms, " + + "Contest.runGeneration: Time budget: $timeBudgetMs ms, jcMachine timeout=$jcMachineTimeoutMillis ms, " + "classpath=$classpathString, methodNameFilter=$methodNameFilter" ) val classpathFiles = classpathString.split(File.pathSeparator).map { File(it) } - val jcDbContainer by lazy { - JacoDBContainer( - key = classpathString, + val jcContainer by lazy { + JcContainer( classpath = classpathFiles, + machineOptions = UMachineOptions(timeout = jcMachineTimeoutMillis.milliseconds) ) { // TODO usvm-sbft: we may want to tune these JcSettings for contest useJavaRuntime(JdkInfoService.provide().path.toFile()) @@ -80,40 +89,28 @@ fun runUsvmGeneration( } } - val runner by lazy { - if (!UTestRunner.isInitialized()) - UTestRunner.initRunner(classpathFiles.map { it.absolutePath }, jcDbContainer.cp) - UTestRunner.runner - } - - val resolver by lazy { JcTestExecutor(jcDbContainer.cp) } + val executor by lazy { JcTestExecutor(jcContainer.cp, jcContainer.runner) } - val idGenerator = ReferencePreservingIntIdGenerator() - - val instructionIds = mutableMapOf, Long>() - val instructionIdProvider = InstructionIdProvider { methodSignature, instrIndex -> - instructionIds.getOrPut(methodSignature to instrIndex) { instructionIds.size.toLong() } - } + val utModelIdGenerator = ReferencePreservingIntIdGenerator() + val instructionIdGenerator = SimpleInstructionIdProvider() if (runFromEstimator) { - setOptions() - //will not be executed in real contest + setOptions() // UtBot options (aka UtSettings) + // will not be executed in real contest (see ContestKt.main() for more details) logger.info().measureTime({ "warmup: 1st optional JacoDB initialization (not to be counted in time budget)" }) { - jcDbContainer // force init lazy property + jcContainer // force init lazy property } logger.info().measureTime({ "warmup: 1st optional executor start (not to be counted in time budget)" }) { - runner.ensureRunnerAlive() + jcContainer.runner.ensureRunnerAlive() } } - //remaining budget + logger.info { "STARTED COUNTING BUDGET FOR ${cut.classId.name}" } + val startTime = System.currentTimeMillis() - logger.debug { "STARTED COUNTING BUDGET FOR ${cut.classId.name}" } fun remainingBudgetMillisWithoutCodegen() = max(0, generationTimeoutMillisWithoutCodegen - (System.currentTimeMillis() - startTime)) - logger.info("$cut") - if (cut.classLoader.javaClass != URLClassLoader::class.java) { logger.error("Seems like classloader for cut not valid (maybe it was backported to system): ${cut.classLoader}") } @@ -139,134 +136,128 @@ fun runUsvmGeneration( } logger.info().measureTime({ "preparation: ensure JacoDB is initialized (counted in time budget)" }) { - jcDbContainer // force init lazy property + jcContainer // force init lazy property } logger.info().measureTime({ "preparation: ensure executor is started (counted in time budget)" }) { - runner.ensureRunnerAlive() + jcContainer.runner.ensureRunnerAlive() } - // TODO usvm-sbft: better budget management - val totalBudgetPerMethod = remainingBudgetMillisWithoutCodegen() / filteredMethods.size - val concreteBudgetMsPerMethod = 500L - .coerceIn((totalBudgetPerMethod / 10L).. (totalBudgetPerMethod / 2L)) - val symbolicBudgetPerMethod = totalBudgetPerMethod - concreteBudgetMsPerMethod - logger.debug { "Symbolic budget per method: $symbolicBudgetPerMethod" } - - // TODO usvm-sbft: reuse same machine for different classes, - // right now I can't do that, because `timeoutMs` can't be changed after machine creation - logger.info().measureTime({ "preparation: creating JcMachine" }) { - JcMachine( - cp = jcDbContainer.cp, - // TODO usvm-sbft: we may want to tune UMachineOptions for contest - options = UMachineOptions(timeoutMs = symbolicBudgetPerMethod) + statsForClass.methodsCount = filteredMethods.size + val methodToStats = filteredMethods.associateWith { method -> + StatsForMethod( + "${method.classId.simpleName}#${method.name}", + expectedExceptions.getForMethod(method.name).exceptionNames ) - }.use { machine -> - statsForClass.methodsCount = filteredMethods.size + }.onEach { (_, statsForMethod) -> statsForClass.statsForMethods.add(statsForMethod) } - // nothing to process further - if (filteredMethods.isEmpty()) return@runBlocking statsForClass + val jcMethods = filteredMethods.mapNotNull { methodId -> + jcContainer.cp.findMethodOrNull(methodId).also { + if (it == null) logger.error { "Method [$methodId] not found in jcClasspath [${jcContainer.cp}]" } + } + } - for (method in filteredMethods) { - val jcClass = jcDbContainer.cp.findClass(method.classId.name) + // nothing to process further + if (jcMethods.isEmpty()) return@runBlocking statsForClass + + val states = logger.info().measureTime({ "machine.analyze(${cut.classId.name})" }) { + ((ThreadBasedExecutor.threadLocal.invokeWithTimeout(jcMachineTimeoutMillis * 11 / 10) { + // TODO usvm-sbft: sometimes `machine.analyze` hangs forever, completely ignoring timeout specified for it + jcContainer.machine.analyze(jcMethods, targets = emptyList()) + } as? Result>) ?: run { + logger.error { "machine.analyze(${cut.classId.name}) timed out" } + Result.success(emptyList()) + }).getOrElse { e -> + logger.error(e) { "machine.analyze(${cut.classId.name}) failed" } + emptyList() + } + } - val jcTypedMethod = jcClass.toType().declaredMethods.firstOrNull { - it.name == method.name && it.method.jcdbSignature == when (method) { - is ConstructorId -> method.constructor.jcdbSignature - is MethodId -> method.method.jcdbSignature - } - } - if (jcTypedMethod == null) { - logger.error { "Method [$method] not found in jcClass [$jcClass]" } - continue - } - val states = logger.debug().measureTime({ "machine.analyze(${method.classId}.${method.signature})" }) { - ((ThreadBasedExecutor.threadLocal.invokeWithTimeout(10 * symbolicBudgetPerMethod) { - machine.analyze(jcTypedMethod.method) - } as? Result>) ?: run { - logger.error { "machine.analyze(${jcTypedMethod.method}) timed out" } - Result.success(emptyList()) - }).getOrElse { e -> - logger.error("JcMachine failed", e) - emptyList() + val jcExecutions = logger.info().measureTime({ "executor.execute(${cut.classId.name})" }) { + states.mapNotNull { state -> + // TODO usvm-sbft: if we have less than CONTEST_TEST_EXECUTION_TIMEOUT time left, we should + // try executing with smaller timeout, but instrumentation currently doesn't allow to change timeout + if (remainingBudgetMillisWithoutCodegen() > CONTEST_TEST_EXECUTION_TIMEOUT.inWholeMilliseconds) { + runCatching { + executor.execute(state.entrypoint.typedMethod, state) + }.getOrElse { e -> + logger.error(e) { "executor.execute(${state.entrypoint}) failed" } + null } + } else { + logger.warn { "executor.execute(${cut.classId.name}) run out of time" } + null } - val jcExecutions = states.mapNotNull { - if (remainingBudgetMillisWithoutCodegen() > UTestRunner.CONTEST_TEST_EXECUTION_TIMEOUT.inWholeMilliseconds) - logger.debug().measureTime({ "resolver.resolve(${method.classId}.${method.signature}, ...)" }) { - runCatching { - resolver.resolve(jcTypedMethod, it) - }.getOrElse { e -> - logger.error(e) { "Resolver failed" } - null - } - } - else null - } + } + } - var testsCounter = 0 - val statsForMethod = StatsForMethod( - "${method.classId.simpleName}#${method.name}", - expectedExceptions.getForMethod(method.name).exceptionNames - ) - statsForClass.statsForMethods.add(statsForMethod) - - val utExecutions: List = jcExecutions.mapNotNull { - logger.debug().measureTime({ "Convert JcExecution" }) { - try { - JcToUtExecutionConverter( - jcExecution = it, - jcClasspath = jcDbContainer.cp, - idGenerator = idGenerator, - instructionIdProvider = instructionIdProvider, - utilMethodProvider = codeGenerator.context.utilMethodProvider - ).convert() - } catch (e: Exception) { - logger.error(e) { - "Can't convert execution for method ${method.name}, exception is ${e.message}" - } - null - } - } + val utExecutions = logger.info().measureTime({"JcToUtExecutionConverter.convert(${cut.classId.name})"}) { + jcExecutions.mapNotNull { jcExecution -> + try { + val methodId = jcExecution.method.method.toExecutableId(jcContainer.cp) + JcToUtExecutionConverter( + jcExecution = jcExecution, + jcClasspath = jcContainer.cp, + idGenerator = utModelIdGenerator, + instructionIdProvider = instructionIdGenerator, + utilMethodProvider = codeGenerator.context.utilMethodProvider + ).convert()?.let { it to methodId } + } catch (e: Exception) { + logger.error(e) { "JcToUtExecutionConverter.convert(${jcExecution.method.method}) failed" } + null } + } + } - utExecutions.forEach { result -> - try { - val testMethodName = testMethodName(method.toString(), ++testsCounter) - val className = Type.getInternalName(method.classId.jClass) - logger.debug { "--new testCase collected, to generate: $testMethodName" } - statsForMethod.testsGeneratedCount++ - result.result.exceptionOrNull()?.let { exception -> - statsForMethod.detectedExceptionFqns += exception::class.java.name - } - result.coverage?.let { - statsForClass.updateCoverage( - newCoverage = it, - isNewClass = !statsForClass.testedClassNames.contains(className), - fromFuzzing = result is UtFuzzedExecution - ) - } - statsForClass.testedClassNames.add(className) - - testsByMethod.getOrPut(method) { mutableListOf() } += result - } catch (e: Throwable) { - //Here we need isolation - logger.error(e) { "Test generation failed during stats update" } + logger.info().measureTime({"Collect stats for ${cut.classId.name}"}) { + utExecutions.forEach { (utExecution, methodId) -> + try { + val className = Type.getInternalName(methodId.classId.jClass) + val statsForMethod = methodToStats.getValue(methodId) + statsForMethod.testsGeneratedCount++ + utExecution.result.exceptionOrNull()?.let { exception -> + statsForMethod.detectedExceptionFqns += exception::class.java.name } + utExecution.coverage?.let { + statsForClass.updateCoverage( + newCoverage = it, + isNewClass = !statsForClass.testedClassNames.contains(className), + fromFuzzing = utExecution is UtFuzzedExecution + ) + } + statsForClass.testedClassNames.add(className) + testsByMethod.getOrPut(methodId) { mutableListOf() } += utExecution + } catch (e: Throwable) { + logger.error(e) { "Test generation failed during stats update for $methodId" } } - logger.debug { "Finished $method" } } } } - val testSets = testsByMethod.map { (method, executions) -> - UtMethodTestSet(method, minimizeExecutions(executions), jimpleBody = null) - }.summarizeAll() + val testSets = logger.info().measureTime({ "Code generation for ${cut.classId.name}" }) { + testsByMethod.map { (method, executions) -> + UtMethodTestSet(method, minimizeExecutions(executions), jimpleBody = null) + }.summarizeAll() + } - logger.info().measureTime({ "Flushing tests for [${cut.simpleName}] on disk" }) { + logger.info().measureTime({ "Flushing tests for [${cut.classId.name}] on disk" }) { writeTestClass(cut, codeGenerator.generateAsString(testSets)) } - logger.debug { "STOPPED COUNTING BUDGET FOR ${cut.classId.name}" } + logger.info { "STOPPED COUNTING BUDGET FOR ${cut.classId.name}" } statsForClass -} \ No newline at end of file +} + +fun JcClasspath.findMethodOrNull(method: ExecutableId): JcMethod? = + findClass(method.classId.name).declaredMethods.firstOrNull { + it.name == method.name && it.jcdbSignature == method.jcdbSignature + } + +val JcMethod.typedMethod: JcTypedMethod get() = enclosingClass.toType().declaredMethods.first { + it.name == name && it.method.jcdbSignature == jcdbSignature +} + +val ExecutableId.jcdbSignature: String get() = when (this) { + is ConstructorId -> constructor.jcdbSignature + is MethodId -> method.jcdbSignature +} diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/EnvironmentStateKind.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/EnvironmentStateKind.kt deleted file mode 100644 index 0c275c4152..0000000000 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/EnvironmentStateKind.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.utbot.contest.usvm - -enum class EnvironmentStateKind { - INITIAL, FINAL -} \ No newline at end of file diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/InstructionIdProvider.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/InstructionIdProvider.kt deleted file mode 100644 index 252d08f566..0000000000 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/InstructionIdProvider.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.utbot.contest.usvm - -fun interface InstructionIdProvider { - fun provideInstructionId( - methodSignature: String, - index: Int, - ): Long -} \ No newline at end of file diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JacoDBContainer.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JacoDBContainer.kt deleted file mode 100644 index e4ae8bbb61..0000000000 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JacoDBContainer.kt +++ /dev/null @@ -1,44 +0,0 @@ -package org.utbot.contest.usvm - -import kotlinx.coroutines.runBlocking -import org.jacodb.api.JcClasspath -import org.jacodb.api.JcDatabase -import org.jacodb.impl.JcSettings -import org.jacodb.impl.features.classpaths.UnknownClasses -import org.jacodb.impl.jacodb -import org.usvm.util.classpathWithApproximations -import java.io.File - -// TODO usvm-sbft-refactoring: copied from `usvm/usvm-jvm/test`, extract this class back to USVM project -class JacoDBContainer( - classpath: List, - builder: JcSettings.() -> Unit, -) { - val db: JcDatabase - val cp: JcClasspath - - init { - val (db, cp) = runBlocking { - val db = jacodb(builder) - // TODO usvm-sbft: use classpathWithApproximations here - val cp = db.classpath(classpath, listOf(UnknownClasses)) - db to cp - } - this.db = db - this.cp = cp - runBlocking { - db.awaitBackgroundJobs() - } - } - - companion object { - private val keyToJacoDBContainer = HashMap() - - operator fun invoke( - key: Any?, - classpath: List, - builder: JcSettings.() -> Unit, - ): JacoDBContainer = - keyToJacoDBContainer.getOrPut(key) { JacoDBContainer(classpath, builder) } - } -} diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/Utils.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/Utils.kt deleted file mode 100644 index f58c682e26..0000000000 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/Utils.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.utbot.contest.usvm - -import org.utbot.framework.plugin.api.mapper.UtModelMapper -import org.utbot.framework.plugin.api.mapper.mapModelIfExists -import org.utbot.framework.plugin.api.mapper.mapModels - -fun UtUsvmExecution.mapModels(mapper: UtModelMapper) = copy( - stateBefore = stateBefore.mapModels(mapper), - stateAfter = stateAfter.mapModels(mapper), - result = result.mapModelIfExists(mapper), - coverage = this.coverage, - summary = this.summary, - testMethodName = this.testMethodName, - displayName = this.displayName, - instrumentation = instrumentation.map { it.mapModels(mapper) }, -) \ No newline at end of file diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/ConverterUtils.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/ConverterUtils.kt similarity index 98% rename from utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/ConverterUtils.kt rename to utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/ConverterUtils.kt index 3bce88c6c9..ffb884ad03 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/ConverterUtils.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/ConverterUtils.kt @@ -1,4 +1,4 @@ -package org.utbot.contest.usvm +package org.utbot.contest.usvm.converter import org.jacodb.analysis.library.analyzers.thisInstance import org.jacodb.api.JcArrayType diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/InstructionIdProvider.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/InstructionIdProvider.kt new file mode 100644 index 0000000000..898ba718eb --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/InstructionIdProvider.kt @@ -0,0 +1,12 @@ +package org.utbot.contest.usvm.converter + +fun interface InstructionIdProvider { + fun provideInstructionId(methodSignature: String, instIndex: Int): Long +} + +class SimpleInstructionIdProvider : InstructionIdProvider { + private val instructionIds = mutableMapOf, Long>() + + override fun provideInstructionId(methodSignature: String, instIndex: Int): Long = + instructionIds.getOrPut(methodSignature to instIndex) { instructionIds.size.toLong() } +} \ No newline at end of file diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JcToUtExecutionConverter.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/JcToUtExecutionConverter.kt similarity index 98% rename from utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JcToUtExecutionConverter.kt rename to utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/JcToUtExecutionConverter.kt index 6643853d45..7d97a082cd 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JcToUtExecutionConverter.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/JcToUtExecutionConverter.kt @@ -1,4 +1,4 @@ -package org.utbot.contest.usvm +package org.utbot.contest.usvm.converter import mu.KotlinLogging import org.jacodb.api.JcClassOrInterface @@ -18,7 +18,7 @@ import org.usvm.instrumentation.testcase.descriptor.UTestExceptionDescriptor import org.usvm.instrumentation.util.enclosingClass import org.usvm.instrumentation.util.enclosingMethod import org.utbot.common.isPublic -import org.utbot.contest.usvm.executor.JcExecution +import org.utbot.contest.usvm.jc.JcExecution import org.utbot.framework.codegen.domain.builtin.UtilMethodProvider import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.Coverage @@ -58,7 +58,7 @@ class JcToUtExecutionConverter( private var jcToUtModelConverter: JcToUtModelConverter init { - val instToModelConverter = UTestInst2UtModelConverter(idGenerator, jcClasspath, utilMethodProvider) + val instToModelConverter = UTestInstToUtModelConverter(idGenerator, jcClasspath, utilMethodProvider) instToModelConverter.processUTest(jcExecution.uTest) jcToUtModelConverter = JcToUtModelConverter(idGenerator, jcClasspath, instToModelConverter) diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JcToUtModelConverter.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/JcToUtModelConverter.kt similarity index 98% rename from utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JcToUtModelConverter.kt rename to utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/JcToUtModelConverter.kt index 9f79379732..67b955af2f 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JcToUtModelConverter.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/JcToUtModelConverter.kt @@ -1,4 +1,4 @@ -package org.utbot.contest.usvm +package org.utbot.contest.usvm.converter import org.jacodb.api.JcClasspath import org.usvm.instrumentation.testcase.api.UTestExpression @@ -29,10 +29,14 @@ import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.stringClassId import org.utbot.fuzzer.IdGenerator +enum class EnvironmentStateKind { + INITIAL, FINAL +} + class JcToUtModelConverter( private val idGenerator: IdGenerator, private val jcClasspath: JcClasspath, - private val instToUtModelConverter: UTestInst2UtModelConverter, + private val instToUtModelConverter: UTestInstToUtModelConverter, ) { private val descriptorToModelCache = mutableMapOf() private val refIdAndStateKindToDescriptorCache = diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/UTestInst2UtModelConverter.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/UTestInstToUtModelConverter.kt similarity index 99% rename from utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/UTestInst2UtModelConverter.kt rename to utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/UTestInstToUtModelConverter.kt index 0615e41f36..42634d0ff3 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/UTestInst2UtModelConverter.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/UTestInstToUtModelConverter.kt @@ -1,4 +1,4 @@ -package org.utbot.contest.usvm +package org.utbot.contest.usvm.converter import org.jacodb.api.JcClasspath import org.usvm.instrumentation.testcase.UTest @@ -49,7 +49,7 @@ import org.utbot.framework.plugin.api.util.classClassId import org.utbot.framework.plugin.api.util.objectClassId import org.utbot.fuzzer.IdGenerator -class UTestInst2UtModelConverter( +class UTestInstToUtModelConverter( private val idGenerator: IdGenerator, private val jcClasspath: JcClasspath, private val utilMethodProvider: UtilMethodProvider, diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/UTestValueDescriptorUtils.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/UTestValueDescriptorUtils.kt similarity index 97% rename from utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/UTestValueDescriptorUtils.kt rename to utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/UTestValueDescriptorUtils.kt index cd96782def..659a086f34 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/UTestValueDescriptorUtils.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/UTestValueDescriptorUtils.kt @@ -1,4 +1,4 @@ -package org.utbot.contest.usvm +package org.utbot.contest.usvm.converter import org.usvm.instrumentation.testcase.descriptor.UTestArrayDescriptor import org.usvm.instrumentation.testcase.descriptor.UTestClassDescriptor diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/UtUsvmExecution.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/UtUsvmExecution.kt similarity index 76% rename from utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/UtUsvmExecution.kt rename to utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/UtUsvmExecution.kt index 0964c0c29b..2bed13737b 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/UtUsvmExecution.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/converter/UtUsvmExecution.kt @@ -1,4 +1,4 @@ -package org.utbot.contest.usvm +package org.utbot.contest.usvm.converter import org.utbot.framework.plugin.api.Coverage import org.utbot.framework.plugin.api.DocStatement @@ -7,6 +7,9 @@ import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtExecutionResult import org.utbot.framework.plugin.api.UtExecutionWithInstrumentation import org.utbot.framework.plugin.api.UtInstrumentation +import org.utbot.framework.plugin.api.mapper.UtModelMapper +import org.utbot.framework.plugin.api.mapper.mapModelIfExists +import org.utbot.framework.plugin.api.mapper.mapModels class UtUsvmExecution( stateBefore: EnvironmentModels, @@ -64,4 +67,15 @@ class UtUsvmExecution( displayName, instrumentation, ) -} \ No newline at end of file +} + +fun UtUsvmExecution.mapModels(mapper: UtModelMapper) = copy( + stateBefore = stateBefore.mapModels(mapper), + stateAfter = stateAfter.mapModels(mapper), + result = result.mapModelIfExists(mapper), + coverage = this.coverage, + summary = this.summary, + testMethodName = this.testMethodName, + displayName = this.displayName, + instrumentation = instrumentation.map { it.mapModels(mapper) }, +) \ No newline at end of file diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/UTestRunner.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/UTestRunner.kt deleted file mode 100644 index 7923305df2..0000000000 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/UTestRunner.kt +++ /dev/null @@ -1,25 +0,0 @@ -package org.utbot.contest.usvm.executor - -import org.jacodb.api.JcClasspath -import org.usvm.instrumentation.executor.UTestConcreteExecutor -import org.usvm.instrumentation.instrumentation.JcRuntimeTraceInstrumenterFactory -import kotlin.time.Duration.Companion.seconds - -// TODO usvm-sbft-refactoring: copied from `usvm/usvm-jvm/test`, extract this class back to USVM project -object UTestRunner { - val CONTEST_TEST_EXECUTION_TIMEOUT = 1.seconds - - lateinit var runner: UTestConcreteExecutor - - fun isInitialized() = this::runner.isInitialized - - fun initRunner(pathToJars: List, classpath: JcClasspath) { - runner = - UTestConcreteExecutor( - JcRuntimeTraceInstrumenterFactory::class, - pathToJars, - classpath, - CONTEST_TEST_EXECUTION_TIMEOUT - ) - } -} \ No newline at end of file diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/jc/JcContainer.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/jc/JcContainer.kt new file mode 100644 index 0000000000..a5e02bf520 --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/jc/JcContainer.kt @@ -0,0 +1,82 @@ +package org.utbot.contest.usvm.jc + +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.jacodb.api.JcClasspath +import org.jacodb.api.JcDatabase +import org.jacodb.impl.JcSettings +import org.jacodb.impl.features.classpaths.UnknownClasses +import org.jacodb.impl.jacodb +import org.usvm.UMachineOptions +import org.usvm.instrumentation.executor.UTestConcreteExecutor +import org.usvm.instrumentation.instrumentation.JcRuntimeTraceInstrumenterFactory +import org.usvm.machine.JcMachine +import java.io.File +import kotlin.time.Duration.Companion.seconds + +private val logger = KotlinLogging.logger {} + +// TODO usvm-sbft-refactoring: copied from `usvm/usvm-jvm/test`, extract this class back to USVM project +class JcContainer private constructor( + classpath: List, + machineOptions: UMachineOptions, + builder: JcSettings.() -> Unit, +) : AutoCloseable { + val db: JcDatabase + val cp: JcClasspath + val machine: JcMachine + val runner: UTestConcreteExecutor + + init { + val (db, cp) = runBlocking { + val db = jacodb(builder) + // TODO usvm-sbft: use classpathWithApproximations here when approximation decoders are finished + val cp = db.classpath(classpath, listOf(UnknownClasses)) + db to cp + } + this.db = db + this.cp = cp + this.machine = JcMachine(cp, machineOptions) + this.runner = UTestConcreteExecutor( + JcRuntimeTraceInstrumenterFactory::class, + classpath.map { it.absolutePath }, + cp, + CONTEST_TEST_EXECUTION_TIMEOUT + ) + runBlocking { + db.awaitBackgroundJobs() + } + } + + override fun close() { + cp.close() + db.close() + machine.close() + runner.close() + } + + companion object : AutoCloseable { + val CONTEST_TEST_EXECUTION_TIMEOUT = 1.seconds + + private val cache = HashMap, UMachineOptions>, JcContainer>() + + operator fun invoke( + classpath: List, + machineOptions: UMachineOptions, + builder: JcSettings.() -> Unit, + ): JcContainer { + val cacheKey = classpath to machineOptions + return cache[cacheKey] ?: run { + // TODO usvm-sbft: right now max cache size is 1, do we need to increase it? + logger.info { "JcContainer cache miss" } + close() + JcContainer(classpath, machineOptions, builder).also { cache[cacheKey] = it } + } + } + + override fun close() { + cache.values.forEach { it.close() } + cache.clear() + } + } +} diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/JcExecution.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/jc/JcExecution.kt similarity index 89% rename from utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/JcExecution.kt rename to utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/jc/JcExecution.kt index 2e940c991f..92812f9d7d 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/JcExecution.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/jc/JcExecution.kt @@ -1,4 +1,4 @@ -package org.utbot.contest.usvm.executor +package org.utbot.contest.usvm.jc import org.jacodb.api.JcTypedMethod import org.usvm.api.JcCoverage diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/JcTestExecutor.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/jc/JcTestExecutor.kt similarity index 86% rename from utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/JcTestExecutor.kt rename to utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/jc/JcTestExecutor.kt index af7291687e..c646747066 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/JcTestExecutor.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/jc/JcTestExecutor.kt @@ -1,11 +1,9 @@ -package org.utbot.contest.usvm.executor +package org.utbot.contest.usvm.jc import io.ksmt.utils.asExpr import kotlinx.coroutines.runBlocking import org.jacodb.api.* import org.jacodb.api.ext.* -import org.jacodb.impl.fs.BuildFolderLocation -import org.jacodb.impl.fs.JarLocation import org.usvm.* import org.usvm.api.JcCoverage import org.usvm.api.JcTest @@ -37,29 +35,17 @@ import org.usvm.types.firstOrNull /** * A class, responsible for resolving a single [JcExecution] for a specific method from a symbolic state. * - * Uses reflection to resolve objects. + * Uses concrete execution */ // TODO usvm-sbft-refactoring: copied from `usvm/usvm-jvm/test`, extract this class back to USVM project class JcTestExecutor( val classpath: JcClasspath, -) { - private val runner: UTestConcreteExecutor - get() { - if (!UTestRunner.isInitialized()) { - val pathToJars = - classpath.locations - .filter { it is BuildFolderLocation || (it is JarLocation && it.type == LocationType.APP) } - .map { it.path } - UTestRunner.initRunner(pathToJars, classpath) - } - return UTestRunner.runner - } - +) { /** * Resolves a [JcTest] from a [method] from a [state]. */ - fun resolve(method: JcTypedMethod, state: JcState): JcExecution { + fun execute(method: JcTypedMethod, state: JcState): JcExecution { val model = state.models.first() val ctx = state.ctx @@ -138,15 +124,41 @@ class JcTestExecutor( ): Pair> { val exprInModel = evaluateInModel(expr) return when (type) { - ctx.cp.boolean -> UTestBooleanExpression(extractBool(exprInModel) ?: false, ctx.cp.boolean) - ctx.cp.short -> UTestShortExpression(extractShort(exprInModel) ?: 0, ctx.cp.short) - ctx.cp.int -> UTestIntExpression(extractInt(exprInModel) ?: 0, ctx.cp.int) - ctx.cp.long -> UTestLongExpression(extractLong(exprInModel) ?: 0L, ctx.cp.long) - ctx.cp.float -> UTestFloatExpression(extractFloat(exprInModel) ?: 0.0f, ctx.cp.float) - ctx.cp.double -> UTestDoubleExpression(extractDouble(exprInModel) ?: 0.0, ctx.cp.double) - ctx.cp.byte -> UTestByteExpression(extractByte(exprInModel) ?: 0, ctx.cp.byte) - ctx.cp.char -> UTestCharExpression(extractChar(exprInModel) ?: '\u0000', ctx.cp.char) - ctx.cp.void -> UTestNullExpression(ctx.cp.void) + ctx.cp.boolean -> UTestBooleanExpression( + value = extractBool(exprInModel) ?: false, + type = ctx.cp.boolean + ) + ctx.cp.short -> UTestShortExpression( + value = extractShort(exprInModel) ?: 0, + type = ctx.cp.short + ) + ctx.cp.int -> UTestIntExpression( + value = extractInt(exprInModel) ?: 0, + type = ctx.cp.int + ) + ctx.cp.long -> UTestLongExpression( + value = extractLong(exprInModel) ?: 0L, + type = ctx.cp.long + ) + ctx.cp.float -> UTestFloatExpression( + value = extractFloat(exprInModel) ?: 0.0f, + type = ctx.cp.float + ) + ctx.cp.double -> UTestDoubleExpression( + value = extractDouble(exprInModel) ?: 0.0, + type = ctx.cp.double + ) + ctx.cp.byte -> UTestByteExpression( + value = extractByte(exprInModel) ?: 0, + type = ctx.cp.byte + ) + ctx.cp.char -> UTestCharExpression( + value = extractChar(exprInModel) ?: '\u0000', + type = ctx.cp.char + ) + ctx.cp.void -> UTestNullExpression( + type = ctx.cp.void + ) else -> error("Unexpected type: ${type.typeName}") }.let { it to listOf() } } @@ -206,7 +218,6 @@ class JcTestExecutor( return resolveLValue(elemRef, type.elementType) } - //val arrLength = UTestIntExpression(length, ctx.cp.int) val arrayInstance = UTestCreateArrayExpression(type.elementType, length) val arraySetters = buildList { @@ -307,7 +318,10 @@ class JcTestExecutor( // TODO simple org.jacodb.api.ext.JcClasses.isEnum does not work with enums with abstract methods private fun JcRefType.getEnumAncestorOrNull(): JcClassOrInterface? = - (sequenceOf(jcClass) + jcClass.allSuperHierarchySequence).firstOrNull { it.isEnum } + jcClass.getAllSuperHierarchyIncludingThis().firstOrNull { it.isEnum } + + private fun JcClassOrInterface.getAllSuperHierarchyIncludingThis() = + (sequenceOf(this) + allSuperHierarchySequence) } } \ No newline at end of file