diff --git a/gradle.properties b/gradle.properties index d67d7f007d..b3a4e98f68 100644 --- a/gradle.properties +++ b/gradle.properties @@ -41,12 +41,15 @@ junit4PlatformVersion=1.9.0 # NOTE: Mockito versions 5+ are not compatible with Java 8: https://www.davidvlijmincx.com/posts/upgrade-to-mockito-5 mockitoVersion=4.11.0 mockitoInlineVersion=4.11.0 -ksmtVersion=0.4.3 +# TODO usvm-sbft-merge: UtBot engine uses ksmt 0.4.3, while USVM uses ksmt 0.5.7 +ksmtVersion=0.5.7 sootVersion=4.4.0-FORK-2 kotlinVersion=1.8.0 log4j2Version=2.13.3 -coroutinesVersion=1.6.3 -collectionsVersion=0.3.4 +# TODO usvm-sbft-merge: UtBot uses kotlinx.coroutines 1.6.3, while USVM uses kotlinx.coroutines 1.6.4 +coroutinesVersion=1.6.4 +# TODO usvm-sbft-merge: UtBot uses kotlinx.collections 0.3.4, while USVM uses kotlinx.collections 0.3.5 +collectionsVersion=0.3.5 # after updating plugin version you should manually bump corresponding versions in plugin # as they cannot be set from properties # utbot-intellij/build.gradle.kts @@ -56,7 +59,8 @@ intellijPluginVersion=1.13.1 # every time you bump rd version: # 1. regenerate all models # 2. check if rider plugin works -rdVersion=2023.1.2 +# TODO usvm-sbft-merge: UtBot engine uses RD 2023.1.2, while USVM uses RD 2023.2.0 +rdVersion=2023.2.0 # to enable - add -PincludeRiderInBuild=true in build CLI includeRiderInBuild=false jacocoVersion=0.8.8 @@ -77,6 +81,7 @@ testNgVersion=7.6.0 kamlVersion=0.51.0 jacksonVersion=2.12.3 kotlinxSerializationVersion=1.5.0 +# TODO usvm-sbft: USVM uses slf4j 1.6.1 slf4jVersion=1.7.36 eclipseAetherVersion=1.1.0 mavenWagonVersion=3.5.1 @@ -96,6 +101,7 @@ commonsLoggingVersion=1.2 commonsIOVersion=2.11.0 javaxVersion=2.2 jakartaVersion=3.1.0 +jacoDbVersion=1.3.0 # use latest Java 8 compaitable Spring and Spring Boot versions springVersion=5.3.28 diff --git a/settings.gradle.kts b/settings.gradle.kts index 38c5173f3b..cbeca8b19d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -91,4 +91,8 @@ if (projectType == ultimateEdition) { } include("utbot-light") + include("utbot-intellij-main") + +// TODO usvm-sbft-merge: add if here if we want merge contest it into main +includeBuild("../usvm") \ No newline at end of file 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 80a8639b5c..ffb15e7344 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 @@ -148,6 +148,10 @@ abstract class UtExecution( val executableToCall get() = stateBefore.executableToCall } +interface UtExecutionWithInstrumentation { + val instrumentation: List +} + /** * Symbolic execution. * @@ -163,7 +167,7 @@ class UtSymbolicExecution( stateBefore: EnvironmentModels, stateAfter: EnvironmentModels, result: UtExecutionResult, - val instrumentation: List, + override val instrumentation: List, val path: MutableList, val fullPath: List, coverage: Coverage? = null, @@ -171,7 +175,7 @@ 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) { +) : UtExecution(stateBefore, stateAfter, result, coverage, summary, testMethodName, displayName), UtExecutionWithInstrumentation { /** * 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'. diff --git a/utbot-framework-test/build.gradle b/utbot-framework-test/build.gradle index eea8967eb1..5c4bc02926 100644 --- a/utbot-framework-test/build.gradle +++ b/utbot-framework-test/build.gradle @@ -42,8 +42,9 @@ dependencies { testImplementation group: 'org.mockito', name: 'mockito-inline', version: mockitoInlineVersion testImplementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2Version - implementation group: 'com.github.UnitTestBot.ksmt', name: 'ksmt-core', version: ksmtVersion - implementation group: 'com.github.UnitTestBot.ksmt', name: 'ksmt-z3', version: ksmtVersion + // TODO sbft-usvm-merge: UtBot engine expects `com.github.UnitTestBot.ksmt` here + implementation group: 'io.ksmt', name: 'ksmt-core', version: ksmtVersion + implementation group: 'io.ksmt', name: 'ksmt-z3', version: ksmtVersion } // This is required to avoid conflict between SpringBoot standard logger and the logger of our project. diff --git a/utbot-framework/build.gradle b/utbot-framework/build.gradle index b56b4bfb88..c471f88cef 100644 --- a/utbot-framework/build.gradle +++ b/utbot-framework/build.gradle @@ -42,8 +42,9 @@ dependencies { implementation group: 'org.junit.jupiter', name: 'junit-jupiter-params', version: '5.8.1' implementation group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.8.1' - implementation group: 'com.github.UnitTestBot.ksmt', name: 'ksmt-core', version: ksmtVersion - implementation group: 'com.github.UnitTestBot.ksmt', name: 'ksmt-z3', version: ksmtVersion + // TODO sbft-usvm-merge: UtBot engine expects `com.github.UnitTestBot.ksmt` here + implementation group: 'io.ksmt', name: 'ksmt-core', version: ksmtVersion + implementation group: 'io.ksmt', name: 'ksmt-z3', version: ksmtVersion fetchInstrumentationJar project(path: ':utbot-instrumentation', configuration: 'instrumentationArchive') } diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt index cc709f84ea..5fbb965f1b 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt @@ -99,6 +99,7 @@ import org.utbot.framework.plugin.api.UtExecution import org.utbot.framework.plugin.api.UtExecutionFailure import org.utbot.framework.plugin.api.UtExecutionResult import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtExecutionWithInstrumentation import org.utbot.framework.plugin.api.UtExplicitlyThrownException import org.utbot.framework.plugin.api.UtLambdaModel import org.utbot.framework.plugin.api.UtModel @@ -201,8 +202,7 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte protected fun setupInstrumentation() { val instrumentation = when (val execution = currentExecution) { - is UtSymbolicExecution -> execution.instrumentation - is UtFuzzedExecution -> execution.instrumentation + is UtExecutionWithInstrumentation -> execution.instrumentation else -> return } if (instrumentation.isEmpty()) return 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 8ea8b2a459..806f3126ec 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,8 @@ class UtFuzzedExecution( displayName: String? = null, val fuzzingValues: List? = null, val fuzzedMethodDescription: FuzzedMethodDescription? = null, - val instrumentation: List = emptyList(), -) : UtExecution(stateBefore, stateAfter, result, coverage, summary, testMethodName, displayName) { + override val instrumentation: List = emptyList(), +) : UtExecution(stateBefore, stateAfter, result, coverage, summary, testMethodName, displayName), UtExecutionWithInstrumentation { /** * 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'. diff --git a/utbot-junit-contest/build.gradle b/utbot-junit-contest/build.gradle index 52254278f2..afea753424 100644 --- a/utbot-junit-contest/build.gradle +++ b/utbot-junit-contest/build.gradle @@ -139,6 +139,19 @@ dependencies { implementation group: 'org.mockito', name: 'mockito-core', version: mockitoVersion implementation group: 'org.mockito', name: 'mockito-inline', version: mockitoInlineVersion implementation 'junit:junit:4.13.2' + + implementation('org.usvm:usvm-core') + implementation('org.usvm:usvm-jvm') + implementation('org.usvm:usvm-jvm-instrumentation') + + 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") + testImplementation fileTree(dir: 'src/main/resources/projects/', include: '*/*.jar') testImplementation files('src/main/resources/evosuite/evosuite-1.2.0.jar') testImplementation files('src/main/resources/evosuite/evosuite-standalone-runtime-1.2.0.jar') diff --git a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt index 896cd35002..3a20f61596 100644 --- a/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/Contest.kt @@ -417,7 +417,7 @@ fun runGeneration( statsForClass } -private fun prepareClass(javaClazz: Class<*>, methodNameFilter: String?): List { +fun prepareClass(javaClazz: Class<*>, methodNameFilter: String?): List { //1. all methods from cut val methods = javaClazz.declaredMethods .filterNot { it.isAbstract } @@ -491,11 +491,13 @@ internal fun File.toUrl(): URL = toURI().toURL() internal fun testMethodName(name: String, num: Int): String = "test${name.capitalize()}$num" +// TODO usvm-sbft: does SBFT allow to generate tests for private methods and constructors +// If no, add more filtering here internal val Method.isVisibleFromGeneratedTest: Boolean get() = (this.modifiers and Modifier.ABSTRACT) == 0 && (this.modifiers and Modifier.NATIVE) == 0 -private fun StatsForClass.updateCoverage(newCoverage: Coverage, isNewClass: Boolean, fromFuzzing: Boolean) { +fun StatsForClass.updateCoverage(newCoverage: Coverage, isNewClass: Boolean, fromFuzzing: Boolean) { coverage.update(newCoverage, isNewClass) // other coverage type updates by empty coverage to respect new class val emptyCoverage = newCoverage.copy( 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 d1271f70c2..0894c45f05 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,6 +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.runUsvmGeneration import org.utbot.features.FeatureExtractorFactoryImpl import org.utbot.features.FeatureProcessorWithStatesRepetitionFactory import org.utbot.framework.PathSelectorType @@ -125,10 +126,20 @@ object Paths { } @Suppress("unused") -enum class Tool { - UtBot { - @OptIn(ObsoleteCoroutinesApi::class) - @Suppress("EXPERIMENTAL_API_USAGE") +interface Tool { + abstract class UtBotBasedTool : Tool { + abstract fun runGeneration( + project: ProjectToEstimate, + cut: ClassUnderTest, + timeLimit: Long, + fuzzingRatio: Double, + methodNameFilter: String?, + statsForProject: StatsForProject, + compiledTestDir: File, + classFqn: String, + expectedExceptions: ExpectedExceptionsForClass + ) : StatsForClass + override fun run( project: ProjectToEstimate, cut: ClassUnderTest, @@ -142,19 +153,21 @@ enum class Tool { ) = withUtContext(ContextManager.createNewContext(project.classloader)) { val classStats: StatsForClass = try { runGeneration( - project.name, + project, cut, timeLimit, fuzzingRatio, - project.sootClasspathString, - runFromEstimator = true, - expectedExceptions, - methodNameFilter + methodNameFilter, + statsForProject, + compiledTestDir, + classFqn, + expectedExceptions ) } catch (e: CancellationException) { logger.info { "[$classFqn] finished with CancellationException" } return } catch (e: Throwable) { + logger.error(e) { "ISOLATION: $e" } logger.info { "ISOLATION: $e" } logger.info { "continue without compilation" } return @@ -198,8 +211,61 @@ enum class Tool { override fun moveProducedFilesIfNeeded() { // don't do anything } - }, - EvoSuite { + } + + object UtBot : UtBotBasedTool() { + @OptIn(ObsoleteCoroutinesApi::class) + @Suppress("EXPERIMENTAL_API_USAGE") + override fun runGeneration( + project: ProjectToEstimate, + cut: ClassUnderTest, + timeLimit: Long, + fuzzingRatio: Double, + methodNameFilter: String?, + statsForProject: StatsForProject, + compiledTestDir: File, + classFqn: String, + expectedExceptions: ExpectedExceptionsForClass + ): StatsForClass { + return runGeneration( + project.name, + cut, + timeLimit, + fuzzingRatio, + project.sootClasspathString, + runFromEstimator = true, + expectedExceptions, + methodNameFilter + ) + } + } + + object USVM : UtBotBasedTool() { + @OptIn(ObsoleteCoroutinesApi::class) + @Suppress("EXPERIMENTAL_API_USAGE") + override fun runGeneration( + project: ProjectToEstimate, + cut: ClassUnderTest, + timeLimit: Long, + fuzzingRatio: Double, + methodNameFilter: String?, + statsForProject: StatsForProject, + compiledTestDir: File, + classFqn: String, + expectedExceptions: ExpectedExceptionsForClass + ): StatsForClass = runUsvmGeneration( + project.name, + cut, + timeLimit, + fuzzingRatio, + project.sootClasspathString, + runFromEstimator = true, + expectedExceptions, + methodNameFilter + ) + } + + object EvoSuite : Tool { override fun run( project: ProjectToEstimate, cut: ClassUnderTest, @@ -272,7 +338,7 @@ enum class Tool { } }; - abstract fun run( + fun run( project: ProjectToEstimate, cut: ClassUnderTest, timeLimit: Long, @@ -284,7 +350,7 @@ enum class Tool { expectedExceptions: ExpectedExceptionsForClass ) - abstract fun moveProducedFilesIfNeeded() + fun moveProducedFilesIfNeeded() } fun main(args: Array) { @@ -295,7 +361,7 @@ fun main(args: Array) { val tools: List // very special case when you run your project directly from IntellijIDEA omitting command line arguments - if (args.isEmpty() && System.getProperty("os.name")?.run { contains("win", ignoreCase = true) } == true) { + if (args.isEmpty()) { processedClassesThreshold = 9999 //change to change number of classes to run val timeLimit = 20 // increase if you want to debug something val fuzzingRatio = 0.1 // sets fuzzing ratio to total test generation @@ -319,7 +385,8 @@ fun main(args: Array) { // config for SBST 2022 methodFilter = null projectFilter = listOf("fastjson-1.2.50", "guava-26.0", "seata-core-0.5.0", "spoon-core-7.0.0") - tools = listOf(Tool.UtBot) + // TODO usvm-sbft-merge: add if here if we want merge contest it into main + tools = listOf(Tool.USVM) estimatorArgs = arrayOf( classesLists, @@ -339,7 +406,8 @@ fun main(args: Array) { processedClassesThreshold = 9999 methodFilter = null projectFilter = null - tools = listOf(Tool.UtBot) + // TODO usvm-sbft-merge: add if here if we want merge contest it into main + tools = listOf(Tool.USVM) } JdkInfoService.jdkInfoProvider = ContestEstimatorJdkInfoProvider(javaHome) 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 new file mode 100644 index 0000000000..8780e74b9a --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/ContestUsvm.kt @@ -0,0 +1,238 @@ +package org.utbot.contest.usvm + +import kotlinx.coroutines.* +import mu.KotlinLogging +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 +import org.usvm.UMachineOptions +import org.usvm.instrumentation.util.toJcdbSignature +import org.usvm.machine.JcMachine +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.framework.codegen.domain.ProjectType +import org.utbot.framework.codegen.domain.RuntimeExceptionTestsBehaviour +import org.utbot.framework.codegen.domain.junitByVersion +import org.utbot.framework.codegen.generator.CodeGenerator +import org.utbot.framework.codegen.generator.CodeGeneratorParams +import org.utbot.framework.codegen.services.language.CgLanguageAssistant +import org.utbot.framework.plugin.api.* +import org.utbot.framework.plugin.api.util.constructor +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.method +import org.utbot.framework.plugin.services.JdkInfoService +import org.utbot.fuzzer.UtFuzzedExecution +import java.io.File +import java.net.URLClassLoader +import kotlin.math.max + +private val logger = KotlinLogging.logger {} + +@ObsoleteCoroutinesApi +@SuppressWarnings +fun runUsvmGeneration( + project: String, + cut: ClassUnderTest, + timeLimitSec: Long, + fuzzingRatio: Double, + classpathString: String, + runFromEstimator: Boolean, + expectedExceptions: ExpectedExceptionsForClass, + methodNameFilter: String? = null // For debug purposes you can specify method name +): StatsForClass = runBlocking { + 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 + + logger.debug { "-----------------------------------------------------------------------------" } + logger.info( + "Contest.runGeneration: Time budget: $timeBudgetMs ms, Generation timeout=$generationTimeoutMillisWithoutCodegen ms, " + + "classpath=$classpathString, methodNameFilter=$methodNameFilter" + ) + + val classpathFiles = classpathString.split(File.pathSeparator).map { File(it) } + + val jcDbContainer by lazy { + JacoDBContainer( + key = classpathString, + classpath = classpathFiles, + ) { + // TODO usvm-sbft: we may want to tune these JcSettings for contest + useJavaRuntime(JdkInfoService.provide().path.toFile()) + installFeatures(InMemoryHierarchy, Approximations) + loadByteCode(classpathFiles) + } + } + + val runner by lazy { + UTestRunner.initRunner(classpathFiles.map { it.absolutePath }, jcDbContainer.cp) + UTestRunner.runner + } + + val resolver by lazy { JcTestExecutor(jcDbContainer.cp) } + + val instructionIds = mutableMapOf, Long>() + val instructionIdProvider = InstructionIdProvider { methodSignature, instrIndex -> + instructionIds.getOrPut(methodSignature to instrIndex) { instructionIds.size.toLong() } + } + + if (runFromEstimator) { + setOptions() + //will not be executed in real contest + logger.info().measureTime({ "warmup: 1st optional JacoDB initialization (not to be counted in time budget)" }) { + jcDbContainer // force init lazy property + } + logger.info().measureTime({ "warmup: 1st optional executor start (not to be counted in time budget)" }) { + runner.ensureRunnerAlive() + } + } + + //remaining budget + val startTime = System.currentTimeMillis() + 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}") + } + + val statsForClass = StatsForClass(project, cut.fqn) + + val codeGenerator = CodeGenerator( + CodeGeneratorParams( + cut.classId, + projectType = ProjectType.PureJvm, + testFramework = junitByVersion(junitVersion), + staticsMocking = staticsMocking, + forceStaticMocking = forceStaticMocking, + generateWarningsForStaticMocking = false, + cgLanguageAssistant = CgLanguageAssistant.getByCodegenLanguage(CodegenLanguage.defaultItem), + runtimeExceptionTestsBehaviour = RuntimeExceptionTestsBehaviour.PASS, + ) + ) + + logger.info().measureTime({ "class ${cut.fqn}" }, { statsForClass }) { + // TODO usvm-sbft: is it still needed for USVM? + val filteredMethods = logger.info().measureTime({ "preparation class ${cut.clazz}: kotlin reflection :: run" }) { + prepareClass(cut.clazz, methodNameFilter) + } + + logger.info().measureTime({ "preparation: ensure JacoDB is initialized (counted in time budget)" }) { + jcDbContainer // force init lazy property + } + logger.info().measureTime({ "preparation: ensure executor is started (counted in time budget)" }) { + runner.ensureRunnerAlive() + } + + // TODO usvm-sbft: better budget management + val totalBudgetPerMethod = remainingBudgetMillisWithoutCodegen() / filteredMethods.size + val concreteBudgetMsPerMethod = 500L + .coerceIn((totalBudgetPerMethod / 10L).. (totalBudgetPerMethod / 2L)) + + // 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 = totalBudgetPerMethod - concreteBudgetMsPerMethod) + ) + }.use { machine -> + val jcClass = jcDbContainer.cp.findClass(cut.fqn) + + statsForClass.methodsCount = filteredMethods.size + + // nothing to process further + if (filteredMethods.isEmpty()) return@runBlocking statsForClass + + for (method in filteredMethods) { + val jcTypedMethod = jcClass.toType().declaredMethods.firstOrNull { + it.name == method.name && it.method.jcdbSignature == when (method) { + is ConstructorId -> method.constructor.toJcdbSignature() + is MethodId -> method.method.toJcdbSignature() + } + } + if (jcTypedMethod == null) { + logger.error { "Method [$method] not found in jcClass [$jcClass]" } + continue + } + val states = machine.analyze(jcTypedMethod.method) + val jcExecutions = states.mapNotNull { + // TODO usvm-sbft: if we have less than `runner.timeout` budget we should only let resolver run + // for `remainingBudgetMillisWithoutCodegen()` ms, right now last resolver call may exceed budget, + // essentially stealing some time from codegen + if (remainingBudgetMillisWithoutCodegen() > 0) + // TODO usvm-sbft: right now this call fails unless you set: + // - "usvm-jvm-instrumentation-jar" environment variable to something like "/home/ilya/IdeaProjects/usvm/usvm-jvm-instrumentation/build/libs/usvm-jvm-instrumentation-1.0.jar" + // - "usvm-jvm-collectors-jar" environment variable to something like "/home/ilya/IdeaProjects/usvm/usvm-jvm-instrumentation/build/libs/usvm-jvm-instrumentation-collectors.jar" + 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 { + JcToUtExecutionConverter(instructionIdProvider).convert(it) + } + + 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" } + } + } + } + } + } + +// TODO usvm-sbft: codegen, requires proper UtUsvmExecution creation (not just coverage) + +// val testSets = testsByMethod.map { (method, executions) -> +// UtMethodTestSet(method, minimizeExecutions(executions), jimpleBody(method)) +// }.summarizeAll(cut.classfileDir.toPath(), sourceFile = null) +// +// logger.info().measureTime({ "Flushing tests for [${cut.simpleName}] on disk" }) { +// writeTestClass(cut, codeGenerator.generateAsString(testSets)) +// } + + statsForClass +} \ 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 new file mode 100644 index 0000000000..252d08f566 --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/InstructionIdProvider.kt @@ -0,0 +1,8 @@ +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 new file mode 100644 index 0000000000..876dc95af4 --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JacoDBContainer.kt @@ -0,0 +1,42 @@ +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.jacodb +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: do we need `db.classpath(classpath)` or `db.classpathWithApproximations(classpath)` for contest? + val cp = db.classpath(classpath) + 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/JcToUtExecutionConverter.kt b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JcToUtExecutionConverter.kt new file mode 100644 index 0000000000..dee5b56ae7 --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/JcToUtExecutionConverter.kt @@ -0,0 +1,102 @@ +package org.utbot.contest.usvm + +import org.jacodb.api.JcClassOrInterface +import org.jacodb.api.cfg.JcInst +import org.jacodb.api.ext.jcdbSignature +import org.usvm.instrumentation.testcase.api.UTestExecutionExceptionResult +import org.usvm.instrumentation.testcase.api.UTestExecutionFailedResult +import org.usvm.instrumentation.testcase.api.UTestExecutionInitFailedResult +import org.usvm.instrumentation.testcase.api.UTestExecutionResult +import org.usvm.instrumentation.testcase.api.UTestExecutionSuccessResult +import org.usvm.instrumentation.testcase.api.UTestExecutionTimedOutResult +import org.usvm.instrumentation.testcase.descriptor.Descriptor2ValueConverter +import org.usvm.instrumentation.util.enclosingClass +import org.usvm.instrumentation.util.enclosingMethod +import org.utbot.contest.usvm.executor.JcExecution +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.Instruction +import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.UtExecution +import org.utbot.framework.plugin.api.UtExecutionSuccess +import org.utbot.framework.plugin.api.UtVoidModel +import org.utbot.framework.plugin.api.util.jClass +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.instrumentation.instrumentation.execution.constructors.StateBeforeAwareIdGenerator +import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor +import org.utbot.instrumentation.instrumentation.execution.constructors.javaStdLibModelWithCompositeOriginConstructors +import java.util.* + +class JcToUtExecutionConverter( + private val instructionIdProvider: InstructionIdProvider +) { + private val valueConstructor = Descriptor2ValueConverter(utContext.classLoader) + + private val utModelConstructor = UtModelConstructor( + objectToModelCache = IdentityHashMap(), + idGenerator = StateBeforeAwareIdGenerator(allPreExistingModels = emptySet()), + utModelWithCompositeOriginConstructorFinder = { classId -> + javaStdLibModelWithCompositeOriginConstructors[classId.jClass]?.invoke() + } + ) + + fun convert(jcExecution: JcExecution): UtExecution? { + // TODO usvm-sbft: convert everything other than coverage + return UtUsvmExecution( + stateBefore = MissingState, + stateAfter = MissingState, + result = UtExecutionSuccess(UtVoidModel), + coverage = convertCoverage(getTrace(jcExecution.uTestExecutionResult), jcExecution.method.enclosingType.jcClass), + instrumentation = emptyList() + ) +// val coverage = Coverage(convertCoverage()) +// return when (jcExecution.uTestExecutionResult) { +// is UTestExecutionSuccessResult -> { +// +// TODO("usvm-sbft") +// } +// is UTestExecutionExceptionResult -> TODO("usvm-sbft") +// is UTestExecutionInitFailedResult -> { +// val exception = +// valueConstructor.buildObjectFromDescriptor(jcExecution.uTestExecutionResult.cause) as Throwable +// logger.error(exception) { "Concrete executor failed" } +// null +// } +// is UTestExecutionFailedResult -> { +// val exception = +// valueConstructor.buildObjectFromDescriptor(jcExecution.uTestExecutionResult.cause) as Throwable +// if (!jcExecution.uTestExecutionResult.cause.raisedByUserCode) +// logger.error(exception) { "Concrete executor failed" } +// // TODO usvm-sbft +// null +// } +// is UTestExecutionTimedOutResult -> { +// // TODO usvm-sbft +// null +// } +// } + } + + private fun getTrace(executionResult: UTestExecutionResult): List? = when (executionResult) { + is UTestExecutionExceptionResult -> executionResult.trace + is UTestExecutionInitFailedResult -> executionResult.trace + is UTestExecutionSuccessResult -> executionResult.trace + is UTestExecutionFailedResult -> emptyList() + is UTestExecutionTimedOutResult -> emptyList() + } + + private fun convertCoverage(jcCoverage: List?, jcClass: JcClassOrInterface) = Coverage( + coveredInstructions = jcCoverage.orEmpty().map { + val methodSignature = it.enclosingMethod.jcdbSignature + Instruction( + internalName = it.enclosingClass.name.replace('.', '/'), + methodSignature = methodSignature, + lineNumber = it.lineNumber, + id = instructionIdProvider.provideInstructionId(methodSignature, it.location.index) + ) + }, + // TODO usvm-sbft: maybe add cache here + // TODO usvm-sbft: make sure static initializers are included into instructions count + // I assume they are counted as part of `` method + instructionsCount = jcClass.declaredMethods.sumOf { it.instList.size.toLong() } + ) +} \ No newline at end of file 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/UtUsvmExecution.kt new file mode 100644 index 0000000000..a7f469ab4d --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/UtUsvmExecution.kt @@ -0,0 +1,47 @@ +package org.utbot.contest.usvm + +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.framework.plugin.api.EnvironmentModels +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 + +class UtUsvmExecution( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage?, + summary: List? = emptyList(), + testMethodName: String? = null, + displayName: String? = null, + override val instrumentation: List +) : UtExecution( + stateBefore, + stateAfter, + result, + coverage, + summary, + testMethodName, + displayName +), UtExecutionWithInstrumentation { + override fun copy( + stateBefore: EnvironmentModels, + stateAfter: EnvironmentModels, + result: UtExecutionResult, + coverage: Coverage?, + summary: List?, + testMethodName: String?, + displayName: String? + ): UtExecution = UtUsvmExecution( + stateBefore, + stateAfter, + result, + coverage, + summary, + testMethodName, + displayName, + instrumentation + ) +} \ No newline at end of file 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/executor/JcExecution.kt new file mode 100644 index 0000000000..2e940c991f --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/JcExecution.kt @@ -0,0 +1,13 @@ +package org.utbot.contest.usvm.executor + +import org.jacodb.api.JcTypedMethod +import org.usvm.api.JcCoverage +import org.usvm.instrumentation.testcase.UTest +import org.usvm.instrumentation.testcase.api.UTestExecutionResult + +data class JcExecution( + val method: JcTypedMethod, + val uTest: UTest, + val uTestExecutionResult: UTestExecutionResult, + val coverage: 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/executor/JcTestExecutor.kt new file mode 100644 index 0000000000..af7291687e --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/JcTestExecutor.kt @@ -0,0 +1,313 @@ +package org.utbot.contest.usvm.executor + +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 +import org.usvm.api.typeStreamOf +import org.usvm.collection.array.UArrayIndexLValue +import org.usvm.collection.array.length.UArrayLengthLValue +import org.usvm.collection.field.UFieldLValue +import org.usvm.instrumentation.executor.UTestConcreteExecutor +import org.usvm.instrumentation.testcase.UTest +import org.usvm.instrumentation.testcase.api.* +import org.usvm.machine.JcContext +import org.usvm.machine.extractBool +import org.usvm.machine.extractByte +import org.usvm.machine.extractChar +import org.usvm.machine.extractDouble +import org.usvm.machine.extractFloat +import org.usvm.machine.extractInt +import org.usvm.machine.extractLong +import org.usvm.machine.extractShort +import org.usvm.machine.state.JcState +import org.usvm.machine.state.localIdx +import org.usvm.memory.ULValue +import org.usvm.memory.UReadOnlyMemory +import org.usvm.memory.URegisterStackLValue +import org.usvm.model.UModelBase +import org.usvm.types.first +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. + */ +// 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 { + val model = state.models.first() + + val ctx = state.ctx + + val memoryScope = MemoryScope(ctx, model, model, method) + + val uTest = memoryScope.createUTest() + + val execResult = runBlocking { + runner.executeAsync(uTest) + } + + val coverage = resolveCoverage(method, state) + + return JcExecution(method, uTest, execResult, coverage) + } + + @Suppress("UNUSED_PARAMETER") + private fun resolveCoverage(method: JcTypedMethod, state: JcState): JcCoverage { + // TODO: extract coverage + return JcCoverage(emptyMap()) + } + + /** + * An actual class for resolving objects from [UExpr]s. + * + * @param model a model to which compose expressions. + * @param memory a read-only memory to read [ULValue]s from. + */ + private class MemoryScope( + private val ctx: JcContext, + private val model: UModelBase, + private val memory: UReadOnlyMemory, + private val method: JcTypedMethod, + ) { + + private val resolvedCache = mutableMapOf>>() + + fun createUTest(): UTest { + val thisInstance = if (!method.isStatic) { + val ref = URegisterStackLValue(ctx.addressSort, idx = 0) + resolveLValue(ref, method.enclosingType) + } else { + UTestNullExpression(ctx.cp.objectType) to listOf() + } + + val parameters = method.parameters.mapIndexed { idx, param -> + val registerIdx = method.method.localIdx(idx) + val ref = URegisterStackLValue(ctx.typeToSort(param.type), registerIdx) + resolveLValue(ref, param.type) + } + + val initStmts = thisInstance.second + parameters.flatMap { it.second } + val callExpr = if (method.isStatic) { + UTestStaticMethodCall(method.method, parameters.map { it.first }) + } else { + UTestMethodCall(thisInstance.first, method.method, parameters.map { it.first }) + } + return UTest(initStmts, callExpr) + } + + + fun resolveLValue(lvalue: ULValue<*, *>, type: JcType): Pair> = + resolveExpr(memory.read(lvalue), type) + + + fun resolveExpr(expr: UExpr, type: JcType): Pair> = + when (type) { + is JcPrimitiveType -> resolvePrimitive(expr, type) + is JcRefType -> resolveReference(expr.asExpr(ctx.addressSort), type) + else -> error("Unexpected type: $type") + } + + fun resolvePrimitive( + expr: UExpr, type: JcPrimitiveType + ): 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) + else -> error("Unexpected type: ${type.typeName}") + }.let { it to listOf() } + } + + fun resolveReference(heapRef: UHeapRef, type: JcRefType): Pair> { + val ref = evaluateInModel(heapRef) as UConcreteHeapRef + if (ref.address == NULL_ADDRESS) { + return UTestNullExpression(type) to listOf() + } + // to find a type, we need to understand the source of the object + val typeStream = if (ref.address <= INITIAL_INPUT_ADDRESS) { + // input object + model.typeStreamOf(ref) + } else { + // allocated object + memory.typeStreamOf(ref) + }.filterBySupertype(type) + + // We filter allocated object type stream, because it could be stored in the input array, + // which resolved to a wrong type, since we do not build connections between element types + // and array types right now. + // In such cases, we need to resolve this element to null. + + val evaluatedType = typeStream.firstOrNull() ?: return UTestNullExpression(type) to listOf() + + // We check for the type stream emptiness firsly and only then for the resolved cache, + // because even if the object is already resolved, it could be incompatible with the [type], if it + // is an element of an array of the wrong type. + + return resolvedCache.getOrElse(ref.address) { + when (evaluatedType) { + is JcArrayType -> resolveArray(ref, heapRef, evaluatedType) + is JcClassType -> resolveObject(ref, heapRef, evaluatedType) + else -> error("Unexpected type: $type") + } + } + } + + private fun resolveArray( + ref: UConcreteHeapRef, heapRef: UHeapRef, type: JcArrayType + ): Pair> { + val arrayDescriptor = ctx.arrayDescriptorOf(type) + val lengthRef = UArrayLengthLValue(heapRef, arrayDescriptor, ctx.sizeSort) + val resolvedLength = resolveLValue(lengthRef, ctx.cp.int).first as UTestIntExpression + // TODO hack + val length = + if (resolvedLength.value in 0..10_000) { + resolvedLength + } else { + UTestIntExpression(0, ctx.cp.int) + } + + val cellSort = ctx.typeToSort(type.elementType) + + fun resolveElement(idx: Int): Pair> { + val elemRef = UArrayIndexLValue(cellSort, heapRef, ctx.mkBv(idx), arrayDescriptor) + return resolveLValue(elemRef, type.elementType) + } + + //val arrLength = UTestIntExpression(length, ctx.cp.int) + val arrayInstance = UTestCreateArrayExpression(type.elementType, length) + + val arraySetters = buildList { + for (i in 0 until length.value) { + with(resolveElement(i)) { + add(UTestArraySetStatement(arrayInstance, UTestIntExpression(i, ctx.cp.int), first)) + addAll(second) + } + } + } + + resolvedCache[ref.address] = arrayInstance to arraySetters + return arrayInstance to arraySetters + } + + private fun resolveObject( + ref: UConcreteHeapRef, heapRef: UHeapRef, type: JcRefType + ): Pair> { + + if (type.jcClass == ctx.classType.jcClass && ref.address <= INITIAL_STATIC_ADDRESS) { + // Note that non-negative addresses are possible only for the result value. + return resolveAllocatedClass(ref) + } + + if (type.jcClass == ctx.stringType.jcClass && ref.address <= INITIAL_STATIC_ADDRESS) { + // Note that non-negative addresses are possible only for the result value. + return resolveAllocatedString(ref) + } + + val anyEnumAncestor = type.getEnumAncestorOrNull() + if (anyEnumAncestor != null) { + return resolveEnumValue(heapRef, anyEnumAncestor) + } + + + val exprs = mutableListOf() + val instance = UTestAllocateMemoryCall(type.jcClass) + + val fieldSetters = mutableListOf() + resolvedCache[ref.address] = instance to fieldSetters + + exprs.add(instance) + + val fields = + generateSequence(type.jcClass) { it.superClass } + .map { it.toType() } + .flatMap { it.declaredFields } + .filter { !it.isStatic } + + for (field in fields) { + val lvalue = UFieldLValue(ctx.typeToSort(field.fieldType), heapRef, field.field) + val fieldValue = resolveLValue(lvalue, field.fieldType) + val uTestSetFieldStmt = UTestSetFieldStatement(instance, field.field, fieldValue.first) + fieldSetters.addAll(fieldValue.second) + fieldSetters.add(uTestSetFieldStmt) + } + return instance to fieldSetters + } + + private fun resolveEnumValue( + heapRef: UHeapRef, + enumAncestor: JcClassOrInterface + ): Pair> { + with(ctx) { + val ordinalLValue = UFieldLValue(sizeSort, heapRef, enumOrdinalField) + val ordinalFieldValue = resolveLValue(ordinalLValue, cp.int).first as UTestIntExpression + val enumField = enumAncestor.enumValues?.get(ordinalFieldValue.value) + ?: error("Cant find enum field with index ${ordinalFieldValue.value}") + + return UTestGetStaticFieldExpression(enumField) to listOf() + } + } + + private fun resolveAllocatedClass(ref: UConcreteHeapRef): Pair> { + val classTypeField = ctx.classTypeSyntheticField + val classTypeLValue = UFieldLValue(ctx.addressSort, ref, classTypeField) + val classTypeRef = memory.read(classTypeLValue) as? UConcreteHeapRef + ?: error("No type for allocated class") + + val classType = memory.typeStreamOf(classTypeRef).first() + return UTestClassExpression(classType) to listOf() + } + + private fun resolveAllocatedString(ref: UConcreteHeapRef): Pair> { + val valueField = ctx.stringValueField + val strValueLValue = UFieldLValue(ctx.typeToSort(valueField.fieldType), ref, valueField.field) + return resolveLValue(strValueLValue, valueField.fieldType) + } + + /** + * If we resolve state after, [expr] is read from a state memory, so it requires concretization via [model]. + * + * @return a concretized expression. + */ + private fun evaluateInModel(expr: UExpr): UExpr { + return model.eval(expr) + } + + // 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 } + } + +} \ 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 new file mode 100644 index 0000000000..8428cc757a --- /dev/null +++ b/utbot-junit-contest/src/main/kotlin/org/utbot/contest/usvm/executor/UTestRunner.kt @@ -0,0 +1,24 @@ +package org.utbot.contest.usvm.executor + +import org.jacodb.api.JcClasspath +import org.usvm.instrumentation.executor.UTestConcreteExecutor +import org.usvm.instrumentation.instrumentation.JcRuntimeTraceInstrumenterFactory +import org.usvm.instrumentation.util.InstrumentationModuleConstants + +// TODO usvm-sbft-refactoring: copied from `usvm/usvm-jvm/test`, extract this class back to USVM project +object UTestRunner { + + lateinit var runner: UTestConcreteExecutor + + fun isInitialized() = this::runner.isInitialized + + fun initRunner(pathToJars: List, classpath: JcClasspath) { + runner = + UTestConcreteExecutor( + JcRuntimeTraceInstrumenterFactory::class, + pathToJars, + classpath, + InstrumentationModuleConstants.testExecutionTimeout + ) + } +} \ No newline at end of file diff --git a/utbot-testing/build.gradle b/utbot-testing/build.gradle index 716fcb0cd1..a66c022c29 100644 --- a/utbot-testing/build.gradle +++ b/utbot-testing/build.gradle @@ -57,6 +57,7 @@ dependencies { testImplementation group: 'org.mockito', name: 'mockito-inline', version: mockitoInlineVersion testImplementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2Version - implementation group: 'com.github.UnitTestBot.ksmt', name: 'ksmt-core', version: ksmtVersion - implementation group: 'com.github.UnitTestBot.ksmt', name: 'ksmt-z3', version: ksmtVersion + // TODO sbft-usvm-merge: UtBot engine expects `com.github.UnitTestBot.ksmt` here + implementation group: 'io.ksmt', name: 'ksmt-core', version: ksmtVersion + implementation group: 'io.ksmt', name: 'ksmt-z3', version: ksmtVersion } \ No newline at end of file