diff --git a/gradle.properties b/gradle.properties index cd83a4920b..b482a13060 100644 --- a/gradle.properties +++ b/gradle.properties @@ -109,7 +109,7 @@ springBootVersion=2.7.13 springSecurityVersion=5.8.5 approximationsVersion=bfce4eedde -usvmVersion=72924ad +usvmVersion=7b45e18b0e # configuration for build server # diff --git a/utbot-cli-go/build.gradle b/utbot-cli-go/build.gradle index 1f0cf22758..dc388f65fb 100644 --- a/utbot-cli-go/build.gradle +++ b/utbot-cli-go/build.gradle @@ -57,6 +57,7 @@ classes { } jar { + zip64 = true manifest { attributes 'Main-Class': 'org.utbot.cli.go.ApplicationKt' attributes 'Bundle-SymbolicName': 'org.utbot.cli.go' diff --git a/utbot-cli-js/build.gradle b/utbot-cli-js/build.gradle index 0248806799..9ffb5fbf1a 100644 --- a/utbot-cli-js/build.gradle +++ b/utbot-cli-js/build.gradle @@ -55,6 +55,7 @@ classes { } jar { + zip64 = true manifest { attributes 'Main-Class': 'org.utbot.cli.js.ApplicationKt' attributes 'Bundle-SymbolicName': 'org.utbot.cli.js' diff --git a/utbot-cli-python/build.gradle b/utbot-cli-python/build.gradle index 350684a5ba..4ea611b57f 100644 --- a/utbot-cli-python/build.gradle +++ b/utbot-cli-python/build.gradle @@ -49,6 +49,7 @@ classes { } jar { + zip64 = true manifest { attributes 'Main-Class': 'org.utbot.cli.language.python.ApplicationKt' attributes 'Bundle-SymbolicName': 'org.utbot.cli.language.python' diff --git a/utbot-cli/build.gradle b/utbot-cli/build.gradle index 139bc81c4c..8615111a26 100644 --- a/utbot-cli/build.gradle +++ b/utbot-cli/build.gradle @@ -55,6 +55,7 @@ classes { } jar { + zip64 = true manifest { attributes 'Main-Class': 'org.utbot.cli.ApplicationKt' attributes 'Bundle-SymbolicName': 'org.utbot.cli' diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt index ec289f2b23..cf97b36e8a 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/Api.kt @@ -57,6 +57,8 @@ import kotlin.contracts.contract import org.utbot.common.isAbstract import org.utbot.framework.plugin.api.mapper.UtModelMapper import org.utbot.framework.plugin.api.mapper.map +import org.utbot.framework.plugin.api.mapper.mapModelIfExists +import org.utbot.framework.plugin.api.mapper.mapModels import org.utbot.framework.plugin.api.mapper.mapPreservingType import org.utbot.framework.plugin.api.util.SpringModelUtils import org.utbot.framework.process.OpenModulesContainer @@ -73,6 +75,11 @@ data class UtMethodTestSet( val clustersInfo: List> = listOf(null to executions.indices) ) +fun UtMethodTestSet.mapModels(mapper: UtModelMapper): UtMethodTestSet = + copy(executions = executions.map { it.mapModels(mapper) }) + +fun Collection.mapModels(mapper: UtModelMapper) = map { it.mapModels(mapper) } + data class Step( val stmt: Stmt, val depth: Int, @@ -145,11 +152,70 @@ abstract class UtExecution( displayName: String? = this.displayName, ): UtExecution + open fun mapModels(mapper: UtModelMapper) = copy( + stateBefore = stateBefore.mapModels(mapper), + stateAfter = stateAfter.mapModels(mapper), + result = result.mapModelIfExists(mapper) + ) + val executableToCall get() = stateBefore.executableToCall } -interface UtExecutionWithInstrumentation { - val instrumentation: List +abstract class UtExecutionWithInstrumentation( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage? = null, + summary: List? = null, + testMethodName: String? = null, + displayName: String? = null, + val instrumentation: List, +) : UtExecution( + stateBefore = stateBefore, + stateAfter = stateAfter, + result = result, + coverage = coverage, + summary = summary, + testMethodName = testMethodName, + displayName = displayName, +) { + abstract fun copy( + stateBefore: EnvironmentModels = this.stateBefore, + stateAfter: EnvironmentModels = this.stateAfter, + result: UtExecutionResult = this.result, + coverage: Coverage? = this.coverage, + summary: List? = this.summary, + testMethodName: String? = this.testMethodName, + displayName: String? = this.displayName, + instrumentation: List = this.instrumentation, + ): UtExecution + + override fun copy( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage?, + summary: List?, + testMethodName: String?, + displayName: String?, + ): UtExecution = copy( + stateBefore = stateBefore, + stateAfter = stateAfter, + result = result, + instrumentation = instrumentation, + coverage = coverage, + summary = summary, + testMethodName = testMethodName, + displayName = displayName, + ) + + override fun mapModels(mapper: UtModelMapper): UtExecution = + copy( + stateBefore = stateBefore.mapModels(mapper), + stateAfter = stateAfter.mapModels(mapper), + result = result.mapModelIfExists(mapper), + instrumentation = instrumentation.map { it.mapModels(mapper) }, + ) } /** @@ -167,7 +233,7 @@ class UtSymbolicExecution( stateBefore: EnvironmentModels, stateAfter: EnvironmentModels, result: UtExecutionResult, - override val instrumentation: List, + instrumentation: List, val path: MutableList, val fullPath: List, coverage: Coverage? = null, @@ -175,7 +241,16 @@ class UtSymbolicExecution( testMethodName: String? = null, displayName: String? = null, /** Convenient view of the full symbolic path */ val symbolicSteps: List = listOf(), -) : UtExecution(stateBefore, stateAfter, result, coverage, summary, testMethodName, displayName), UtExecutionWithInstrumentation { +) : UtExecutionWithInstrumentation( + stateBefore, + stateAfter, + result, + coverage, + summary, + testMethodName, + displayName, + instrumentation +) { /** * By design the 'before' and 'after' states contain info about the same fields. * It means that it is not possible for a field to be present at 'before' and to be absent at 'after'. @@ -193,7 +268,8 @@ class UtSymbolicExecution( coverage: Coverage?, summary: List?, testMethodName: String?, - displayName: String? + displayName: String?, + instrumentation: List, ): UtExecution = UtSymbolicExecution( stateBefore = stateBefore, stateAfter = stateAfter, @@ -205,7 +281,9 @@ class UtSymbolicExecution( summary = summary, testMethodName = testMethodName, displayName = displayName - ) + ).also { + it.containsMocking = containsMocking + } override fun toString(): String = buildString { append("UtSymbolicExecution(") @@ -252,7 +330,9 @@ class UtSymbolicExecution( summary = summary, testMethodName = testMethodName, displayName = displayName - ) + ).also { + it.containsMocking = containsMocking + } } /** @@ -1369,19 +1449,49 @@ class BuiltinMethodId( name: String, returnType: ClassId, parameters: List, - bypassesSandbox: Boolean = false, - // by default we assume that the builtin method is non-static and public - isStatic: Boolean = false, - isPublic: Boolean = true, - isProtected: Boolean = false, - isPrivate: Boolean = false + bypassesSandbox: Boolean, + override val modifiers: Int, ) : MethodId(classId, name, returnType, parameters, bypassesSandbox) { - override val modifiers: Int = ModifierFactory { - static = isStatic - public = isPublic - private = isPrivate - protected = isProtected - } + constructor( + classId: ClassId, + name: String, + returnType: ClassId, + parameters: List, + bypassesSandbox: Boolean = false, + // by default, we assume that the builtin method is non-static and public + isStatic: Boolean = false, + isPublic: Boolean = true, + isProtected: Boolean = false, + isPrivate: Boolean = false + ) : this( + classId = classId, + name = name, + returnType = returnType, + parameters = parameters, + bypassesSandbox = bypassesSandbox, + modifiers = ModifierFactory { + static = isStatic + public = isPublic + private = isPrivate + protected = isProtected + } + ) + + fun copy( + classId: ClassId = this.classId, + name: String = this.name, + returnType: ClassId = this.returnType, + parameters: List = this.parameters, + bypassesSandbox: Boolean = this.bypassesSandbox, + modifiers: Int = this.modifiers, + ) = BuiltinMethodId( + classId = classId, + name = name, + returnType = returnType, + parameters = parameters, + bypassesSandbox = bypassesSandbox, + modifiers = modifiers, + ) } class BuiltinConstructorId( diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt index 9c8ed80a8a..2ea3667575 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/mapper/Utils.kt @@ -1,6 +1,7 @@ package org.utbot.framework.plugin.api.mapper import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.MissingState import org.utbot.framework.plugin.api.UtDirectGetFieldModel import org.utbot.framework.plugin.api.UtDirectSetFieldModel import org.utbot.framework.plugin.api.UtExecutableCallModel @@ -50,12 +51,15 @@ fun UtStatementCallModel.mapModels(mapper: UtModelMapper): UtStatementCallModel ) } -fun EnvironmentModels.mapModels(mapper: UtModelMapper) = EnvironmentModels( - thisInstance = thisInstance?.map(mapper), - statics = statics.mapModelValues(mapper), - parameters = parameters.mapModels(mapper), - executableToCall = executableToCall, -) +fun EnvironmentModels.mapModels(mapper: UtModelMapper) = when (this) { + MissingState -> MissingState + else -> EnvironmentModels( + thisInstance = thisInstance?.map(mapper), + statics = statics.mapModelValues(mapper), + parameters = parameters.mapModels(mapper), + executableToCall = executableToCall, + ) +} fun UtExecutionResult.mapModelIfExists(mapper: UtModelMapper) = if (this.isSuccess) { val successResult = this as UtExecutionSuccess @@ -72,4 +76,4 @@ fun UtInstrumentation.mapModels(mapper: UtModelMapper) = when (this) { fun UtExecution.mapStateBeforeModels(mapper: UtModelMapper) = copy( stateBefore = stateBefore.mapModels(mapper) -) +) \ No newline at end of file diff --git a/utbot-framework/build.gradle b/utbot-framework/build.gradle index c471f88cef..a80f884b2a 100644 --- a/utbot-framework/build.gradle +++ b/utbot-framework/build.gradle @@ -15,6 +15,8 @@ dependencies { api project(':utbot-rd') api project(':utbot-modificators-analyzer') + api project(':utbot-usvm') + implementation group: 'com.jetbrains.rd', name: 'rd-framework', version: rdVersion implementation group: 'com.jetbrains.rd', name: 'rd-core', version: rdVersion diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt new file mode 100644 index 0000000000..49b5b123da --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -0,0 +1,238 @@ +package org.utbot.engine + +import kotlinx.coroutines.runBlocking +import mu.KotlinLogging +import org.jacodb.approximation.Approximations +import org.jacodb.impl.features.InMemoryHierarchy +import org.usvm.PathSelectionStrategy +import org.usvm.PathSelectorFairnessStrategy +import org.usvm.SolverType +import org.usvm.UMachineOptions +import org.usvm.api.JcCoverage +import org.usvm.instrumentation.testcase.api.UTestExecutionTimedOutResult +import org.usvm.machine.JcMachine +import org.usvm.machine.state.JcState +import org.usvm.types.ClassScorer +import org.usvm.types.TypeScorer +import org.usvm.types.scoreClassNode +import org.usvm.util.ApproximationPaths +import org.utbot.common.utBotTempDirectory +import org.utbot.framework.UtSettings +import org.utbot.framework.assemble.AssembleModelGenerator +import org.utbot.framework.codegen.domain.builtin.UtilMethodProviderPlaceholder +import org.utbot.framework.context.ConcreteExecutionContext +import org.utbot.framework.fuzzer.ReferencePreservingIntIdGenerator +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException +import org.utbot.framework.plugin.api.UtConcreteExecutionFailure +import org.utbot.framework.plugin.api.UtError +import org.utbot.framework.plugin.api.UtFailedExecution +import org.utbot.framework.plugin.api.UtResult +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.services.JdkInfoService +import org.utbot.instrumentation.ConcreteExecutor +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrumentation +import org.utbot.usvm.converter.JcToUtExecutionConverter +import org.utbot.usvm.converter.SimpleInstructionIdProvider +import org.utbot.usvm.converter.UtExecutionInitialState +import org.utbot.usvm.converter.UtUsvmExecution +import org.utbot.usvm.converter.toExecutableId +import org.utbot.usvm.jc.JcContainer +import org.utbot.usvm.jc.JcExecution +import org.utbot.usvm.jc.JcJars +import org.utbot.usvm.jc.JcTestExecutor +import org.utbot.usvm.jc.UTestConcreteExecutionResult +import org.utbot.usvm.jc.findMethodOrNull +import org.utbot.usvm.jc.typedMethod +import org.utbot.usvm.machine.analyzeAsync +import java.io.File +import java.util.concurrent.CancellationException +import kotlin.time.Duration.Companion.milliseconds + +object UsvmSymbolicEngine { + + private val logger = KotlinLogging.logger {} + + fun runUsvmGeneration( + methods: List, + classpath: String, + concreteExecutionContext: ConcreteExecutionContext, + timeoutMillis: Long + ): List> { + + if (timeoutMillis == 0L) { + return emptyList() + } + + val collectedExecutions = mutableListOf>() + val classpathFiles = classpath.split(File.pathSeparator).map { File(it) } + + createJcContainer(classpathFiles).use { jcContainer -> + val jcMethods = methods + .mapNotNull { methodId -> + jcContainer.cp.findMethodOrNull(methodId).also { + if (it == null) { + logger.error { "Method [$methodId] not found in jcClasspath [${jcContainer.cp}]" } + } + } + } + + JcMachine( + cp = jcContainer.cp, + options = UMachineOptions( + timeout = timeoutMillis.milliseconds, + pathSelectionStrategies = listOf(PathSelectionStrategy.CLOSEST_TO_UNCOVERED_RANDOM), + pathSelectorFairnessStrategy = PathSelectorFairnessStrategy.COMPLETELY_FAIR, + solverType = SolverType.Z3, + ) + ).use { jcMachine -> + jcMachine.analyzeAsync( + forceTerminationTimeout = (timeoutMillis * 1.1 + 2000).toLong(), + methods = jcMethods, + targets = emptyList() + ) { state -> + val jcExecution = constructJcExecution(jcMachine, state, jcContainer) + + val executableId = jcExecution.method.method.toExecutableId(jcContainer.cp) + + val executionConverter = JcToUtExecutionConverter( + jcExecution = jcExecution, + jcClasspath = jcContainer.cp, + idGenerator = ReferencePreservingIntIdGenerator(), + instructionIdProvider = SimpleInstructionIdProvider(), + utilMethodProvider = UtilMethodProviderPlaceholder, + ) + + var utResult = runCatching { + executionConverter.convert() + }.getOrElse { e -> + logger.warn(e) { "JcToUtExecutionConverter.convert(${jcExecution.method.method}) failed" } + val initialState = executionConverter.convertInitialStateOnly() + val concreteExecutor = + ConcreteExecutor(concreteExecutionContext.instrumentationFactory, classpath) + .apply { this.classLoader = utContext.classLoader } + + runStandardConcreteExecution(concreteExecutor, executableId, initialState) + } + + if(utResult is UtExecution) { + val assembleModelGenerator = AssembleModelGenerator(executableId.classId.packageName) + utResult = utResult.mapModels(UtModelDeepMapper { model -> + assembleModelGenerator.createAssembleModels(listOf(model)).getValue(model) + }) + } + + utResult?.let { + collectedExecutions.add(executableId to it) + } + } + } + } + + return collectedExecutions + } + + private fun constructJcExecution( + jcMachine: JcMachine, + state: JcState, + jcContainer: JcContainer, + ): JcExecution { + val executor = JcTestExecutor(jcContainer.cp, jcContainer.runner) + + var realJcExecution = runCatching { + executor.execute( + method = state.entrypoint.typedMethod, + state = state, + stringConstants = jcMachine.stringConstants, + classConstants = jcMachine.classConstants, + allowSymbolicResult = false + ) + }.getOrElse { e -> + logger.warn(e) { "executor.execute(${state.entrypoint}) failed" } + null + } + + if (realJcExecution != null) { + // We should not rely on `UTestExecutionTimedOutResult` from usvm instrumentation as + // it sometimes hides another problems like `IllegalStateMonitorException` and so on. + realJcExecution = realJcExecution.copy( + uTestExecutionResultWrappers = realJcExecution + .uTestExecutionResultWrappers + .filterNot { it is UTestConcreteExecutionResult && it.uTestExecutionResult is UTestExecutionTimedOutResult }) + + return realJcExecution + } + + return JcExecution( + method = state.entrypoint.typedMethod, + uTest = executor.createUTest( + method = state.entrypoint.typedMethod, + state = state, + stringConstants = jcMachine.stringConstants, + classConstants = jcMachine.classConstants, + ), + uTestExecutionResultWrappers = emptySequence(), + coverage = JcCoverage(emptyMap()), + ) + } + + private fun runStandardConcreteExecution( + concreteExecutor: ConcreteExecutor, + executableId: ExecutableId, + initialState: UtExecutionInitialState, + ): UtResult? { + return try { + val concreteExecutionResult = runBlocking { + concreteExecutor.executeConcretely( + executableId, + initialState.stateBefore, + initialState.instrumentations, + UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis, + ) + } + + concreteExecutionResult.processedFailure()?.let { failure -> + logger.warn { "Instrumented process failed with exception ${failure.exception} " + + "before concrete execution started" } + null + } ?: UtUsvmExecution( + initialState.stateBefore, + concreteExecutionResult.stateAfter, + concreteExecutionResult.result, + concreteExecutionResult.coverage, + instrumentation = concreteExecutionResult.newInstrumentation ?: initialState.instrumentations, + ) + } catch (e: CancellationException) { + logger.debug(e) { "Cancellation happened" } + null + } catch (e: InstrumentedProcessDeathException) { + UtFailedExecution( + stateBefore = initialState.stateBefore, + result = UtConcreteExecutionFailure(e) + ) + } catch (e: Throwable) { + UtError("Concrete execution failed", e) + } + } + + private fun createJcContainer(classpathFiles: List) = JcContainer( + usePersistence = false, + persistenceDir = utBotTempDirectory.toFile().resolve("jacoDbPersisitenceDirectory"), + classpath = classpathFiles, + javaHome = JdkInfoService.provide().path.toFile(), + ) { + val approximationPaths = ApproximationPaths( + usvmApiJarPath = JcJars.approximationsApiJar.absolutePath, + usvmApproximationsJarPath = JcJars.approximationsJar.absolutePath, + ) + installFeatures( + InMemoryHierarchy, + Approximations, + ClassScorer(TypeScorer, ::scoreClassNode, approximationPaths) + ) + loadByteCode(classpathFiles) + } +} \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt index 49ec333e76..edb89b6d41 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -864,7 +864,7 @@ private fun UtConcreteExecutionResult.violatesUtMockAssumption(): Boolean { return result.exceptionOrNull()?.javaClass?.name == UtMockAssumptionViolatedException::class.java.name } -private fun UtConcreteExecutionResult.processedFailure(): UtConcreteExecutionProcessedFailure? +fun UtConcreteExecutionResult.processedFailure(): UtConcreteExecutionProcessedFailure? = result as? UtConcreteExecutionProcessedFailure private fun checkStaticMethodsMock(execution: UtSymbolicExecution) = diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt index dfe8e2be57..5fbb8dc295 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/Domain.kt @@ -691,6 +691,30 @@ enum class ParametrizedTestSource( } } +enum class SymbolicEngineSource( + override val id: String, + override val displayName: String, + override val description: String = "Use $displayName symbolic engine" +) : CodeGenerationSettingItem { + UnitTestBot( + id = "UnitTestBot", + displayName = "UnitTestBot", + description = "Use UnitTestBot symbolic engine", + ), + Usvm( + id = "USVM", + displayName = "USVM", + description = "Use USVM symbolic engine", + ); + + override fun toString(): String = id + + companion object : CodeGenerationSettingBox { + override val defaultItem: SymbolicEngineSource = SymbolicEngineSource.UnitTestBot + override val allItems: List = SymbolicEngineSource.values().toList() + } +} + enum class ProjectType { /** * Standard JVM project without DI frameworks diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/UtilMethodProviderPlaceholder.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/UtilMethodProviderPlaceholder.kt new file mode 100644 index 0000000000..2810ea5274 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/UtilMethodProviderPlaceholder.kt @@ -0,0 +1,48 @@ +package org.utbot.framework.codegen.domain.builtin + +import org.utbot.framework.plugin.api.BuiltinMethodId +import org.utbot.framework.plugin.api.ClassId +import org.utbot.framework.plugin.api.UtAssembleModel +import org.utbot.framework.plugin.api.UtExecutableCallModel +import org.utbot.framework.plugin.api.UtModel +import org.utbot.framework.plugin.api.UtStatementCallModel +import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.utils.UtilMethodProvider + +/** + * Can be used in `UtModel`s to denote class containing util methods, + * before actual [ClassId] containing these methods has been determined. + * + * At the very start of the code generation, [utilClassIdPlaceholder] is + * replaced with actual [ClassId] containing util methods. + */ +val utilClassIdPlaceholder = utJavaUtilsClassId +object UtilMethodProviderPlaceholder : UtilMethodProvider(utilClassIdPlaceholder) + +fun UtModel.shallowlyFixUtilClassIds(actualUtilClassId: ClassId) = when (this) { + is UtAssembleModel -> copy( + modificationsChain = modificationsChain.map { statement -> + statement.shallowlyFixUtilClassId(actualUtilClassId) + } + ) + else -> this +} + +private fun UtStatementModel.shallowlyFixUtilClassId(actualUtilClassId: ClassId) = + when (this) { + is UtExecutableCallModel -> shallowlyFixUtilClassId(actualUtilClassId) + else -> this + } + +private fun UtStatementCallModel.shallowlyFixUtilClassId(actualUtilClassId: ClassId) = + when (this) { + is UtExecutableCallModel -> { + val executable = executable + if (executable.classId == utilClassIdPlaceholder && executable is BuiltinMethodId) { + copy(executable = executable.copy(classId = actualUtilClassId)) + } else { + this + } + } + else -> this + } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/AbstractCodeGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/AbstractCodeGenerator.kt index a7a05831f5..44f61a7dea 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/AbstractCodeGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/generator/AbstractCodeGenerator.kt @@ -1,11 +1,14 @@ package org.utbot.framework.codegen.generator import mu.KotlinLogging +import org.utbot.framework.codegen.domain.builtin.shallowlyFixUtilClassIds import org.utbot.framework.codegen.domain.context.CgContext import org.utbot.framework.codegen.domain.models.CgClassFile import org.utbot.framework.codegen.domain.models.CgMethodTestSet import org.utbot.framework.codegen.renderer.CgAbstractRenderer import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.api.mapModels +import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper import java.time.LocalDateTime import java.time.format.DateTimeFormatter @@ -42,6 +45,11 @@ abstract class AbstractCodeGenerator(params: CodeGeneratorParams) { testSets: Collection, testClassCustomName: String? = null, ): CodeGeneratorResult { + @Suppress("NAME_SHADOWING") + val testSets = testSets.mapModels(UtModelDeepMapper { model -> + model.shallowlyFixUtilClassIds(actualUtilClassId = context.utilsClassId) + }) + val cgTestSets = testSets.map { CgMethodTestSet(it) }.toList() return withCustomContext(testClassCustomName) { context.withTestClassFileScope { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt index 634a66f6e8..b69a089e3e 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map import kotlinx.coroutines.isActive import kotlinx.coroutines.launch import kotlinx.coroutines.yield @@ -16,6 +17,7 @@ import mu.KotlinLogging import org.utbot.common.* import org.utbot.engine.EngineController import org.utbot.engine.Mocker +import org.utbot.engine.UsvmSymbolicEngine import org.utbot.engine.UtBotSymbolicEngine import org.utbot.engine.util.mockListeners.ForceMockListener import org.utbot.engine.util.mockListeners.ForceStaticMockListener @@ -173,9 +175,10 @@ open class TestCaseGenerator( methods: List, mockStrategy: MockStrategyApi, chosenClassesToMockAlways: Set = Mocker.javaDefaultClasses.mapTo(mutableSetOf()) { it.id }, - methodsGenerationTimeout: Long = utBotGenerationTimeoutInMillis, + utBotTimeout: Long = utBotGenerationTimeoutInMillis, userTaintConfigurationProvider: TaintConfigurationProvider? = null, - generate: (engine: UtBotSymbolicEngine) -> Flow = defaultTestFlow(methodsGenerationTimeout) + generate: (engine: UtBotSymbolicEngine) -> Flow = defaultTestFlow(utBotTimeout), + usvmTimeoutMillis: Long = 0, ): List = ConcreteExecutor.defaultPool.use { _ -> // TODO: think on appropriate way to close instrumented processes if (isCanceled()) return@use methods.map { UtMethodTestSet(it) } @@ -189,7 +192,7 @@ open class TestCaseGenerator( return@use methods.map { method -> UtMethodTestSet(method, errors = method2errors.getValue(method)) } val executionStartInMillis = System.currentTimeMillis() - val executionTimeEstimator = ExecutionTimeEstimator(methodsGenerationTimeout, methods.size) + val executionTimeEstimator = ExecutionTimeEstimator(utBotTimeout, methods.size) val currentUtContext = utContext @@ -200,6 +203,24 @@ open class TestCaseGenerator( val forceMockListener = ForceMockListener.create(this, conflictTriggers) val forceStaticMockListener = ForceStaticMockListener.create(this, conflictTriggers) + fun consumeUtResult(executableId: ExecutableId, utResult: UtResult) = + when (utResult) { + is UtExecution -> { + if (utResult is UtSymbolicExecution && + (conflictTriggers.triggered(Conflict.ForceMockHappened) || + conflictTriggers.triggered(Conflict.ForceStaticMockHappened)) + ) { + utResult.containsMocking = true + } + method2executions.getValue(executableId) += utResult + } + + is UtError -> { + method2errors.getValue(executableId).merge(utResult.description, 1, Int::plus) + logger.error(utResult.error) { "UtError occurred" } + } + } + runIgnoringCancellationException { runBlockingWithCancellationPredicate(isCanceled) { for ((method, controller) in method2controller) { @@ -227,25 +248,11 @@ open class TestCaseGenerator( .catch { logger.error(it) { "Error in flow" } } - .collect { - when (it) { - is UtExecution -> { - if (it is UtSymbolicExecution && - (conflictTriggers.triggered(Conflict.ForceMockHappened) || - conflictTriggers.triggered(Conflict.ForceStaticMockHappened)) - ) { - it.containsMocking = true - } - method2executions.getValue(method) += it - } - is UtError -> { - method2errors.getValue(method).merge(it.description, 1, Int::plus) - logger.error(it.error) { "UtError occurred" } - } - } + .collect { utResult -> + consumeUtResult(method, utResult) } } catch (e: Exception) { - logger.error(e) {"Error in engine"} + logger.error(e) { "Error in engine" } throw e } } @@ -284,6 +291,10 @@ open class TestCaseGenerator( } logger.debug("test generator global scope lifecycle check ended") } + + UsvmSymbolicEngine + .runUsvmGeneration(methods, classpathForEngine, concreteExecutionContext, usvmTimeoutMillis) + .forEach { (executableId, usvmExecution) -> consumeUtResult(executableId, usvmExecution) } } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt index b62ecace4d..aab381af57 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestFlow.kt @@ -6,7 +6,6 @@ import kotlinx.coroutines.flow.flattenConcat import kotlinx.coroutines.flow.flowOf import org.utbot.engine.UtBotSymbolicEngine import org.utbot.framework.UtSettings -import org.utbot.framework.process.generated.GenerateParams /** * Constructs [TestFlow] for customization and creates flow producer. @@ -17,7 +16,7 @@ fun testFlow(block: TestFlow.() -> Unit): UtBotSymbolicEngine.() -> Flow 0) { @@ -40,7 +39,7 @@ class TestFlow internal constructor(block: TestFlow.() -> Unit) { set(value) { field = maxOf(0, value) } - var isSymbolicEngineEnabled = true + var isUtBotSymbolicEngineEnabled = true var isFuzzingEnabled = false var fuzzingValue: Double = 0.1 set(value) { @@ -61,7 +60,7 @@ class TestFlow internal constructor(block: TestFlow.() -> Unit) { return when { generationTimeout == 0L -> emptyFlow() isFuzzingEnabled -> { - when (val value = if (isSymbolicEngineEnabled) (fuzzingValue * generationTimeout).toLong() else generationTimeout) { + when (val value = if (isUtBotSymbolicEngineEnabled) (fuzzingValue * generationTimeout).toLong() else generationTimeout) { 0L -> engine.traverse() generationTimeout -> engine.fuzzing(System.currentTimeMillis() + value) else -> flowOf( @@ -70,7 +69,7 @@ class TestFlow internal constructor(block: TestFlow.() -> Unit) { ).flattenConcat() } } - isSymbolicEngineEnabled -> engine.traverse() + isUtBotSymbolicEngineEnabled -> engine.traverse() else -> emptyFlow() } } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt index 2be3d2f84d..576c10f304 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/EngineProcessMain.kt @@ -12,6 +12,8 @@ import org.utbot.framework.codegen.domain.NoStaticMocking import org.utbot.framework.codegen.domain.ParametrizedTestSource import org.utbot.framework.codegen.domain.ProjectType import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.SymbolicEngineSource +import org.utbot.framework.codegen.domain.SymbolicEngineSource.* import org.utbot.framework.codegen.domain.testFrameworkByName import org.utbot.framework.codegen.generator.AbstractCodeGenerator import org.utbot.framework.codegen.generator.CodeGeneratorParams @@ -113,9 +115,17 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch val methods: List = kryoHelper.readObject(params.methods) logger.debug() .measureTime({ "starting generation for ${methods.size} methods, starting with ${methods.first()}" }) { + val symbolicEngineSource = SymbolicEngineSource.valueOf(params.symbolicEngineType) + val utBotTimeout = when { + !params.isSymbolicEngineEnabled -> params.timeout + symbolicEngineSource == UnitTestBot -> params.timeout + params.isFuzzingEnabled -> (params.fuzzingValue * params.timeout).toLong() + else -> 0L + } + val generateFlow = testFlow { - generationTimeout = params.generationTimeout - isSymbolicEngineEnabled = params.isSymbolicEngineEnabled + generationTimeout = utBotTimeout + isUtBotSymbolicEngineEnabled = params.isSymbolicEngineEnabled && symbolicEngineSource == UnitTestBot isFuzzingEnabled = params.isFuzzingEnabled fuzzingValue = params.fuzzingValue } @@ -128,9 +138,10 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch methods, MockStrategyApi.valueOf(params.mockStrategy), kryoHelper.readObject(params.chosenClassesToMockAlways), - params.timeout, + utBotTimeout, userTaintConfigurationProvider, generate = generateFlow, + usvmTimeoutMillis = params.generationTimeout - utBotTimeout, ) .summarizeAll(Paths.get(params.searchDirectory), null) .filterNot { it.executions.isEmpty() && it.errors.isEmpty() } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt index c399ead4df..86ae0c885b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/process/generated/EngineProcessModel.Generated.kt @@ -75,7 +75,7 @@ class EngineProcessModel private constructor( } - const val serializationHash = 7072495177628793247L + const val serializationHash = -6931237043403275202L } override val serializersOwner: ISerializersOwner get() = EngineProcessModel @@ -188,7 +188,7 @@ val IProtocol.engineProcessModel get() = getOrCreateExtension(EngineProcessModel /** - * #### Generated from [EngineProcessModel.kt:106] + * #### Generated from [EngineProcessModel.kt:107] */ data class FindMethodParamNamesArguments ( val classId: ByteArray, @@ -251,7 +251,7 @@ data class FindMethodParamNamesArguments ( /** - * #### Generated from [EngineProcessModel.kt:110] + * #### Generated from [EngineProcessModel.kt:111] */ data class FindMethodParamNamesResult ( val paramNames: ByteArray @@ -308,7 +308,7 @@ data class FindMethodParamNamesResult ( /** - * #### Generated from [EngineProcessModel.kt:99] + * #### Generated from [EngineProcessModel.kt:100] */ data class FindMethodsInClassMatchingSelectedArguments ( val classId: ByteArray, @@ -371,7 +371,7 @@ data class FindMethodsInClassMatchingSelectedArguments ( /** - * #### Generated from [EngineProcessModel.kt:103] + * #### Generated from [EngineProcessModel.kt:104] */ data class FindMethodsInClassMatchingSelectedResult ( val executableIds: ByteArray @@ -437,6 +437,7 @@ data class GenerateParams ( val timeout: Long, val generationTimeout: Long, val isSymbolicEngineEnabled: Boolean, + val symbolicEngineType: String, val isFuzzingEnabled: Boolean, val fuzzingValue: Double, val searchDirectory: String, @@ -455,11 +456,12 @@ data class GenerateParams ( val timeout = buffer.readLong() val generationTimeout = buffer.readLong() val isSymbolicEngineEnabled = buffer.readBool() + val symbolicEngineType = buffer.readString() val isFuzzingEnabled = buffer.readBool() val fuzzingValue = buffer.readDouble() val searchDirectory = buffer.readString() val taintConfigPath = buffer.readNullable { buffer.readString() } - return GenerateParams(methods, mockStrategy, chosenClassesToMockAlways, timeout, generationTimeout, isSymbolicEngineEnabled, isFuzzingEnabled, fuzzingValue, searchDirectory, taintConfigPath) + return GenerateParams(methods, mockStrategy, chosenClassesToMockAlways, timeout, generationTimeout, isSymbolicEngineEnabled, symbolicEngineType, isFuzzingEnabled, fuzzingValue, searchDirectory, taintConfigPath) } override fun write(ctx: SerializationCtx, buffer: AbstractBuffer, value: GenerateParams) { @@ -469,6 +471,7 @@ data class GenerateParams ( buffer.writeLong(value.timeout) buffer.writeLong(value.generationTimeout) buffer.writeBool(value.isSymbolicEngineEnabled) + buffer.writeString(value.symbolicEngineType) buffer.writeBool(value.isFuzzingEnabled) buffer.writeDouble(value.fuzzingValue) buffer.writeString(value.searchDirectory) @@ -494,6 +497,7 @@ data class GenerateParams ( if (timeout != other.timeout) return false if (generationTimeout != other.generationTimeout) return false if (isSymbolicEngineEnabled != other.isSymbolicEngineEnabled) return false + if (symbolicEngineType != other.symbolicEngineType) return false if (isFuzzingEnabled != other.isFuzzingEnabled) return false if (fuzzingValue != other.fuzzingValue) return false if (searchDirectory != other.searchDirectory) return false @@ -510,6 +514,7 @@ data class GenerateParams ( __r = __r*31 + timeout.hashCode() __r = __r*31 + generationTimeout.hashCode() __r = __r*31 + isSymbolicEngineEnabled.hashCode() + __r = __r*31 + symbolicEngineType.hashCode() __r = __r*31 + isFuzzingEnabled.hashCode() __r = __r*31 + fuzzingValue.hashCode() __r = __r*31 + searchDirectory.hashCode() @@ -526,6 +531,7 @@ data class GenerateParams ( print("timeout = "); timeout.print(printer); println() print("generationTimeout = "); generationTimeout.print(printer); println() print("isSymbolicEngineEnabled = "); isSymbolicEngineEnabled.print(printer); println() + print("symbolicEngineType = "); symbolicEngineType.print(printer); println() print("isFuzzingEnabled = "); isFuzzingEnabled.print(printer); println() print("fuzzingValue = "); fuzzingValue.print(printer); println() print("searchDirectory = "); searchDirectory.print(printer); println() @@ -539,7 +545,7 @@ data class GenerateParams ( /** - * #### Generated from [EngineProcessModel.kt:65] + * #### Generated from [EngineProcessModel.kt:66] */ data class GenerateResult ( val notEmptyCases: Int, @@ -602,7 +608,7 @@ data class GenerateResult ( /** - * #### Generated from [EngineProcessModel.kt:118] + * #### Generated from [EngineProcessModel.kt:119] */ data class GenerateTestReportArgs ( val eventLogMessage: String?, @@ -695,7 +701,7 @@ data class GenerateTestReportArgs ( /** - * #### Generated from [EngineProcessModel.kt:127] + * #### Generated from [EngineProcessModel.kt:128] */ data class GenerateTestReportResult ( val notifyMessage: String, @@ -827,7 +833,7 @@ data class JdkInfo ( /** - * #### Generated from [EngineProcessModel.kt:94] + * #### Generated from [EngineProcessModel.kt:95] */ data class MethodDescription ( val name: String, @@ -896,7 +902,7 @@ data class MethodDescription ( /** - * #### Generated from [EngineProcessModel.kt:132] + * #### Generated from [EngineProcessModel.kt:133] */ data class PerformParams ( val engineProcessTask: ByteArray @@ -953,7 +959,7 @@ data class PerformParams ( /** - * #### Generated from [EngineProcessModel.kt:69] + * #### Generated from [EngineProcessModel.kt:70] */ data class RenderParams ( val testSetsId: Long, @@ -1100,7 +1106,7 @@ data class RenderParams ( /** - * #### Generated from [EngineProcessModel.kt:87] + * #### Generated from [EngineProcessModel.kt:88] */ data class RenderResult ( val generatedCode: String, @@ -1163,7 +1169,7 @@ data class RenderResult ( /** - * #### Generated from [EngineProcessModel.kt:91] + * #### Generated from [EngineProcessModel.kt:92] */ data class SetupContextParams ( val classpathForUrlsClassloader: List @@ -1415,7 +1421,7 @@ data class TestGeneratorParams ( /** - * #### Generated from [EngineProcessModel.kt:113] + * #### Generated from [EngineProcessModel.kt:114] */ data class WriteSarifReportArguments ( val testSetsId: Long, diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt index 94fed03ce8..2f4e185bda 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/generator/UtTestsDialogProcessor.kt @@ -413,6 +413,7 @@ object UtTestsDialogProcessor { timeout = model.timeout, generationTimeout = model.timeout, isSymbolicEngineEnabled = useEngine, + symbolicEngineSource = model.symbolicEngineSource, isFuzzingEnabled = useFuzzing, fuzzingValue = project.service().fuzzingValue, searchDirectory = searchDirectory.pathString, diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt index 2ba4bff9bd..3e89a1c79a 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/models/GenerateTestsModel.kt @@ -26,6 +26,7 @@ import org.jetbrains.kotlin.psi.KtFile import org.utbot.common.PathUtil.fileExtension import org.utbot.framework.SummariesGenerationType import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.SymbolicEngineSource import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.MockFramework import org.utbot.framework.plugin.api.MockStrategyApi @@ -77,6 +78,7 @@ class GenerateTestsModel( lateinit var mockFramework: MockFramework lateinit var staticsMocking: StaticsMocking lateinit var parametrizedTestSource: ParametrizedTestSource + lateinit var symbolicEngineSource: SymbolicEngineSource lateinit var runtimeExceptionTestsBehaviour: RuntimeExceptionTestsBehaviour lateinit var hangingTestsTimeout: HangingTestsTimeout var useTaintAnalysis: Boolean = false diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt index 7d180f6363..edfab31431 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/process/EngineProcess.kt @@ -15,6 +15,7 @@ import kotlinx.coroutines.runBlocking import mu.KotlinLogging import org.utbot.common.* import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.SymbolicEngineSource import org.utbot.framework.codegen.tree.ututils.UtilClassKind import org.utbot.framework.context.ApplicationContext import org.utbot.framework.plugin.api.* @@ -229,6 +230,7 @@ class EngineProcess private constructor(val project: Project, private val classN timeout: Long, generationTimeout: Long, isSymbolicEngineEnabled: Boolean, + symbolicEngineSource: SymbolicEngineSource, isFuzzingEnabled: Boolean, fuzzingValue: Double, searchDirectory: String, @@ -242,6 +244,7 @@ class EngineProcess private constructor(val project: Project, private val classN timeout, generationTimeout, isSymbolicEngineEnabled, + symbolicEngineSource.name, isFuzzingEnabled, fuzzingValue, searchDirectory, diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt index b246da7cb8..9d6d0349a7 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/settings/Settings.kt @@ -23,6 +23,7 @@ private fun fromGenerateTestsModel(model: GenerateTestsModel): Settings.State { runInspectionAfterTestGeneration = model.runInspectionAfterTestGeneration, forceStaticMocking = model.forceStaticMocking, parametrizedTestSource = model.parametrizedTestSource, + symbolicEngineSource = model.symbolicEngineSource, classesToMockAlways = model.chosenClassesToMockAlways.mapTo(mutableSetOf()) { it.name }.toTypedArray(), springTestType = model.springTestType, springConfig = model.springConfig, diff --git a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt index 760046ef60..53e465d895 100644 --- a/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt +++ b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt @@ -88,6 +88,7 @@ import org.utbot.framework.codegen.domain.ParametrizedTestSource import org.utbot.framework.codegen.domain.ProjectType import org.utbot.framework.codegen.domain.SpringModule.* import org.utbot.framework.codegen.domain.StaticsMocking +import org.utbot.framework.codegen.domain.SymbolicEngineSource import org.utbot.framework.codegen.domain.TestFramework import org.utbot.framework.codegen.domain.TestNg import org.utbot.framework.plugin.api.MockStrategyApi @@ -215,6 +216,8 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m } private val parametrizedTestSources = JCheckBox("Parameterized tests") + private val useExperimentalEngine = JCheckBox("Experimental symbolic engine") + private lateinit var panel: DialogPanel @Suppress("UNCHECKED_CAST") @@ -412,6 +415,13 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m cell(testFrameworks) } + row { + cell(useExperimentalEngine) + contextHelp("USVM symbolic engine will be used") + }.enabledIf(ComboBoxPredicate(springConfig) { + model.projectType == ProjectType.PureJvm + }) + if (model.projectType == ProjectType.Spring) { row("Spring configuration:") { cell(springConfig) @@ -705,6 +715,8 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m model.mockStrategy = mockStrategies.item model.parametrizedTestSource = if (parametrizedTestSources.isSelected) ParametrizedTestSource.PARAMETRIZE else ParametrizedTestSource.DO_NOT_PARAMETRIZE + model.symbolicEngineSource = + if (useExperimentalEngine.isSelected) SymbolicEngineSource.Usvm else SymbolicEngineSource.UnitTestBot model.mockFramework = MOCKITO model.staticsMocking = if (staticsMocking.isSelected) MockitoStaticMocking else NoStaticMocking @@ -894,15 +906,26 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m else -> {} } + useExperimentalEngine.isSelected = (settings.symbolicEngineSource == SymbolicEngineSource.Usvm + && model.projectType == ProjectType.PureJvm) + useExperimentalEngine.isEnabled = model.projectType == ProjectType.PureJvm + mockStrategies.item = when (model.projectType) { ProjectType.Spring -> if (isSpringConfigSelected()) MockStrategyApi.springDefaultItem else settings.mockStrategy - else -> settings.mockStrategy + else -> if (settings.symbolicEngineSource == SymbolicEngineSource.Usvm) { + MockStrategyApi.NO_MOCKS + } else { + settings.mockStrategy + } } staticsMocking.isSelected = settings.staticsMocking == MockitoStaticMocking parametrizedTestSources.isSelected = (settings.parametrizedTestSource == ParametrizedTestSource.PARAMETRIZE && model.projectType == ProjectType.PureJvm) + mockStrategies.isEnabled = true + staticsMocking.isEnabled = mockStrategies.item != MockStrategyApi.NO_MOCKS + codegenLanguages.item = model.codegenLanguage val installedTestFramework = TestFramework.allItems.singleOrNull { it.isInstalled } @@ -929,8 +952,8 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m ProjectType.Python, ProjectType.JavaScript -> { } } - - mockStrategies.isEnabled = !isSpringConfigSelected() + mockStrategies.isEnabled = + settings.symbolicEngineSource == SymbolicEngineSource.UnitTestBot && !isSpringConfigSelected() updateStaticMockEnabled() updateMockStrategyList() @@ -1212,6 +1235,18 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m updateControlsEnabledStatus() } + useExperimentalEngine.addActionListener {_ -> + if (useExperimentalEngine.isSelected) { + mockStrategies.isEnabled = false + mockStrategies.item = MockStrategyApi.NO_MOCKS + + staticsMocking.isEnabled = false + staticsMocking.isSelected = false + } else { + updateControlsEnabledStatus() + } + } + springTestType.addActionListener { event -> val comboBox = event.source as ComboBox<*> val item = comboBox.item as SpringTestType @@ -1382,7 +1417,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m } private fun updateControlsEnabledStatus() { - mockStrategies.isEnabled = true + mockStrategies.isEnabled = !useExperimentalEngine.isSelected updateParametrizationEnabled() updateStaticMockEnabled() diff --git a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/UtFuzzedExecution.kt b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/UtFuzzedExecution.kt index 806f3126ec..f1958db378 100644 --- a/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/UtFuzzedExecution.kt +++ b/utbot-java-fuzzing/src/main/kotlin/org/utbot/fuzzer/UtFuzzedExecution.kt @@ -22,8 +22,17 @@ class UtFuzzedExecution( displayName: String? = null, val fuzzingValues: List? = null, val fuzzedMethodDescription: FuzzedMethodDescription? = null, - override val instrumentation: List = emptyList(), -) : UtExecution(stateBefore, stateAfter, result, coverage, summary, testMethodName, displayName), UtExecutionWithInstrumentation { + instrumentation: List = emptyList(), +) : UtExecutionWithInstrumentation( + stateBefore, + stateAfter, + result, + coverage, + summary, + testMethodName, + displayName, + instrumentation +) { /** * By design the 'before' and 'after' states contain info about the same fields. * It means that it is not possible for a field to be present at 'before' and to be absent at 'after'. @@ -39,7 +48,8 @@ class UtFuzzedExecution( coverage: Coverage?, summary: List?, testMethodName: String?, - displayName: String? + displayName: String?, + instrumentation: List, ): UtExecution { return UtFuzzedExecution( stateBefore, diff --git a/utbot-junit-contest/build.gradle b/utbot-junit-contest/build.gradle index 0b9d5c0d8e..38e573f12c 100644 --- a/utbot-junit-contest/build.gradle +++ b/utbot-junit-contest/build.gradle @@ -172,16 +172,6 @@ dependencies { implementation "org.burningwave:core:12.62.7" - implementation "$usvmRepo:usvm-core:$usvmVersion" - implementation "$usvmRepo:usvm-jvm:$usvmVersion" - implementation "$usvmRepo:usvm-jvm-api:$usvmVersion" - implementation "$usvmRepo:usvm-jvm-instrumentation:$usvmVersion" - implementation "$usvmRepo:usvm-jvm-instrumentation-collectors:$usvmVersion" - - implementation group: "org.jacodb", name: "jacodb-core", version: jacoDbVersion - implementation group: "org.jacodb", name: "jacodb-analysis", version: jacoDbVersion - implementation group: "org.jacodb", name: "jacodb-approximations", version: jacoDbVersion - // TODO uvms-sbft-hack: UtBot has `fastutil:8.3.0` on the classpath that overrides classes from // `fastutil-core:8.5.11` that USVM adds. Solution: bump `fastutil` version to `8.5.11` runtimeOnly("it.unimi.dsi:fastutil:8.5.11") 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 40c281908d..787d96e3c2 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 @@ -1,13 +1,8 @@ package org.utbot.contest.usvm -import kotlinx.coroutines.* +import kotlinx.coroutines.ObsoleteCoroutinesApi +import kotlinx.coroutines.runBlocking 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 import org.jacodb.approximation.Approximations import org.jacodb.impl.features.InMemoryHierarchy import org.objectweb.asm.Type @@ -15,20 +10,25 @@ import org.usvm.PathSelectionStrategy import org.usvm.PathSelectorFairnessStrategy import org.usvm.SolverType import org.usvm.UMachineOptions -import org.usvm.api.targets.JcTarget -import org.usvm.instrumentation.util.jcdbSignature import org.usvm.machine.JcMachine -import org.usvm.machine.state.JcState -import org.usvm.statistics.collectors.StatesCollector import org.usvm.types.ClassScorer import org.usvm.types.TypeScorer import org.usvm.types.scoreClassNode -import org.utbot.common.ThreadBasedExecutor +import org.usvm.util.ApproximationPaths import org.utbot.common.info import org.utbot.common.measureTime -import org.utbot.contest.* +import org.utbot.contest.ClassUnderTest +import org.utbot.contest.ExpectedExceptionsForClass +import org.utbot.contest.StatsForClass +import org.utbot.contest.StatsForMethod +import org.utbot.contest.forceStaticMocking import org.utbot.contest.junitVersion +import org.utbot.contest.prepareClass +import org.utbot.contest.setOptions +import org.utbot.contest.staticsMocking +import org.utbot.contest.updateCoverage import org.utbot.contest.usvm.log.ErrorCountingLoggerAppender +import org.utbot.contest.writeTestClass import org.utbot.framework.codegen.domain.ProjectType import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour import org.utbot.framework.codegen.domain.junitByVersion @@ -37,12 +37,12 @@ import org.utbot.framework.codegen.generator.CodeGeneratorParams import org.utbot.framework.codegen.services.language.CgLanguageAssistant import org.utbot.framework.fuzzer.ReferencePreservingIntIdGenerator import org.utbot.framework.minimization.minimizeExecutions -import org.utbot.framework.plugin.api.* -import org.utbot.framework.plugin.api.util.constructor +import org.utbot.framework.plugin.api.CodegenLanguage +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtMethodTestSet +import org.utbot.framework.plugin.api.exceptionOrNull import org.utbot.framework.plugin.api.util.jClass -import org.utbot.framework.plugin.api.util.method -import org.utbot.framework.plugin.api.util.utContext -import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.framework.plugin.services.JdkInfoService import org.utbot.fuzzer.UtFuzzedExecution import org.utbot.summary.usvm.summarizeAll @@ -50,8 +50,12 @@ import org.utbot.usvm.converter.JcToUtExecutionConverter import org.utbot.usvm.converter.SimpleInstructionIdProvider import org.utbot.usvm.converter.toExecutableId import org.utbot.usvm.jc.JcContainer -import org.utbot.usvm.jc.JcContainer.Companion.TEST_EXECUTION_TIMEOUT +import org.utbot.usvm.jc.JcContainer.Companion.testExecutionTimeout +import org.utbot.usvm.jc.JcJars import org.utbot.usvm.jc.JcTestExecutor +import org.utbot.usvm.jc.findMethodOrNull +import org.utbot.usvm.jc.typedMethod +import org.utbot.usvm.machine.analyzeAsync import java.io.File import java.net.URLClassLoader import kotlin.time.Duration.Companion.milliseconds @@ -164,7 +168,7 @@ fun runUsvmGeneration( options = UMachineOptions( // TODO usvm-sbft: if we have less than CONTEST_TEST_EXECUTION_TIMEOUT time left, we should try execute // with smaller timeout, but instrumentation currently doesn't allow to change timeout for individual runs - timeout = generationTimeoutMillisWithoutCodegen.milliseconds - alreadySpentBudgetMillis.milliseconds - TEST_EXECUTION_TIMEOUT, + timeout = generationTimeoutMillisWithoutCodegen.milliseconds - alreadySpentBudgetMillis.milliseconds - testExecutionTimeout, pathSelectionStrategies = listOf(PathSelectionStrategy.CLOSEST_TO_UNCOVERED_RANDOM), pathSelectorFairnessStrategy = PathSelectorFairnessStrategy.COMPLETELY_FAIR, solverType = SolverType.Z3, // TODO: usvm-ksmt: Yices doesn't work on old linux @@ -181,7 +185,8 @@ fun runUsvmGeneration( method = state.entrypoint.typedMethod, state = state, stringConstants = jcMachine.stringConstants, - classConstants = jcMachine.classConstants + classConstants = jcMachine.classConstants, + allowSymbolicResult = true ) ?: return@analyzeAsync }.getOrElse { e -> logger.error(e) { "executor.execute(${state.entrypoint}) failed" } @@ -250,7 +255,7 @@ fun runUsvmGeneration( fun createJcContainer( tmpDir: File, classpathFiles: List -) = JcContainer( +) = JcContainer.getOrCreate( usePersistence = false, persistenceDir = tmpDir, classpath = classpathFiles, @@ -258,57 +263,14 @@ fun createJcContainer( ) { // TODO usvm-sbft: we may want to tune these JcSettings for contest // TODO: require usePersistence=false for ClassScorer - installFeatures(InMemoryHierarchy, Approximations, ClassScorer(TypeScorer, ::scoreClassNode)) + val approximationPaths = ApproximationPaths( + usvmApiJarPath = JcJars.approximationsApiJar.absolutePath, + usvmApproximationsJarPath = JcJars.approximationsJar.absolutePath, + ) + installFeatures(InMemoryHierarchy, Approximations, ClassScorer(TypeScorer, ::scoreClassNode, approximationPaths)) loadByteCode(classpathFiles) } -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 -} - -fun JcMachine.analyzeAsync( - forceTerminationTimeout: Long, - methods: List, - targets: List, - callback: (JcState) -> Unit -) { - val utContext = utContext - // TODO usvm-sbft: sometimes `machine.analyze` or `executor.execute` hangs forever, - // completely ignoring timeout specified for it, so additional hard time out is enforced here. - // Hard timeout seems to be working ok so far, however it may leave machine or executor in an inconsistent state. - // Also, `machine` or `executor` may catch `ThreadDeath` and still continue working (that is in fact what happens, - // but throwing `ThreadDeath` every 500 ms seems to eventually work). - ThreadBasedExecutor.threadLocal.invokeWithTimeout(forceTerminationTimeout, threadDeathThrowPeriodMillis = 500) { - withUtContext(utContext) { - analyze( - methods = methods, - statesCollector = object : StatesCollector { - override var count: Int = 0 - private set - - override fun addState(state: JcState) { - count++ - callback(state) - } - }, - targets = targets - ) - } - }?.onFailure { e -> - logger.error(e) { "analyzeAsync failed" } - } ?: logger.error { "analyzeAsync time exceeded hard time out" } -} - private inline fun accumulateMeasureTime( regionName: String, timeAccumulatorMillis: MutableMap, diff --git a/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt index bc8ffcabfb..5d6c7469ea 100644 --- a/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt +++ b/utbot-rd/src/main/rdgen/org/utbot/rd/models/EngineProcessModel.kt @@ -55,6 +55,7 @@ object EngineProcessModel : Ext(EngineProcessRoot) { // testflow field("generationTimeout", PredefinedType.long) field("isSymbolicEngineEnabled", PredefinedType.bool) + field("symbolicEngineType", PredefinedType.string) field("isFuzzingEnabled", PredefinedType.bool) field("fuzzingValue", PredefinedType.double) // method filters diff --git a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt index e75e865f35..c6c92d52b8 100644 --- a/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt +++ b/utbot-ui-commons/src/main/kotlin/org/utbot/intellij/plugin/settings/CommonSettings.kt @@ -37,6 +37,7 @@ import java.util.concurrent.CompletableFuture import kotlin.reflect.KClass import org.utbot.common.isWindows import org.utbot.framework.SummariesGenerationType +import org.utbot.framework.codegen.domain.SymbolicEngineSource import org.utbot.framework.codegen.domain.UnknownTestFramework import org.utbot.framework.plugin.api.SpringTestType import org.utbot.framework.plugin.api.isSummarizationCompatible @@ -66,6 +67,7 @@ class Settings(val project: Project) : PersistentStateComponent var forceStaticMocking: ForceStaticMocking = ForceStaticMocking.defaultItem, var treatOverflowAsError: TreatOverflowAsError = TreatOverflowAsError.defaultItem, var parametrizedTestSource: ParametrizedTestSource = ParametrizedTestSource.defaultItem, + var symbolicEngineSource: SymbolicEngineSource = SymbolicEngineSource.defaultItem, var classesToMockAlways: Array = Mocker.defaultSuperClassesToMockAlwaysNames.toTypedArray(), var springTestType: SpringTestType = SpringTestType.defaultItem, var springConfig: String = SpringConfig.defaultItem, @@ -98,6 +100,7 @@ class Settings(val project: Project) : PersistentStateComponent if (forceStaticMocking != other.forceStaticMocking) return false if (treatOverflowAsError != other.treatOverflowAsError) return false if (parametrizedTestSource != other.parametrizedTestSource) return false + if (symbolicEngineSource != other.symbolicEngineSource) return false if (!classesToMockAlways.contentEquals(other.classesToMockAlways)) return false if (springTestType != other.springTestType) return false if (springConfig != other.springConfig) return false @@ -124,6 +127,7 @@ class Settings(val project: Project) : PersistentStateComponent result = 31 * result + forceStaticMocking.hashCode() result = 31 * result + treatOverflowAsError.hashCode() result = 31 * result + parametrizedTestSource.hashCode() + result = 31 * result + symbolicEngineSource.hashCode() result = 31 * result + classesToMockAlways.contentHashCode() result = 31 * result + springTestType.hashCode() result = 31 * result + springConfig.hashCode() @@ -174,6 +178,8 @@ class Settings(val project: Project) : PersistentStateComponent val parametrizedTestSource: ParametrizedTestSource get() = state.parametrizedTestSource + val symbolicEngineSource: SymbolicEngineSource get() = state.symbolicEngineSource + val classesToMockAlways: Set get() = state.classesToMockAlways.toSet() val springTestType: SpringTestType get() = state.springTestType diff --git a/utbot-usvm/build.gradle.kts b/utbot-usvm/build.gradle.kts index 67c5b6d982..a69a2aa680 100644 --- a/utbot-usvm/build.gradle.kts +++ b/utbot-usvm/build.gradle.kts @@ -18,15 +18,15 @@ val usvmInstrumentationRunner: Configuration by configurations.creating {} dependencies { implementation(project(":utbot-framework-api")) - implementation(group = "org.jacodb", name = "jacodb-core", version = jacoDbVersion) - implementation(group = "org.jacodb", name = "jacodb-analysis", version = jacoDbVersion) - implementation(group = "org.jacodb", name = "jacodb-approximations", version = jacoDbVersion) + api(group = "org.jacodb", name = "jacodb-core", version = jacoDbVersion) + api(group = "org.jacodb", name = "jacodb-analysis", version = jacoDbVersion) + api(group = "org.jacodb", name = "jacodb-approximations", version = jacoDbVersion) - implementation(group = usvmRepo, name = "usvm-core", version = usvmVersion) - implementation(group = usvmRepo, name = "usvm-jvm", version = usvmVersion) - implementation(group = usvmRepo, name = "usvm-jvm-api", version = usvmVersion) - implementation(group = usvmRepo, name = "usvm-jvm-instrumentation", version = usvmVersion) - implementation(group = usvmRepo, name = "usvm-jvm-instrumentation-collectors", version = usvmVersion) + api(group = usvmRepo, name = "usvm-core", version = usvmVersion) + api(group = usvmRepo, name = "usvm-jvm", version = usvmVersion) + api(group = usvmRepo, name = "usvm-jvm-api", version = usvmVersion) + api(group = usvmRepo, name = "usvm-jvm-instrumentation", version = usvmVersion) + api(group = usvmRepo, name = "usvm-jvm-instrumentation-collectors", version = usvmVersion) approximations("$approximationsRepo:approximations:$approximationsVersion") diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/JcToUtExecutionConverter.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/JcToUtExecutionConverter.kt index 2b1392b7e7..9586e004e3 100644 --- a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/JcToUtExecutionConverter.kt +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/JcToUtExecutionConverter.kt @@ -36,11 +36,13 @@ import org.utbot.framework.plugin.api.UtExecutionFailure import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.UtImplicitlyThrownException +import org.utbot.framework.plugin.api.UtInstrumentation import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.UtStatementCallModel import org.utbot.framework.plugin.api.UtTimeoutException import org.utbot.framework.plugin.api.UtVoidModel import org.utbot.framework.plugin.api.mapper.UtModelDeepMapper +import org.utbot.framework.plugin.api.mapper.mapModels import org.utbot.framework.plugin.api.util.executableId import org.utbot.framework.plugin.api.util.jClass import org.utbot.framework.plugin.api.util.utContext @@ -54,6 +56,11 @@ import java.util.IdentityHashMap private val logger = KotlinLogging.logger {} +data class UtExecutionInitialState( + val stateBefore: EnvironmentModels, + val instrumentations: List, +) + class JcToUtExecutionConverter( private val jcExecution: JcExecution, private val jcClasspath: JcClasspath, @@ -78,6 +85,11 @@ class JcToUtExecutionConverter( .getOrNull() } ?: error("Failed to construct UtExecution for all uTestExecutionResultWrappers on ${jcExecution.method.method}") + fun convertInitialStateOnly(): UtExecutionInitialState = UtExecutionInitialState( + stateBefore = constructStateBeforeFromUTest().applyCommonMappings(), + instrumentations = uTestProcessResult.instrumentation.applyCommonMappings() + ) + private fun convert(uTestResultWrapper: UTestResultWrapper): UtExecution? { val coverage = convertCoverage(getTrace(uTestResultWrapper), jcExecution.method.enclosingType.jcClass) @@ -165,16 +177,18 @@ class JcToUtExecutionConverter( } } ?: return null - return utUsvmExecution - .mapModels(jcToUtModelConverter.utCyclicReferenceModelResolver) - .mapModels(constructAssemblingMapper()) - .mapModels(constructAssembleToCompositeModelMapper()) - .mapModels(constructConstArrayModelMapper()) + + return utUsvmExecution.applyCommonMappings() } private fun constructAssemblingMapper(): UtModelDeepMapper = UtModelDeepMapper { model -> - // TODO usvm-sbft: support constructors with parameters here if it is really required - // Unfortunately, it is not possible to use [AssembleModelGeneral] as it requires soot being initialized. + /* + Unfortunately, sometimes it is not possible to use [AssembleModelGeneral] + as it requires soot being initialized. Soot is not used in `ContestUsvm`. + + After that, even if Soot is initialized, it required some refactoring to move + some AssembleModelGenerated API related features to `utbot-framework-api`. + */ if (model !is UtAssembleModel || utilMethodProvider.createInstanceMethodId != model.instantiationCall.statement || model.modificationsChain.isNotEmpty()) { @@ -186,6 +200,7 @@ class JcToUtExecutionConverter( .params .single() as UtPrimitiveModel).value.toString() + // TODO usvm-sbft: support constructors with parameters here if it is really required val defaultConstructor = ClassId(instantiatingClassName) .jClass .constructors @@ -342,4 +357,25 @@ class JcToUtExecutionConverter( // I assume they are counted as part of `` method instructionsCount = jcClass.declaredMethods.sumOf { it.instList.size.toLong() } ) + + private val commonMappers: List = listOf( + jcToUtModelConverter.utCyclicReferenceModelResolver, + constructAssemblingMapper(), + constructAssembleToCompositeModelMapper(), + constructConstArrayModelMapper(), + ) + private fun UtExecution.applyCommonMappings(): UtExecution = + commonMappers.fold(this) { mappedExecution, mapper -> + mappedExecution.mapModels(mapper) + } + + private fun EnvironmentModels.applyCommonMappings(): EnvironmentModels = + commonMappers.fold(this) { mappedEnvironmentalModels, mapper -> + mappedEnvironmentalModels.mapModels(mapper) + } + + private fun List.applyCommonMappings(): List = + commonMappers.fold(this) { mappedInstrumentation, mapper -> + mappedInstrumentation.map { instr -> instr.mapModels(mapper) } + } } \ No newline at end of file diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UtUsvmExecution.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UtUsvmExecution.kt index 4d8513f5fa..f6d196fbf1 100644 --- a/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UtUsvmExecution.kt +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/converter/UtUsvmExecution.kt @@ -7,9 +7,6 @@ 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, @@ -19,16 +16,17 @@ class UtUsvmExecution( summary: List? = emptyList(), testMethodName: String? = null, displayName: String? = null, - override val instrumentation: List -) : UtExecution( + instrumentation: List +) : UtExecutionWithInstrumentation( stateBefore, stateAfter, result, coverage, summary, testMethodName, - displayName -), UtExecutionWithInstrumentation { + displayName, + instrumentation, +) { override fun copy( stateBefore: EnvironmentModels, stateAfter: EnvironmentModels, @@ -36,7 +34,8 @@ class UtUsvmExecution( coverage: Coverage?, summary: List?, testMethodName: String?, - displayName: String? + displayName: String?, + instrumentation: List, ): UtExecution = UtUsvmExecution( stateBefore, stateAfter, @@ -47,35 +46,4 @@ class UtUsvmExecution( displayName, instrumentation, ) - - fun copy( - stateBefore: EnvironmentModels = this.stateBefore, - stateAfter: EnvironmentModels = this.stateAfter, - result: UtExecutionResult = this.result, - coverage: Coverage? = this.coverage, - summary: List? = this.summary, - testMethodName: String? = this.testMethodName, - displayName: String? = this.displayName, - instrumentation: List = this.instrumentation, - ) = UtUsvmExecution( - stateBefore, - stateAfter, - result, - coverage, - summary, - testMethodName, - displayName, - instrumentation, - ) } - -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-usvm/src/main/kotlin/org/utbot/usvm/jacodb/JacoDbUtils.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jacodb/JacoDbUtils.kt new file mode 100644 index 0000000000..df1eaddb48 --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jacodb/JacoDbUtils.kt @@ -0,0 +1,14 @@ +package org.utbot.usvm.jacodb + +import org.usvm.instrumentation.util.jcdbSignature +import org.utbot.framework.plugin.api.ConstructorId +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.framework.plugin.api.MethodId +import org.utbot.framework.plugin.api.util.constructor +import org.utbot.framework.plugin.api.util.method + +val ExecutableId.jcdbSignature: String + get() = when (this) { + is ConstructorId -> constructor.jcdbSignature + is MethodId -> method.jcdbSignature + } \ No newline at end of file diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcContainer.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcContainer.kt index 0cf23878ee..ac2cf3ad6d 100644 --- a/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcContainer.kt +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcContainer.kt @@ -7,16 +7,25 @@ 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.instrumentation.executor.InstrumentationProcessPaths import org.usvm.instrumentation.executor.UTestConcreteExecutor import org.usvm.instrumentation.instrumentation.JcRuntimeTraceInstrumenterFactory +import org.usvm.util.ApproximationPaths import org.usvm.util.classpathWithApproximations +import org.utbot.framework.UtSettings import java.io.File -import kotlin.time.Duration.Companion.seconds +import kotlin.time.Duration.Companion.milliseconds 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( +/** + * NOTE that JcContainer can also be instantiated with [JcContainer.getOrCreate] method. + * + * Calling constructor directly should be used if you definitely want to create + * new JcContainer instead of trying it get it from containers cache. + */ +class JcContainer( usePersistence: Boolean, persistenceDir: File, classpath: List, @@ -51,7 +60,12 @@ class JcContainer private constructor( persistent(location = persistenceLocation, clearOnStart = false) } } - val cp = db.classpathWithApproximations(classpath, listOf(UnknownClasses)) + + val approximationPaths = ApproximationPaths( + usvmApiJarPath = JcJars.approximationsApiJar.absolutePath, + usvmApproximationsJarPath = JcJars.approximationsJar.absolutePath, + ) + val cp = db.classpathWithApproximations(classpath, listOf(UnknownClasses), approximationPaths) db to cp } this.db = db @@ -60,9 +74,13 @@ class JcContainer private constructor( JcRuntimeTraceInstrumenterFactory::class, cpPath, cp, - javaHome.absolutePath, + InstrumentationProcessPaths( + pathToUsvmInstrumentationJar = JcJars.runnerJar.absolutePath, + pathToUsvmCollectorsJar = JcJars.collectorsJar.absolutePath, + pathToJava = javaHome.absolutePath, + ), persistenceLocation, - TEST_EXECUTION_TIMEOUT + testExecutionTimeout ) runBlocking { db.awaitBackgroundJobs() @@ -76,11 +94,12 @@ class JcContainer private constructor( } companion object : AutoCloseable { - val TEST_EXECUTION_TIMEOUT = 1.seconds + val testExecutionTimeout + get() = UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis.milliseconds private val cache = HashMap, JcContainer>() - operator fun invoke( + fun getOrCreate( usePersistence: Boolean, persistenceDir: File, classpath: List, diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutor.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutor.kt index 8ff326c6dd..02b112f30c 100644 --- a/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutor.kt +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutor.kt @@ -63,20 +63,9 @@ class JcTestExecutor( state: JcState, stringConstants: Map, classConstants: Map, + allowSymbolicResult: Boolean ): JcExecution? { - val model = state.models.first() - - val ctx = state.ctx - - val mocker = state.memory.mocker as JcMocker -// val staticMethodMocks = mocker.statics TODO global mocks????????????????????????? - val methodMocks = mocker.symbols - - val resolvedMethodMocks = methodMocks.entries.groupBy({ model.eval(it.key) }, { it.value }) - .mapValues { it.value.flatten() } - - val memoryScope = MemoryScope(ctx, model, model, stringConstants, classConstants, resolvedMethodMocks, method) - + val memoryScope = createMemoryScope(state, stringConstants, classConstants, method) val uTest = memoryScope.createUTest() val concreteResult = runCatching { @@ -84,20 +73,24 @@ class JcTestExecutor( UTestConcreteExecutionResult(runner.executeAsync(uTest)) } } - .onFailure { e -> logger.warn(e) { "Recoverable: runner.executeAsync(uTest) failed on ${method.method}" } } + .onFailure { e -> + logger.warn(e) { "Recoverable: runner.executeAsync(uTest) failed on ${method.method}" } + } .getOrNull() val symbolicResult by lazy { - when (val methodResult = state.methodResult) { - is JcMethodResult.JcException -> UTestSymbolicExceptionResult(methodResult.type) - is JcMethodResult.Success -> { - val resultScope = MemoryScope(ctx, model, state.memory, stringConstants, classConstants, resolvedMethodMocks, method) - val resultExpr = resultScope.resolveExpr(methodResult.value, method.returnType) - val resultInitializer = resultScope.decoderApi.initializerInstructions() - UTestSymbolicSuccessResult(resultInitializer, resultExpr) + if (allowSymbolicResult) { + when (val methodResult = state.methodResult) { + is JcMethodResult.JcException -> UTestSymbolicExceptionResult(methodResult.type) + is JcMethodResult.Success -> { + val resultExpr = memoryScope.resolveExpr(methodResult.value, method.returnType) + val resultInitializer = memoryScope.decoderApi.initializerInstructions() + UTestSymbolicSuccessResult(resultInitializer, resultExpr) + } + + JcMethodResult.NoCall -> null } - JcMethodResult.NoCall -> null - } + } else null } val testExecutionResult = concreteResult?.uTestExecutionResult @@ -113,9 +106,7 @@ class JcTestExecutor( if (testExecutionResult is UTestExecutionSuccessResult || testExecutionResult is UTestExecutionExceptionResult) { concreteResult } else { - symbolicResult - ?: concreteResult - ?: error("Can't create JcExecution, there's no symbolic nor concrete result for ${method.method}") + symbolicResult ?: concreteResult } val coverage = resolveCoverage(method, state) @@ -124,7 +115,7 @@ class JcTestExecutor( method = method, uTest = uTest, uTestExecutionResultWrappers = sequence { - yield(preferableResult) + preferableResult?.let { yield(it) } if (preferableResult !== symbolicResult) symbolicResult?.let { yield(it) } }, @@ -132,6 +123,38 @@ class JcTestExecutor( ) } + fun createUTest( + method: JcTypedMethod, + state: JcState, + stringConstants: Map, + classConstants: Map, + ): UTest { + val memoryScope = createMemoryScope(state, stringConstants, classConstants, method) + return memoryScope.createUTest() + } + + private fun createMemoryScope( + state: JcState, + stringConstants: Map, + classConstants: Map, + method: JcTypedMethod + ): MemoryScope { + val model = state.models.first() + val ctx = state.ctx + + val mocker = state.memory.mocker as JcMocker + // val staticMethodMocks = mocker.statics TODO global mocks????????????????????????? + val methodMocks = mocker.symbols + + val resolvedMethodMocks = methodMocks + .entries + .groupBy({ model.eval(it.key) }, { it.value }) + .mapValues { it.value.flatten() } + + val memoryScope = MemoryScope(ctx, model, model, stringConstants, classConstants, resolvedMethodMocks, method) + return memoryScope + } + @Suppress("UNUSED_PARAMETER") private fun resolveCoverage(method: JcTypedMethod, state: JcState): JcCoverage { // TODO: extract coverage diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcUtils.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcUtils.kt new file mode 100644 index 0000000000..cc37fe701f --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcUtils.kt @@ -0,0 +1,20 @@ +package org.utbot.usvm.jc + +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 +import org.utbot.framework.plugin.api.ExecutableId +import org.utbot.usvm.jacodb.jcdbSignature + +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 + } \ No newline at end of file diff --git a/utbot-usvm/src/main/kotlin/org/utbot/usvm/machine/UsvmAnalyzer.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/machine/UsvmAnalyzer.kt new file mode 100644 index 0000000000..5d44308d32 --- /dev/null +++ b/utbot-usvm/src/main/kotlin/org/utbot/usvm/machine/UsvmAnalyzer.kt @@ -0,0 +1,46 @@ +package org.utbot.usvm.machine + +import mu.KotlinLogging +import org.jacodb.api.JcMethod +import org.usvm.api.targets.JcTarget +import org.usvm.machine.JcMachine +import org.usvm.machine.state.JcState +import org.usvm.statistics.collectors.StatesCollector +import org.utbot.common.ThreadBasedExecutor +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.framework.plugin.api.util.withUtContext + +private val logger = KotlinLogging.logger {} + +fun JcMachine.analyzeAsync( + forceTerminationTimeout: Long, + methods: List, + targets: List, + callback: (JcState) -> Unit +) { + val utContext = utContext + // TODO usvm-sbft: sometimes `machine.analyze` or `executor.execute` hangs forever, + // completely ignoring timeout specified for it, so additional hard time out is enforced here. + // Hard timeout seems to be working ok so far, however it may leave machine or executor in an inconsistent state. + // Also, `machine` or `executor` may catch `ThreadDeath` and still continue working (that is in fact what happens, + // but throwing `ThreadDeath` every 500 ms seems to eventually work). + ThreadBasedExecutor.threadLocal.invokeWithTimeout(forceTerminationTimeout, threadDeathThrowPeriodMillis = 500) { + withUtContext(utContext) { + analyze( + methods = methods, + statesCollector = object : StatesCollector { + override var count: Int = 0 + private set + + override fun addState(state: JcState) { + count++ + callback(state) + } + }, + targets = targets + ) + } + }?.onFailure { e -> + logger.error(e) { "analyzeAsync failed" } + } ?: logger.error { "analyzeAsync time exceeded hard time out" } +} \ No newline at end of file