From dbeaf15317dd2023c69b81d48e187d5a2196714c Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 11 Dec 2023 12:10:05 +0300 Subject: [PATCH 01/26] Add usvm engine option on UI --- .../utbot/framework/codegen/domain/Domain.kt | 24 +++++++++++++++ .../plugin/models/GenerateTestsModel.kt | 2 ++ .../plugin/ui/GenerateTestsDialogWindow.kt | 29 ++++++++++++++++++- .../plugin/settings/CommonSettings.kt | 6 ++++ 4 files changed, 60 insertions(+), 1 deletion(-) 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-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/ui/GenerateTestsDialogWindow.kt b/utbot-intellij/src/main/kotlin/org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt index 760046ef60..1a3c4a7587 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 @@ -903,6 +915,9 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m parametrizedTestSources.isSelected = (settings.parametrizedTestSource == ParametrizedTestSource.PARAMETRIZE && model.projectType == ProjectType.PureJvm) + useExperimentalEngine.isSelected = (settings.symbolicEngineSource == SymbolicEngineSource.Usvm + && model.projectType == ProjectType.PureJvm) + codegenLanguages.item = model.codegenLanguage val installedTestFramework = TestFramework.allItems.singleOrNull { it.isInstalled } @@ -1212,6 +1227,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 +1409,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m } private fun updateControlsEnabledStatus() { - mockStrategies.isEnabled = true + mockStrategies.isEnabled = !useExperimentalEngine.isSelected updateParametrizationEnabled() updateStaticMockEnabled() 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 From 8c4a3eaf951cd3fd46553d0c8da687606cbb8ffe Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 11 Dec 2023 14:53:32 +0300 Subject: [PATCH 02/26] Sens engine type data to engine process via Rd --- .../utbot/framework/plugin/api/TestFlow.kt | 3 ++ .../framework/process/EngineProcessMain.kt | 2 ++ .../generated/EngineProcessModel.Generated.kt | 36 +++++++++++-------- .../generator/UtTestsDialogProcessor.kt | 1 + .../intellij/plugin/process/EngineProcess.kt | 3 ++ .../org/utbot/rd/models/EngineProcessModel.kt | 1 + 6 files changed, 31 insertions(+), 15 deletions(-) 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..8a0c891b80 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,6 +6,7 @@ import kotlinx.coroutines.flow.flattenConcat import kotlinx.coroutines.flow.flowOf import org.utbot.engine.UtBotSymbolicEngine import org.utbot.framework.UtSettings +import org.utbot.framework.codegen.domain.SymbolicEngineSource import org.utbot.framework.process.generated.GenerateParams /** @@ -18,6 +19,7 @@ fun testFlow(block: TestFlow.() -> Unit): UtBotSymbolicEngine.() -> Flow 0) { @@ -41,6 +43,7 @@ class TestFlow internal constructor(block: TestFlow.() -> Unit) { field = maxOf(0, value) } var isSymbolicEngineEnabled = true + var symbolicEngineType = SymbolicEngineSource.UnitTestBot var isFuzzingEnabled = false var fuzzingValue: Double = 0.1 set(value) { 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..4da7b7d221 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,7 @@ 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.testFrameworkByName import org.utbot.framework.codegen.generator.AbstractCodeGenerator import org.utbot.framework.codegen.generator.CodeGeneratorParams @@ -116,6 +117,7 @@ private fun EngineProcessModel.setup(kryoHelper: KryoHelper, watchdog: IdleWatch val generateFlow = testFlow { generationTimeout = params.generationTimeout isSymbolicEngineEnabled = params.isSymbolicEngineEnabled + symbolicEngineType = SymbolicEngineSource.valueOf(params.symbolicEngineType) isFuzzingEnabled = params.isFuzzingEnabled fuzzingValue = params.fuzzingValue } 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/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-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 From e1e7139e03020a9184b70e27ad331ad783893cfc Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 11 Dec 2023 15:28:32 +0300 Subject: [PATCH 03/26] Use appropriate engine in flow --- .../main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt | 10 ++++++++++ .../kotlin/org/utbot/framework/plugin/api/TestFlow.kt | 9 +++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt 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..1fe7404d18 --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -0,0 +1,10 @@ +package org.utbot.engine + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow +import org.utbot.framework.plugin.api.UtResult + +class UsvmSymbolicEngine { + + fun generateWithUsvm(): Flow = emptyFlow() +} \ No newline at end of file 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 8a0c891b80..78bbb4bf9c 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 @@ -4,10 +4,10 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flattenConcat import kotlinx.coroutines.flow.flowOf +import org.utbot.engine.UsvmSymbolicEngine import org.utbot.engine.UtBotSymbolicEngine import org.utbot.framework.UtSettings import org.utbot.framework.codegen.domain.SymbolicEngineSource -import org.utbot.framework.process.generated.GenerateParams /** * Constructs [TestFlow] for customization and creates flow producer. @@ -73,7 +73,12 @@ class TestFlow internal constructor(block: TestFlow.() -> Unit) { ).flattenConcat() } } - isSymbolicEngineEnabled -> engine.traverse() + isSymbolicEngineEnabled -> { + when (symbolicEngineType) { + SymbolicEngineSource.UnitTestBot -> engine.traverse() + SymbolicEngineSource.Usvm -> UsvmSymbolicEngine().generateWithUsvm() + } + } else -> emptyFlow() } } From 3fc587e13bbe44fe808ecae8cd6b2adde5f73365 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov <71839386+IlyaMuravjov@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:19:25 +0300 Subject: [PATCH 04/26] Introduce `UtilMethodProviderPlaceholder` (#2717) --- .../org/utbot/framework/plugin/api/Api.kt | 140 +++++++++++++++--- .../builtin/UtilMethodProviderPlaceholder.kt | 50 +++++++ .../generator/AbstractCodeGenerator.kt | 8 + .../org/utbot/fuzzer/UtFuzzedExecution.kt | 16 +- .../utbot/usvm/converter/UtUsvmExecution.kt | 46 +----- 5 files changed, 201 insertions(+), 59 deletions(-) create mode 100644 utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/UtilMethodProviderPlaceholder.kt 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..0b5493d89a 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, @@ -1369,19 +1445,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/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..4e8d58c39f --- /dev/null +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/domain/builtin/UtilMethodProviderPlaceholder.kt @@ -0,0 +1,50 @@ +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 + +/** + * 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 -> UtAssembleModel( + id = id, + classId = classId, + modelName = modelName, + instantiationCall = instantiationCall.shallowlyFixUtilClassId(actualUtilClassId), + origin = origin, + modificationsChainProvider = { modificationsChain.map { it.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-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-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 From 5d688394938916fc0742347c41f506fff5a7ca1a Mon Sep 17 00:00:00 2001 From: IlyaMuravjov <71839386+IlyaMuravjov@users.noreply.github.com> Date: Wed, 13 Dec 2023 17:23:00 +0300 Subject: [PATCH 05/26] Make `TestCaseGenerator` use `UsvmSymbolicEngine` for entire class (#2716) --- .../org/utbot/engine/UsvmSymbolicEngine.kt | 12 +++- .../framework/plugin/api/TestCaseGenerator.kt | 56 +++++++++++-------- .../utbot/framework/plugin/api/TestFlow.kt | 17 ++---- .../framework/process/EngineProcessMain.kt | 17 ++++-- 4 files changed, 58 insertions(+), 44 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt index 1fe7404d18..1c876b54f9 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -2,9 +2,15 @@ package org.utbot.engine import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow +import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.UtResult -class UsvmSymbolicEngine { - - fun generateWithUsvm(): Flow = emptyFlow() +object UsvmSymbolicEngine { + // TODO implement + fun runUsvmGeneration( + methods: List, + classpath: String, + timeoutMillis: Long + ): Flow> = + emptyFlow() } \ No newline at end of file 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..4e5d30cf14 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,29 @@ open class TestCaseGenerator( val forceMockListener = ForceMockListener.create(this, conflictTriggers) val forceStaticMockListener = ForceStaticMockListener.create(this, conflictTriggers) + suspend fun consumeUtResultFlow(utResultFlow: Flow>) = + utResultFlow.catch { + logger.error(it) { "Error in flow" } + } + .collect { (executableId, 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) { @@ -223,27 +249,7 @@ open class TestCaseGenerator( engineActions.map { engine.apply(it) } engineActions.clear() - generate(engine) - .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" } - } - } - } + consumeUtResultFlow(generate(engine).map { utResult -> method to utResult }) } catch (e: Exception) { logger.error(e) {"Error in engine"} throw e @@ -284,6 +290,8 @@ open class TestCaseGenerator( } logger.debug("test generator global scope lifecycle check ended") } + + consumeUtResultFlow(UsvmSymbolicEngine.runUsvmGeneration(methods, classpathForEngine, usvmTimeoutMillis)) } } 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 78bbb4bf9c..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 @@ -4,10 +4,8 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flattenConcat import kotlinx.coroutines.flow.flowOf -import org.utbot.engine.UsvmSymbolicEngine import org.utbot.engine.UtBotSymbolicEngine import org.utbot.framework.UtSettings -import org.utbot.framework.codegen.domain.SymbolicEngineSource /** * Constructs [TestFlow] for customization and creates flow producer. @@ -18,8 +16,7 @@ fun testFlow(block: TestFlow.() -> Unit): UtBotSymbolicEngine.() -> Flow 0) { @@ -42,8 +39,7 @@ class TestFlow internal constructor(block: TestFlow.() -> Unit) { set(value) { field = maxOf(0, value) } - var isSymbolicEngineEnabled = true - var symbolicEngineType = SymbolicEngineSource.UnitTestBot + var isUtBotSymbolicEngineEnabled = true var isFuzzingEnabled = false var fuzzingValue: Double = 0.1 set(value) { @@ -64,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( @@ -73,12 +69,7 @@ class TestFlow internal constructor(block: TestFlow.() -> Unit) { ).flattenConcat() } } - isSymbolicEngineEnabled -> { - when (symbolicEngineType) { - SymbolicEngineSource.UnitTestBot -> engine.traverse() - SymbolicEngineSource.Usvm -> UsvmSymbolicEngine().generateWithUsvm() - } - } + 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 4da7b7d221..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 @@ -13,6 +13,7 @@ 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 @@ -114,10 +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 - symbolicEngineType = SymbolicEngineSource.valueOf(params.symbolicEngineType) + generationTimeout = utBotTimeout + isUtBotSymbolicEngineEnabled = params.isSymbolicEngineEnabled && symbolicEngineSource == UnitTestBot isFuzzingEnabled = params.isFuzzingEnabled fuzzingValue = params.fuzzingValue } @@ -130,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() } From 24d1003a96d88f2d8ca06a69151e3ffb43a9fa46 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Mon, 18 Dec 2023 14:06:47 +0300 Subject: [PATCH 06/26] Initial implementation of usvm tests generation in UtBot flow --- utbot-framework/build.gradle | 2 + .../org/utbot/engine/UsvmSymbolicEngine.kt | 211 +++++++++++++++++- .../builtin/UtilMethodProviderPlaceholder.kt | 1 + .../framework/plugin/api/TestCaseGenerator.kt | 10 +- utbot-junit-contest/build.gradle | 10 - .../org/utbot/contest/usvm/ContestUsvm.kt | 88 ++------ utbot-usvm/build.gradle.kts | 16 +- .../converter/JcToUtExecutionConverter.kt | 11 + .../org/utbot/usvm/jacodb/JacoDbUtils.kt | 14 ++ .../org/utbot/usvm/jc/JcTestExecutor.kt | 72 ++++-- .../main/kotlin/org/utbot/usvm/jc/JcUtils.kt | 20 ++ .../org/utbot/usvm/machine/UsvmAnalyzer.kt | 46 ++++ 12 files changed, 390 insertions(+), 111 deletions(-) create mode 100644 utbot-usvm/src/main/kotlin/org/utbot/usvm/jacodb/JacoDbUtils.kt create mode 100644 utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcUtils.kt create mode 100644 utbot-usvm/src/main/kotlin/org/utbot/usvm/machine/UsvmAnalyzer.kt 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 index 1c876b54f9..216bd0e650 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -1,16 +1,219 @@ package org.utbot.engine import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flow +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.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.utbot.common.utBotTempDirectory +import org.utbot.framework.UtSettings +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.UtSymbolicExecution +import org.utbot.framework.plugin.api.util.utContext +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.toExecutableId +import org.utbot.usvm.jc.JcContainer +import org.utbot.usvm.jc.JcExecution +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.util.concurrent.CancellationException +import kotlin.time.Duration.Companion.milliseconds object UsvmSymbolicEngine { - // TODO implement + + private val logger = KotlinLogging.logger {} + fun runUsvmGeneration( methods: List, classpath: String, + concreteExecutionContext: ConcreteExecutionContext, timeoutMillis: Long - ): Flow> = - emptyFlow() + ): Flow> = flow { + var analysisResult: AnalysisResult? = null + + val classpathFiles = classpath.split(File.pathSeparator).map { File(it) } + val jcContainer = createJcContainer(classpathFiles) + + 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 jcExecutionConstruction = constructJcExecution(jcMachine, state, jcContainer) + + val jcExecution = jcExecutionConstruction.jcExecution + val executableId = jcExecution.method.method.toExecutableId(jcContainer.cp) + + val executionConverter = JcToUtExecutionConverter( + jcExecution = jcExecution, + jcClasspath = jcContainer.cp, + idGenerator = ReferencePreservingIntIdGenerator(), + instructionIdProvider = SimpleInstructionIdProvider(), + utilMethodProvider = UtilMethodProviderPlaceholder, + ) + + val utResult = if (jcExecutionConstruction.useUsvmExecutor) { + executionConverter.convert() + } else { + val initialState = executionConverter.convertInitialStateOnly() + val concreteExecutor = ConcreteExecutor(concreteExecutionContext.instrumentationFactory, classpath) + .apply { this.classLoader = utContext.classLoader } + + runStandardConcreteExecution(concreteExecutor, executableId, initialState) + } + + utResult?.let { + analysisResult = AnalysisResult(executableId, it) + } + } + } + + analysisResult?.let { + emit(it.executableId to it.utResult) + } + } + + private fun constructJcExecution( + jcMachine: JcMachine, + state: JcState, + jcContainer: JcContainer, + ): JcExecutionConstruction { + val executor = JcTestExecutor(jcContainer.cp, jcContainer.runner) + + val realJcExecution = runCatching { + executor.execute( + method = state.entrypoint.typedMethod, + state = state, + stringConstants = jcMachine.stringConstants, + classConstants = jcMachine.classConstants, + allowSymbolicResult = false + ) + }.getOrElse { null } + + realJcExecution?.let { + return JcExecutionConstruction( + jcExecution = it, + useUsvmExecutor = true, + ) + } + + val jcExecutionWithUTest = 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()), + ) + + return JcExecutionConstruction( + jcExecution = jcExecutionWithUTest, + useUsvmExecutor = false, + ) + } + + private fun runStandardConcreteExecution( + concreteExecutor: ConcreteExecutor, + executableId: ExecutableId, + initialState: UtExecutionInitialState, + ): UtResult? { + return try { + val concreteExecutionResult = runBlocking { + concreteExecutor.executeConcretely( + executableId, + initialState.stateBefore, + initialState.instrumentations, + UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis, + ) + } + + UtSymbolicExecution( + initialState.stateBefore, + concreteExecutionResult.stateAfter, + concreteExecutionResult.result, + concreteExecutionResult.newInstrumentation ?: initialState.instrumentations, + mutableListOf(), + listOf(), + concreteExecutionResult.coverage + ) + } 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(), + ) { + installFeatures(InMemoryHierarchy, Approximations, ClassScorer(TypeScorer, ::scoreClassNode)) + loadByteCode(classpathFiles) + } + + data class JcExecutionConstruction( + val jcExecution: JcExecution, + val useUsvmExecutor: Boolean, + ) + + data class AnalysisResult( + val executableId: ExecutableId, + val utResult: UtResult, + ) } \ No newline at end of file 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 index 4e8d58c39f..2f70590b83 100644 --- 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 @@ -7,6 +7,7 @@ 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, 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 4e5d30cf14..2eace635f5 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 @@ -291,7 +291,15 @@ open class TestCaseGenerator( logger.debug("test generator global scope lifecycle check ended") } - consumeUtResultFlow(UsvmSymbolicEngine.runUsvmGeneration(methods, classpathForEngine, usvmTimeoutMillis)) + // retrieves from cache ConcreteExecutor(concreteExecutionContext.instrumentationFactory, classpathForEngine) + consumeUtResultFlow( + UsvmSymbolicEngine.runUsvmGeneration( + methods = methods, + classpath = classpathForEngine, + concreteExecutionContext = concreteExecutionContext, + timeoutMillis = usvmTimeoutMillis, + ) + ) } } 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..a126e700ca 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,24 @@ 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.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 +36,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 @@ -52,6 +51,9 @@ 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.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 @@ -181,7 +183,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" } @@ -262,53 +265,6 @@ fun createJcContainer( 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-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..9b316042de 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,6 +36,7 @@ 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 @@ -54,6 +55,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 +84,11 @@ class JcToUtExecutionConverter( .getOrNull() } ?: error("Failed to construct UtExecution for all uTestExecutionResultWrappers on ${jcExecution.method.method}") + fun convertInitialStateOnly(): UtExecutionInitialState = UtExecutionInitialState( + stateBefore = constructStateBeforeFromUTest(), + instrumentations = uTestProcessResult.instrumentation + ) + private fun convert(uTestResultWrapper: UTestResultWrapper): UtExecution? { val coverage = convertCoverage(getTrace(uTestResultWrapper), jcExecution.method.enclosingType.jcClass) 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/JcTestExecutor.kt b/utbot-usvm/src/main/kotlin/org/utbot/usvm/jc/JcTestExecutor.kt index 8ff326c6dd..dc9909e118 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,21 +63,17 @@ 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 }) + val resolvedMethodMocks = mocker.symbols + .entries + .groupBy({ model.eval(it.key) }, { it.value }) .mapValues { it.value.flatten() } - val memoryScope = MemoryScope(ctx, model, model, stringConstants, classConstants, resolvedMethodMocks, method) - - val uTest = memoryScope.createUTest() + val uTest = createUTest(method, state, stringConstants, classConstants) val concreteResult = runCatching { runBlocking { @@ -88,16 +84,27 @@ class JcTestExecutor( .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 resultScope = MemoryScope( + state.ctx, + model, + state.memory, + stringConstants, + classConstants, + resolvedMethodMocks, + method + ) + val resultExpr = resultScope.resolveExpr(methodResult.value, method.returnType) + val resultInitializer = resultScope.decoderApi.initializerInstructions() + UTestSymbolicSuccessResult(resultInitializer, resultExpr) + } + + JcMethodResult.NoCall -> null } - JcMethodResult.NoCall -> null - } + } else null } val testExecutionResult = concreteResult?.uTestExecutionResult @@ -113,9 +120,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 +129,7 @@ class JcTestExecutor( method = method, uTest = uTest, uTestExecutionResultWrappers = sequence { - yield(preferableResult) + preferableResult?.let { yield(it) } if (preferableResult !== symbolicResult) symbolicResult?.let { yield(it) } }, @@ -132,6 +137,29 @@ class JcTestExecutor( ) } + fun createUTest( + method: JcTypedMethod, + state: JcState, + stringConstants: Map, + classConstants: Map, + ): UTest { + 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.createUTest() + } + @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 From 4ad8c4a4ca2992be7b4c1adf3e962244362c2351 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Tue, 19 Dec 2023 23:08:57 +0300 Subject: [PATCH 07/26] Apply some improvements --- .../framework/plugin/api/mapper/Utils.kt | 2 +- .../org/utbot/engine/UsvmSymbolicEngine.kt | 163 +++-- .../org/utbot/engine/UtBotSymbolicEngine.kt | 2 +- .../framework/plugin/api/TestCaseGenerator.kt | 57 +- .../org/utbot/runtime/utils/java/UtUtils.java | 609 ++++++++++++++++++ .../org/utbot/contest/usvm/ContestUsvm.kt | 1 + .../converter/JcToUtExecutionConverter.kt | 33 +- .../kotlin/org/utbot/usvm/jc/JcContainer.kt | 2 + 8 files changed, 744 insertions(+), 125 deletions(-) create mode 100644 utbot-instrumentation/src/main/java/org/utbot/runtime/utils/java/UtUtils.java 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..3e3a41c71a 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 @@ -72,4 +72,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/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt index 216bd0e650..acfc7fea60 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -1,7 +1,5 @@ package org.utbot.engine -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runBlocking import mu.KotlinLogging import org.jacodb.approximation.Approximations @@ -24,10 +22,10 @@ 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.UtConcreteExecutionProcessedFailure 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.UtSymbolicExecution import org.utbot.framework.plugin.api.util.utContext import org.utbot.framework.plugin.services.JdkInfoService import org.utbot.instrumentation.ConcreteExecutor @@ -36,6 +34,7 @@ import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrument 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 @@ -56,73 +55,75 @@ object UsvmSymbolicEngine { classpath: String, concreteExecutionContext: ConcreteExecutionContext, timeoutMillis: Long - ): Flow> = flow { - var analysisResult: AnalysisResult? = null + ): List> { + val collectedExecutions = mutableListOf>() val classpathFiles = classpath.split(File.pathSeparator).map { File(it) } - val jcContainer = createJcContainer(classpathFiles) - val jcMethods = methods - .mapNotNull { methodId -> jcContainer.cp.findMethodOrNull(methodId).also { - if (it == null) { - logger.error { "Method [$methodId] not found in jcClasspath [${jcContainer.cp}]" } + 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 jcExecutionConstruction = constructJcExecution(jcMachine, state, jcContainer) - - val jcExecution = jcExecutionConstruction.jcExecution - val executableId = jcExecution.method.method.toExecutableId(jcContainer.cp) - - val executionConverter = JcToUtExecutionConverter( - jcExecution = jcExecution, - jcClasspath = jcContainer.cp, - idGenerator = ReferencePreservingIntIdGenerator(), - instructionIdProvider = SimpleInstructionIdProvider(), - utilMethodProvider = UtilMethodProviderPlaceholder, + JcMachine( + cp = jcContainer.cp, + options = UMachineOptions( + timeout = timeoutMillis.milliseconds, + pathSelectionStrategies = listOf(PathSelectionStrategy.CLOSEST_TO_UNCOVERED_RANDOM), + pathSelectorFairnessStrategy = PathSelectorFairnessStrategy.COMPLETELY_FAIR, + solverType = SolverType.Z3, ) - - val utResult = if (jcExecutionConstruction.useUsvmExecutor) { - executionConverter.convert() - } else { - val initialState = executionConverter.convertInitialStateOnly() - val concreteExecutor = ConcreteExecutor(concreteExecutionContext.instrumentationFactory, classpath) - .apply { this.classLoader = utContext.classLoader } - - runStandardConcreteExecution(concreteExecutor, executableId, initialState) - } - - utResult?.let { - analysisResult = AnalysisResult(executableId, it) + ).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, + ) + + val 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) + } + + utResult?.let { + collectedExecutions.add(executableId to it) + } } } } - analysisResult?.let { - emit(it.executableId to it.utResult) - } + return collectedExecutions } private fun constructJcExecution( jcMachine: JcMachine, state: JcState, jcContainer: JcContainer, - ): JcExecutionConstruction { + ): JcExecution { val executor = JcTestExecutor(jcContainer.cp, jcContainer.runner) val realJcExecution = runCatching { @@ -133,16 +134,16 @@ object UsvmSymbolicEngine { classConstants = jcMachine.classConstants, allowSymbolicResult = false ) - }.getOrElse { null } + }.getOrElse { e -> + logger.warn(e) { "executor.execute(${state.entrypoint}) failed" } + null + } realJcExecution?.let { - return JcExecutionConstruction( - jcExecution = it, - useUsvmExecutor = true, - ) + return it } - val jcExecutionWithUTest = JcExecution( + return JcExecution( method = state.entrypoint.typedMethod, uTest = executor.createUTest( method = state.entrypoint.typedMethod, @@ -153,11 +154,6 @@ object UsvmSymbolicEngine { uTestExecutionResultWrappers = emptySequence(), coverage = JcCoverage(emptyMap()), ) - - return JcExecutionConstruction( - jcExecution = jcExecutionWithUTest, - useUsvmExecutor = false, - ) } private fun runStandardConcreteExecution( @@ -175,15 +171,17 @@ object UsvmSymbolicEngine { ) } - UtSymbolicExecution( - initialState.stateBefore, - concreteExecutionResult.stateAfter, - concreteExecutionResult.result, - concreteExecutionResult.newInstrumentation ?: initialState.instrumentations, - mutableListOf(), - listOf(), - concreteExecutionResult.coverage - ) + 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 @@ -203,17 +201,12 @@ object UsvmSymbolicEngine { classpath = classpathFiles, javaHome = JdkInfoService.provide().path.toFile(), ) { - installFeatures(InMemoryHierarchy, Approximations, ClassScorer(TypeScorer, ::scoreClassNode)) + installFeatures( + InMemoryHierarchy, + Approximations, + // ApproximationPaths(JcJars.approximationsJar, ...) + ClassScorer(TypeScorer, ::scoreClassNode) + ) loadByteCode(classpathFiles) } - - data class JcExecutionConstruction( - val jcExecution: JcExecution, - val useUsvmExecutor: Boolean, - ) - - data class AnalysisResult( - val executableId: ExecutableId, - val utResult: UtResult, - ) } \ 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/plugin/api/TestCaseGenerator.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/plugin/api/TestCaseGenerator.kt index 2eace635f5..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 @@ -203,29 +203,24 @@ open class TestCaseGenerator( val forceMockListener = ForceMockListener.create(this, conflictTriggers) val forceStaticMockListener = ForceStaticMockListener.create(this, conflictTriggers) - suspend fun consumeUtResultFlow(utResultFlow: Flow>) = - utResultFlow.catch { - logger.error(it) { "Error in flow" } - } - .collect { (executableId, 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" } - } + 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) { @@ -249,9 +244,15 @@ open class TestCaseGenerator( engineActions.map { engine.apply(it) } engineActions.clear() - consumeUtResultFlow(generate(engine).map { utResult -> method to utResult }) + generate(engine) + .catch { + logger.error(it) { "Error in flow" } + } + .collect { utResult -> + consumeUtResult(method, utResult) + } } catch (e: Exception) { - logger.error(e) {"Error in engine"} + logger.error(e) { "Error in engine" } throw e } } @@ -291,15 +292,9 @@ open class TestCaseGenerator( logger.debug("test generator global scope lifecycle check ended") } - // retrieves from cache ConcreteExecutor(concreteExecutionContext.instrumentationFactory, classpathForEngine) - consumeUtResultFlow( - UsvmSymbolicEngine.runUsvmGeneration( - methods = methods, - classpath = classpathForEngine, - concreteExecutionContext = concreteExecutionContext, - timeoutMillis = usvmTimeoutMillis, - ) - ) + UsvmSymbolicEngine + .runUsvmGeneration(methods, classpathForEngine, concreteExecutionContext, usvmTimeoutMillis) + .forEach { (executableId, usvmExecution) -> consumeUtResult(executableId, usvmExecution) } } } diff --git a/utbot-instrumentation/src/main/java/org/utbot/runtime/utils/java/UtUtils.java b/utbot-instrumentation/src/main/java/org/utbot/runtime/utils/java/UtUtils.java new file mode 100644 index 0000000000..dd99abdfc6 --- /dev/null +++ b/utbot-instrumentation/src/main/java/org/utbot/runtime/utils/java/UtUtils.java @@ -0,0 +1,609 @@ +package org.utbot.runtime.utils.java; + +/** + * This is a regular UtUtils class (without mock framework usage) + * UtUtils class version: 2.1 + */ +public final class UtUtils { + ///region Util classes + + /** + * This class represents the {@code type} and {@code value} of a value captured by lambda. + * Captured values are represented as arguments of a synthetic method that lambda is compiled into, + * hence the name of the class. + */ + public static class CapturedArgument { + private Class type; + private Object value; + + public CapturedArgument(Class type, Object value) { + this.type = type; + this.value = value; + } + } + ///endregion + + ///region Util methods + + public static Object getUnsafeInstance() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { + java.lang.reflect.Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe"); + f.setAccessible(true); + return f.get(null); + } + + public static Object createInstance(String className) throws Exception { + Class clazz = Class.forName(className); + return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class.class) + .invoke(getUnsafeInstance(), clazz); + } + + public static Object[] createArray(String className, int length, Object... values) throws ClassNotFoundException { + Object array = java.lang.reflect.Array.newInstance(Class.forName(className), length); + + for (int i = 0; i < values.length; i++) { + java.lang.reflect.Array.set(array, i, values[i]); + } + + return (Object[]) array; + } + + public static void setField(Object object, String fieldClassName, String fieldName, Object fieldValue) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, java.lang.reflect.InvocationTargetException { + Class clazz = Class.forName(fieldClassName); + java.lang.reflect.Field field = clazz.getDeclaredField(fieldName); + + java.lang.reflect.Field modifiersField; + + java.lang.reflect.Method methodForGetDeclaredFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + methodForGetDeclaredFields.setAccessible(true); + java.lang.reflect.Field[] allFieldsFromFieldClass = (java.lang.reflect.Field[]) methodForGetDeclaredFields.invoke(java.lang.reflect.Field.class, false); + modifiersField = java.util.Arrays.stream(allFieldsFromFieldClass).filter(field1 -> field1.getName().equals("modifiers")).findFirst().get(); + + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + field.setAccessible(true); + field.set(object, fieldValue); + } + + public static void setStaticField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { + java.lang.reflect.Field field; + + try { + do { + try { + field = clazz.getDeclaredField(fieldName); + } catch (Exception e) { + clazz = clazz.getSuperclass(); + field = null; + } + } while (field == null); + + java.lang.reflect.Field modifiersField; + + java.lang.reflect.Method methodForGetDeclaredFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + methodForGetDeclaredFields.setAccessible(true); + java.lang.reflect.Field[] allFieldsFromFieldClass = (java.lang.reflect.Field[]) methodForGetDeclaredFields.invoke(java.lang.reflect.Field.class, false); + modifiersField = java.util.Arrays.stream(allFieldsFromFieldClass).filter(field1 -> field1.getName().equals("modifiers")).findFirst().get(); + + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + field.setAccessible(true); + field.set(null, fieldValue); + } catch (java.lang.reflect.InvocationTargetException e) { + e.printStackTrace(); + } catch (NoSuchMethodException e2) { + e2.printStackTrace(); + } + } + + public static Object getFieldValue(Object obj, String fieldClassName, String fieldName) throws ClassNotFoundException, NoSuchMethodException, java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchFieldException { + Class clazz = Class.forName(fieldClassName); + java.lang.reflect.Field field = clazz.getDeclaredField(fieldName); + + field.setAccessible(true); + + java.lang.reflect.Field modifiersField; + + java.lang.reflect.Method methodForGetDeclaredFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + methodForGetDeclaredFields.setAccessible(true); + java.lang.reflect.Field[] allFieldsFromFieldClass = (java.lang.reflect.Field[]) methodForGetDeclaredFields.invoke(java.lang.reflect.Field.class, false); + modifiersField = java.util.Arrays.stream(allFieldsFromFieldClass).filter(field1 -> field1.getName().equals("modifiers")).findFirst().get(); + + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + return field.get(obj); + } + + public static Object getStaticFieldValue(Class clazz, String fieldName) throws IllegalAccessException, NoSuchFieldException { + java.lang.reflect.Field field; + Class originClass = clazz; + do { + try { + field = clazz.getDeclaredField(fieldName); + field.setAccessible(true); + + java.lang.reflect.Field modifiersField; + + java.lang.reflect.Method methodForGetDeclaredFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + methodForGetDeclaredFields.setAccessible(true); + java.lang.reflect.Field[] allFieldsFromFieldClass952853319934400 = (java.lang.reflect.Field[]) methodForGetDeclaredFields.invoke(java.lang.reflect.Field.class, false); + modifiersField = java.util.Arrays.stream(allFieldsFromFieldClass952853319934400).filter(field1 -> field1.getName().equals("modifiers")).findFirst().get(); + + modifiersField.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); + + return field.get(null); + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } catch (NoSuchMethodException e2) { + e2.printStackTrace(); + } catch (java.lang.reflect.InvocationTargetException e3) { + e3.printStackTrace(); + } + } while (clazz != null); + + throw new NoSuchFieldException("Field '" + fieldName + "' not found on class " + originClass); + } + + public static Object getEnumConstantByName(Class enumClass, String name) throws IllegalAccessException { + java.lang.reflect.Field[] fields = enumClass.getDeclaredFields(); + for (java.lang.reflect.Field field : fields) { + String fieldName = field.getName(); + if (field.isEnumConstant() && fieldName.equals(name)) { + field.setAccessible(true); + + return field.get(null); + } + } + + return null; + } + + static class FieldsPair { + final Object o1; + final Object o2; + + public FieldsPair(Object o1, Object o2) { + this.o1 = o1; + this.o2 = o2; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FieldsPair that = (FieldsPair) o; + return java.util.Objects.equals(o1, that.o1) && java.util.Objects.equals(o2, that.o2); + } + + @Override + public int hashCode() { + return java.util.Objects.hash(o1, o2); + } + } + + public static boolean deepEquals(Object o1, Object o2) { + return deepEquals(o1, o2, new java.util.HashSet<>()); + } + + private static boolean deepEquals(Object o1, Object o2, java.util.Set visited) { + visited.add(new FieldsPair(o1, o2)); + + if (o1 == o2) { + return true; + } + + if (o1 == null || o2 == null) { + return false; + } + + if (o1 instanceof Iterable) { + if (!(o2 instanceof Iterable)) { + return false; + } + + return iterablesDeepEquals((Iterable) o1, (Iterable) o2, visited); + } + + if (o2 instanceof Iterable) { + return false; + } + + if (o1 instanceof java.util.stream.BaseStream) { + if (!(o2 instanceof java.util.stream.BaseStream)) { + return false; + } + + return streamsDeepEquals((java.util.stream.BaseStream) o1, (java.util.stream.BaseStream) o2, visited); + } + + if (o2 instanceof java.util.stream.BaseStream) { + return false; + } + + if (o1 instanceof java.util.Map) { + if (!(o2 instanceof java.util.Map)) { + return false; + } + + return mapsDeepEquals((java.util.Map) o1, (java.util.Map) o2, visited); + } + + if (o2 instanceof java.util.Map) { + return false; + } + + Class firstClass = o1.getClass(); + if (firstClass.isArray()) { + if (!o2.getClass().isArray()) { + return false; + } + + // Primitive arrays should not appear here + return arraysDeepEquals(o1, o2, visited); + } + + // common classes + + // check if class has custom equals method (including wrappers and strings) + // It is very important to check it here but not earlier because iterables and maps also have custom equals + // based on elements equals + if (hasCustomEquals(firstClass)) { + return o1.equals(o2); + } + + // common classes without custom equals, use comparison by fields + final java.util.List fields = new java.util.ArrayList<>(); + while (firstClass != Object.class) { + fields.addAll(java.util.Arrays.asList(firstClass.getDeclaredFields())); + // Interface should not appear here + firstClass = firstClass.getSuperclass(); + } + + for (java.lang.reflect.Field field : fields) { + field.setAccessible(true); + try { + final Object field1 = field.get(o1); + final Object field2 = field.get(o2); + if (!visited.contains(new FieldsPair(field1, field2)) && !deepEquals(field1, field2, visited)) { + return false; + } + } catch (IllegalArgumentException e) { + return false; + } catch (IllegalAccessException e) { + // should never occur because field was set accessible + return false; + } + } + + return true; + } + + public static boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set visited) { + final int length = java.lang.reflect.Array.getLength(arr1); + if (length != java.lang.reflect.Array.getLength(arr2)) { + return false; + } + + for (int i = 0; i < length; i++) { + if (!deepEquals(java.lang.reflect.Array.get(arr1, i), java.lang.reflect.Array.get(arr2, i), visited)) { + return false; + } + } + + return true; + } + + public static boolean iterablesDeepEquals(Iterable i1, Iterable i2, java.util.Set visited) { + final java.util.Iterator firstIterator = i1.iterator(); + final java.util.Iterator secondIterator = i2.iterator(); + while (firstIterator.hasNext() && secondIterator.hasNext()) { + if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { + return false; + } + } + + if (firstIterator.hasNext()) { + return false; + } + + return !secondIterator.hasNext(); + } + + public static boolean streamsDeepEquals( + java.util.stream.BaseStream s1, + java.util.stream.BaseStream s2, + java.util.Set visited + ) { + final java.util.Iterator firstIterator = s1.iterator(); + final java.util.Iterator secondIterator = s2.iterator(); + while (firstIterator.hasNext() && secondIterator.hasNext()) { + if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { + return false; + } + } + + if (firstIterator.hasNext()) { + return false; + } + + return !secondIterator.hasNext(); + } + + public static boolean mapsDeepEquals( + java.util.Map m1, + java.util.Map m2, + java.util.Set visited + ) { + final java.util.Iterator> firstIterator = m1.entrySet().iterator(); + final java.util.Iterator> secondIterator = m2.entrySet().iterator(); + while (firstIterator.hasNext() && secondIterator.hasNext()) { + final java.util.Map.Entry firstEntry = firstIterator.next(); + final java.util.Map.Entry secondEntry = secondIterator.next(); + + if (!deepEquals(firstEntry.getKey(), secondEntry.getKey(), visited)) { + return false; + } + + if (!deepEquals(firstEntry.getValue(), secondEntry.getValue(), visited)) { + return false; + } + } + + if (firstIterator.hasNext()) { + return false; + } + + return !secondIterator.hasNext(); + } + + public static boolean hasCustomEquals(Class clazz) { + while (!Object.class.equals(clazz)) { + try { + clazz.getDeclaredMethod("equals", Object.class); + return true; + } catch (Exception e) { + // Interface should not appear here + clazz = clazz.getSuperclass(); + } + } + + return false; + } + + public static int getArrayLength(Object arr) { + return java.lang.reflect.Array.getLength(arr); + } + + public static void consumeBaseStream(java.util.stream.BaseStream stream) { + stream.iterator().forEachRemaining(value -> { + }); + } + + /** + * @param samType a class representing a functional interface. + * @param declaringClass a class where the lambda is declared. + * @param lambdaName a name of the synthetic method that represents a lambda. + * @param capturedArguments a vararg containing {@link CapturedArgument} instances representing + * values that the given lambda has captured. + * @return an {@link Object} that represents an instance of the given functional interface {@code samType} + * and implements its single abstract method with the behavior of the given lambda. + */ + public static Object buildStaticLambda( + Class samType, + Class declaringClass, + String lambdaName, + CapturedArgument... capturedArguments + ) throws Throwable { + // Create lookup for class where the lambda is declared in. + java.lang.invoke.MethodHandles.Lookup caller = getLookupIn(declaringClass); + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + java.lang.reflect.Method singleAbstractMethod = getSingleAbstractMethod(samType); + String invokedName = singleAbstractMethod.getName(); + // Method type of single abstract method of the target functional interface. + java.lang.invoke.MethodType samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.getReturnType(), singleAbstractMethod.getParameterTypes()); + + java.lang.reflect.Method lambdaMethod = getLambdaMethod(declaringClass, lambdaName); + lambdaMethod.setAccessible(true); + java.lang.invoke.MethodType lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), lambdaMethod.getParameterTypes()); + java.lang.invoke.MethodHandle lambdaMethodHandle = caller.findStatic(declaringClass, lambdaName, lambdaMethodType); + + Class[] capturedArgumentTypes = getLambdaCapturedArgumentTypes(capturedArguments); + java.lang.invoke.MethodType invokedType = java.lang.invoke.MethodType.methodType(samType, capturedArgumentTypes); + java.lang.invoke.MethodType instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes); + + // Create a CallSite for the given lambda. + java.lang.invoke.CallSite site = java.lang.invoke.LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType); + + Object[] capturedValues = getLambdaCapturedArgumentValues(capturedArguments); + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + java.lang.invoke.MethodHandle handle = site.getTarget(); + return handle.invokeWithArguments(capturedValues); + } + + /** + * @param samType a class representing a functional interface. + * @param declaringClass a class where the lambda is declared. + * @param lambdaName a name of the synthetic method that represents a lambda. + * @param capturedReceiver an object of {@code declaringClass} that is captured by the lambda. + * When the synthetic lambda method is not static, it means that the lambda captures an instance + * of the class it is declared in. + * @param capturedArguments a vararg containing {@link CapturedArgument} instances representing + * values that the given lambda has captured. + * @return an {@link Object} that represents an instance of the given functional interface {@code samType} + * and implements its single abstract method with the behavior of the given lambda. + */ + public static Object buildLambda( + Class samType, + Class declaringClass, + String lambdaName, + Object capturedReceiver, + CapturedArgument... capturedArguments + ) throws Throwable { + // Create lookup for class where the lambda is declared in. + java.lang.invoke.MethodHandles.Lookup caller = getLookupIn(declaringClass); + + // Obtain the single abstract method of a functional interface whose instance we are building. + // For example, for `java.util.function.Predicate` it will be method `test`. + java.lang.reflect.Method singleAbstractMethod = getSingleAbstractMethod(samType); + String invokedName = singleAbstractMethod.getName(); + // Method type of single abstract method of the target functional interface. + java.lang.invoke.MethodType samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.getReturnType(), singleAbstractMethod.getParameterTypes()); + + java.lang.reflect.Method lambdaMethod = getLambdaMethod(declaringClass, lambdaName); + lambdaMethod.setAccessible(true); + java.lang.invoke.MethodType lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), lambdaMethod.getParameterTypes()); + java.lang.invoke.MethodHandle lambdaMethodHandle = caller.findVirtual(declaringClass, lambdaName, lambdaMethodType); + + Class[] capturedArgumentTypes = getLambdaCapturedArgumentTypes(capturedArguments); + java.lang.invoke.MethodType invokedType = java.lang.invoke.MethodType.methodType(samType, capturedReceiver.getClass(), capturedArgumentTypes); + java.lang.invoke.MethodType instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes); + + // Create a CallSite for the given lambda. + java.lang.invoke.CallSite site = java.lang.invoke.LambdaMetafactory.metafactory( + caller, + invokedName, + invokedType, + samMethodType, + lambdaMethodHandle, + instantiatedMethodType); + + Object[] capturedArgumentValues = getLambdaCapturedArgumentValues(capturedArguments); + + // This array will contain the value of captured receiver + // (`this` instance of class where the lambda is declared) + // and the values of captured arguments. + Object[] capturedValues = new Object[capturedArguments.length + 1]; + + // Setting the captured receiver value. + capturedValues[0] = capturedReceiver; + + // Setting the captured argument values. + System.arraycopy(capturedArgumentValues, 0, capturedValues, 1, capturedArgumentValues.length); + + // Get MethodHandle and pass captured values to it to obtain an object + // that represents the target functional interface instance. + java.lang.invoke.MethodHandle handle = site.getTarget(); + return handle.invokeWithArguments(capturedValues); + } + + /** + * @param clazz a class to create lookup instance for. + * @return {@link java.lang.invoke.MethodHandles.Lookup} instance for the given {@code clazz}. + * It can be used, for example, to search methods of this {@code clazz}, even the {@code private} ones. + */ + private static java.lang.invoke.MethodHandles.Lookup getLookupIn(Class clazz) throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, java.lang.reflect.InvocationTargetException { + java.lang.invoke.MethodHandles.Lookup lookup = java.lang.invoke.MethodHandles.lookup().in(clazz); + + // Allow lookup to access all members of declaringClass, including the private ones. + // For example, it is useful to access private synthetic methods representing lambdas. + + java.lang.reflect.Field allowedModes; + java.lang.reflect.Field allModesField; + + java.lang.reflect.Method methodForGetDeclaredFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + methodForGetDeclaredFields.setAccessible(true); + java.lang.reflect.Field[] allFieldsFromFieldClass952853338898200 = (java.lang.reflect.Field[]) methodForGetDeclaredFields.invoke(java.lang.invoke.MethodHandles.Lookup.class, false); + allowedModes = java.util.Arrays.stream(allFieldsFromFieldClass952853338898200).filter(field1 -> field1.getName().equals("allowedModes")).findFirst().get(); + + + java.lang.reflect.Method methodForGetDeclaredFields952853338908800 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); + methodForGetDeclaredFields952853338908800.setAccessible(true); + java.lang.reflect.Field[] allFieldsFromFieldClass952853338909800 = (java.lang.reflect.Field[]) methodForGetDeclaredFields952853338908800.invoke(java.lang.invoke.MethodHandles.Lookup.class, false); + allModesField = java.util.Arrays.stream(allFieldsFromFieldClass952853338909800).filter(field1 -> field1.getName().equals("ALL_MODES")).findFirst().get(); + + allModesField.setAccessible(true); + allowedModes.setAccessible(true); + allowedModes.setInt(lookup, (Integer) allModesField.get(null)); + + return lookup; + } + + /** + * @param capturedArguments values captured by some lambda. Note that this argument does not contain + * the possibly captured instance of the class where the lambda is declared. + * It contains all the other captured values. They are represented as arguments of the synthetic method + * that the lambda is compiled into. Hence, the name of the argument. + * @return types of the given {@code capturedArguments}. + * These types are required to build {@code invokedType}, which represents + * the target functional interface with info about captured values' types. + * See {@link java.lang.invoke.LambdaMetafactory#metafactory} method documentation for more details on what {@code invokedType} is. + */ + private static Class[] getLambdaCapturedArgumentTypes(CapturedArgument... capturedArguments) { + Class[] capturedArgumentTypes = new Class[capturedArguments.length]; + for (int i = 0; i < capturedArguments.length; i++) { + capturedArgumentTypes[i] = capturedArguments[i].type; + } + return capturedArgumentTypes; + } + + /** + * Obtain captured values to be used as captured arguments in the lambda call. + */ + private static Object[] getLambdaCapturedArgumentValues(CapturedArgument... capturedArguments) { + return java.util.Arrays.stream(capturedArguments) + .map(argument -> argument.value) + .toArray(); + } + + /** + * @param lambdaMethod {@link java.lang.reflect.Method} that represents a synthetic method for lambda. + * @param capturedArgumentTypes types of values captured by lambda. + * @return {@link java.lang.invoke.MethodType} that represents the value of argument {@code instantiatedMethodType} + * of method {@link java.lang.invoke.LambdaMetafactory#metafactory}. + */ + private static java.lang.invoke.MethodType getInstantiatedMethodType(java.lang.reflect.Method lambdaMethod, Class[] capturedArgumentTypes) { + // Types of arguments of synthetic method (representing lambda) without the types of captured values. + java.util.List> instantiatedMethodParamTypeList = java.util.Arrays.stream(lambdaMethod.getParameterTypes()) + .skip(capturedArgumentTypes.length) + .collect(java.util.stream.Collectors.toList()); + + // The same types, but stored in an array. + Class[] instantiatedMethodParamTypes = new Class[instantiatedMethodParamTypeList.size()]; + for (int i = 0; i < instantiatedMethodParamTypeList.size(); i++) { + instantiatedMethodParamTypes[i] = instantiatedMethodParamTypeList.get(i); + } + + return java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), instantiatedMethodParamTypes); + } + + /** + * @param declaringClass class where a lambda is declared. + * @param lambdaName name of synthetic method that represents a lambda. + * @return {@link java.lang.reflect.Method} instance for the synthetic method that represent a lambda. + */ + private static java.lang.reflect.Method getLambdaMethod(Class declaringClass, String lambdaName) { + return java.util.Arrays.stream(declaringClass.getDeclaredMethods()) + .filter(method -> method.getName().equals(lambdaName)) + .findFirst() + .orElseThrow(() -> new IllegalArgumentException("No lambda method named " + lambdaName + " was found in class: " + declaringClass.getCanonicalName())); + } + + private static java.lang.reflect.Method getSingleAbstractMethod(Class clazz) { + java.util.List abstractMethods = java.util.Arrays.stream(clazz.getMethods()) + .filter(method -> java.lang.reflect.Modifier.isAbstract(method.getModifiers())) + .collect(java.util.stream.Collectors.toList()); + + if (abstractMethods.isEmpty()) { + throw new IllegalArgumentException("No abstract methods found in class: " + clazz.getCanonicalName()); + } + if (abstractMethods.size() > 1) { + throw new IllegalArgumentException("More than one abstract method found in class: " + clazz.getCanonicalName()); + } + + return abstractMethods.get(0); + } + ///endregion +} 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 a126e700ca..72a1b44753 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 @@ -261,6 +261,7 @@ fun createJcContainer( ) { // TODO usvm-sbft: we may want to tune these JcSettings for contest // TODO: require usePersistence=false for ClassScorer + // ApproximationPaths(JcJars.approximationsJar, ...) в ClassScorer installFeatures(InMemoryHierarchy, Approximations, ClassScorer(TypeScorer, ::scoreClassNode)) loadByteCode(classpathFiles) } 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 9b316042de..44ba50f90b 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 @@ -42,6 +42,7 @@ 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 @@ -85,8 +86,8 @@ class JcToUtExecutionConverter( } ?: error("Failed to construct UtExecution for all uTestExecutionResultWrappers on ${jcExecution.method.method}") fun convertInitialStateOnly(): UtExecutionInitialState = UtExecutionInitialState( - stateBefore = constructStateBeforeFromUTest(), - instrumentations = uTestProcessResult.instrumentation + stateBefore = constructStateBeforeFromUTest().applyCommonMappings(), + instrumentations = uTestProcessResult.instrumentation.applyCommonMappings() ) private fun convert(uTestResultWrapper: UTestResultWrapper): UtExecution? { @@ -176,11 +177,8 @@ class JcToUtExecutionConverter( } } ?: return null - return utUsvmExecution - .mapModels(jcToUtModelConverter.utCyclicReferenceModelResolver) - .mapModels(constructAssemblingMapper()) - .mapModels(constructAssembleToCompositeModelMapper()) - .mapModels(constructConstArrayModelMapper()) + + return utUsvmExecution.applyCommonMappings() } private fun constructAssemblingMapper(): UtModelDeepMapper = UtModelDeepMapper { model -> @@ -353,4 +351,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.forEach { mapper -> this.mapModels(mapper) } + return this + } + + private fun EnvironmentModels.applyCommonMappings(): EnvironmentModels { + commonMappers.forEach { this.mapModels(it) } + return this + } + + private fun List.applyCommonMappings(): List { + commonMappers.forEach { mapper -> this.map { instr -> instr.mapModels(mapper) } } + return this + } } \ 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..2308364a51 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 @@ -51,6 +51,7 @@ class JcContainer private constructor( persistent(location = persistenceLocation, clearOnStart = false) } } + // TODO ApproximationPaths(JcJars.approximationsJar, ...) val cp = db.classpathWithApproximations(classpath, listOf(UnknownClasses)) db to cp } @@ -60,6 +61,7 @@ class JcContainer private constructor( JcRuntimeTraceInstrumenterFactory::class, cpPath, cp, + // TODO InstrumentedProcessPaths(JcJars.runnerJar, collectorsJar, javaHome.absolutePath) javaHome.absolutePath, persistenceLocation, TEST_EXECUTION_TIMEOUT From 91803c26e7ee56a55f003afa0434aabd74dbf485 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 20 Dec 2023 00:59:16 +0300 Subject: [PATCH 08/26] Update usvm version and related updates in JcContainer --- gradle.properties | 2 +- .../kotlin/org/utbot/usvm/jc/JcContainer.kt | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/gradle.properties b/gradle.properties index cd83a4920b..4eddd1d941 100644 --- a/gradle.properties +++ b/gradle.properties @@ -109,7 +109,7 @@ springBootVersion=2.7.13 springSecurityVersion=5.8.5 approximationsVersion=bfce4eedde -usvmVersion=72924ad +usvmVersion=89797677a7 # configuration for build server # 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 2308364a51..ead8a6ed2c 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,8 +7,10 @@ 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 java.io.File import kotlin.time.Duration.Companion.seconds @@ -51,8 +53,12 @@ class JcContainer private constructor( persistent(location = persistenceLocation, clearOnStart = false) } } - // TODO ApproximationPaths(JcJars.approximationsJar, ...) - 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 @@ -61,8 +67,11 @@ class JcContainer private constructor( JcRuntimeTraceInstrumenterFactory::class, cpPath, cp, - // TODO InstrumentedProcessPaths(JcJars.runnerJar, collectorsJar, javaHome.absolutePath) - javaHome.absolutePath, + InstrumentationProcessPaths( + pathToUsvmInstrumentationJar = JcJars.runnerJar.absolutePath, + pathToUsvmCollectorsJar = JcJars.collectorsJar.absolutePath, + pathToJava = javaHome.absolutePath, + ), persistenceLocation, TEST_EXECUTION_TIMEOUT ) From 7779a3baff20e71313ab9abf564a37242226c7c6 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 20 Dec 2023 10:10:34 +0300 Subject: [PATCH 09/26] Update usvm version and related updates in ClassScorer usages --- gradle.properties | 2 +- .../main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt | 10 +++++++--- .../main/kotlin/org/utbot/contest/usvm/ContestUsvm.kt | 9 +++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/gradle.properties b/gradle.properties index 4eddd1d941..b482a13060 100644 --- a/gradle.properties +++ b/gradle.properties @@ -109,7 +109,7 @@ springBootVersion=2.7.13 springSecurityVersion=5.8.5 approximationsVersion=bfce4eedde -usvmVersion=89797677a7 +usvmVersion=7b45e18b0e # configuration for build server # diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt index acfc7fea60..c1f540d87f 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -14,6 +14,7 @@ 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.codegen.domain.builtin.UtilMethodProviderPlaceholder @@ -22,7 +23,6 @@ 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.UtConcreteExecutionProcessedFailure import org.utbot.framework.plugin.api.UtError import org.utbot.framework.plugin.api.UtFailedExecution import org.utbot.framework.plugin.api.UtResult @@ -38,6 +38,7 @@ 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.findMethodOrNull import org.utbot.usvm.jc.typedMethod @@ -201,11 +202,14 @@ object UsvmSymbolicEngine { classpath = classpathFiles, javaHome = JdkInfoService.provide().path.toFile(), ) { + val approximationPaths = ApproximationPaths( + usvmApiJarPath = JcJars.approximationsApiJar.absolutePath, + usvmApproximationsJarPath = JcJars.approximationsJar.absolutePath, + ) installFeatures( InMemoryHierarchy, Approximations, - // ApproximationPaths(JcJars.approximationsJar, ...) - ClassScorer(TypeScorer, ::scoreClassNode) + ClassScorer(TypeScorer, ::scoreClassNode, approximationPaths) ) loadByteCode(classpathFiles) } 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 72a1b44753..a9ca4fbe68 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 @@ -14,6 +14,7 @@ import org.usvm.machine.JcMachine import org.usvm.types.ClassScorer import org.usvm.types.TypeScorer import org.usvm.types.scoreClassNode +import org.usvm.util.ApproximationPaths import org.utbot.common.info import org.utbot.common.measureTime import org.utbot.contest.ClassUnderTest @@ -50,6 +51,7 @@ 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.JcJars import org.utbot.usvm.jc.JcTestExecutor import org.utbot.usvm.jc.findMethodOrNull import org.utbot.usvm.jc.typedMethod @@ -261,8 +263,11 @@ fun createJcContainer( ) { // TODO usvm-sbft: we may want to tune these JcSettings for contest // TODO: require usePersistence=false for ClassScorer - // ApproximationPaths(JcJars.approximationsJar, ...) в 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) } From 44f0c2ac36e808552b89b2a823fab639d4d1cd64 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 20 Dec 2023 10:54:24 +0300 Subject: [PATCH 10/26] Correct applyCommonMappings methods --- .../usvm/converter/JcToUtExecutionConverter.kt | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) 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 44ba50f90b..d90f732998 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 @@ -359,17 +359,22 @@ class JcToUtExecutionConverter( constructConstArrayModelMapper(), ) private fun UtExecution.applyCommonMappings(): UtExecution { - commonMappers.forEach { mapper -> this.mapModels(mapper) } - return this + var mappedExecution = this + commonMappers.forEach { mapper -> mappedExecution = mappedExecution.mapModels(mapper) } + return mappedExecution } private fun EnvironmentModels.applyCommonMappings(): EnvironmentModels { - commonMappers.forEach { this.mapModels(it) } - return this + var mappedEnvironmentalModels = this + commonMappers.forEach { mapper -> mappedEnvironmentalModels = mappedEnvironmentalModels.mapModels(mapper) } + return mappedEnvironmentalModels } private fun List.applyCommonMappings(): List { - commonMappers.forEach { mapper -> this.map { instr -> instr.mapModels(mapper) } } - return this + var mappedInstrumentation = this + commonMappers.forEach { + mapper -> mappedInstrumentation = mappedInstrumentation.map { instr -> instr.mapModels(mapper) } + } + return mappedInstrumentation } } \ No newline at end of file From ce168215791f38e9f66c03b3512d5b160f5c5984 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 20 Dec 2023 12:58:16 +0300 Subject: [PATCH 11/26] Several UI corrections --- gradle.properties | 2 +- .../org/utbot/engine/UsvmSymbolicEngine.kt | 1 - .../org/utbot/runtime/utils/java/UtUtils.java | 609 ------------------ .../intellij/plugin/settings/Settings.kt | 1 + .../plugin/ui/GenerateTestsDialogWindow.kt | 17 +- .../org/utbot/usvm/jc/JcTestExecutor.kt | 4 +- 6 files changed, 17 insertions(+), 617 deletions(-) delete mode 100644 utbot-instrumentation/src/main/java/org/utbot/runtime/utils/java/UtUtils.java diff --git a/gradle.properties b/gradle.properties index b482a13060..0a58b6796a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,7 +20,7 @@ buildType=NOJS # IDE types that supports appropriate language javaIde=IC,IU -pythonIde=IC,IU,PC,PY +pythonIde=PC,PY jsIde=IU,PY jsBuild=ALL goIde=IU,GO diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt index c1f540d87f..867edb71a0 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -101,7 +101,6 @@ object UsvmSymbolicEngine { executionConverter.convert() }.getOrElse { e -> logger.warn(e) { "JcToUtExecutionConverter.convert(${jcExecution.method.method}) failed" } - val initialState = executionConverter.convertInitialStateOnly() val concreteExecutor = ConcreteExecutor(concreteExecutionContext.instrumentationFactory, classpath) diff --git a/utbot-instrumentation/src/main/java/org/utbot/runtime/utils/java/UtUtils.java b/utbot-instrumentation/src/main/java/org/utbot/runtime/utils/java/UtUtils.java deleted file mode 100644 index dd99abdfc6..0000000000 --- a/utbot-instrumentation/src/main/java/org/utbot/runtime/utils/java/UtUtils.java +++ /dev/null @@ -1,609 +0,0 @@ -package org.utbot.runtime.utils.java; - -/** - * This is a regular UtUtils class (without mock framework usage) - * UtUtils class version: 2.1 - */ -public final class UtUtils { - ///region Util classes - - /** - * This class represents the {@code type} and {@code value} of a value captured by lambda. - * Captured values are represented as arguments of a synthetic method that lambda is compiled into, - * hence the name of the class. - */ - public static class CapturedArgument { - private Class type; - private Object value; - - public CapturedArgument(Class type, Object value) { - this.type = type; - this.value = value; - } - } - ///endregion - - ///region Util methods - - public static Object getUnsafeInstance() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { - java.lang.reflect.Field f = Class.forName("sun.misc.Unsafe").getDeclaredField("theUnsafe"); - f.setAccessible(true); - return f.get(null); - } - - public static Object createInstance(String className) throws Exception { - Class clazz = Class.forName(className); - return Class.forName("sun.misc.Unsafe").getDeclaredMethod("allocateInstance", Class.class) - .invoke(getUnsafeInstance(), clazz); - } - - public static Object[] createArray(String className, int length, Object... values) throws ClassNotFoundException { - Object array = java.lang.reflect.Array.newInstance(Class.forName(className), length); - - for (int i = 0; i < values.length; i++) { - java.lang.reflect.Array.set(array, i, values[i]); - } - - return (Object[]) array; - } - - public static void setField(Object object, String fieldClassName, String fieldName, Object fieldValue) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, java.lang.reflect.InvocationTargetException { - Class clazz = Class.forName(fieldClassName); - java.lang.reflect.Field field = clazz.getDeclaredField(fieldName); - - java.lang.reflect.Field modifiersField; - - java.lang.reflect.Method methodForGetDeclaredFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); - methodForGetDeclaredFields.setAccessible(true); - java.lang.reflect.Field[] allFieldsFromFieldClass = (java.lang.reflect.Field[]) methodForGetDeclaredFields.invoke(java.lang.reflect.Field.class, false); - modifiersField = java.util.Arrays.stream(allFieldsFromFieldClass).filter(field1 -> field1.getName().equals("modifiers")).findFirst().get(); - - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - field.setAccessible(true); - field.set(object, fieldValue); - } - - public static void setStaticField(Class clazz, String fieldName, Object fieldValue) throws NoSuchFieldException, IllegalAccessException { - java.lang.reflect.Field field; - - try { - do { - try { - field = clazz.getDeclaredField(fieldName); - } catch (Exception e) { - clazz = clazz.getSuperclass(); - field = null; - } - } while (field == null); - - java.lang.reflect.Field modifiersField; - - java.lang.reflect.Method methodForGetDeclaredFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); - methodForGetDeclaredFields.setAccessible(true); - java.lang.reflect.Field[] allFieldsFromFieldClass = (java.lang.reflect.Field[]) methodForGetDeclaredFields.invoke(java.lang.reflect.Field.class, false); - modifiersField = java.util.Arrays.stream(allFieldsFromFieldClass).filter(field1 -> field1.getName().equals("modifiers")).findFirst().get(); - - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - field.setAccessible(true); - field.set(null, fieldValue); - } catch (java.lang.reflect.InvocationTargetException e) { - e.printStackTrace(); - } catch (NoSuchMethodException e2) { - e2.printStackTrace(); - } - } - - public static Object getFieldValue(Object obj, String fieldClassName, String fieldName) throws ClassNotFoundException, NoSuchMethodException, java.lang.reflect.InvocationTargetException, IllegalAccessException, NoSuchFieldException { - Class clazz = Class.forName(fieldClassName); - java.lang.reflect.Field field = clazz.getDeclaredField(fieldName); - - field.setAccessible(true); - - java.lang.reflect.Field modifiersField; - - java.lang.reflect.Method methodForGetDeclaredFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); - methodForGetDeclaredFields.setAccessible(true); - java.lang.reflect.Field[] allFieldsFromFieldClass = (java.lang.reflect.Field[]) methodForGetDeclaredFields.invoke(java.lang.reflect.Field.class, false); - modifiersField = java.util.Arrays.stream(allFieldsFromFieldClass).filter(field1 -> field1.getName().equals("modifiers")).findFirst().get(); - - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - return field.get(obj); - } - - public static Object getStaticFieldValue(Class clazz, String fieldName) throws IllegalAccessException, NoSuchFieldException { - java.lang.reflect.Field field; - Class originClass = clazz; - do { - try { - field = clazz.getDeclaredField(fieldName); - field.setAccessible(true); - - java.lang.reflect.Field modifiersField; - - java.lang.reflect.Method methodForGetDeclaredFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); - methodForGetDeclaredFields.setAccessible(true); - java.lang.reflect.Field[] allFieldsFromFieldClass952853319934400 = (java.lang.reflect.Field[]) methodForGetDeclaredFields.invoke(java.lang.reflect.Field.class, false); - modifiersField = java.util.Arrays.stream(allFieldsFromFieldClass952853319934400).filter(field1 -> field1.getName().equals("modifiers")).findFirst().get(); - - modifiersField.setAccessible(true); - modifiersField.setInt(field, field.getModifiers() & ~java.lang.reflect.Modifier.FINAL); - - return field.get(null); - } catch (NoSuchFieldException e) { - clazz = clazz.getSuperclass(); - } catch (NoSuchMethodException e2) { - e2.printStackTrace(); - } catch (java.lang.reflect.InvocationTargetException e3) { - e3.printStackTrace(); - } - } while (clazz != null); - - throw new NoSuchFieldException("Field '" + fieldName + "' not found on class " + originClass); - } - - public static Object getEnumConstantByName(Class enumClass, String name) throws IllegalAccessException { - java.lang.reflect.Field[] fields = enumClass.getDeclaredFields(); - for (java.lang.reflect.Field field : fields) { - String fieldName = field.getName(); - if (field.isEnumConstant() && fieldName.equals(name)) { - field.setAccessible(true); - - return field.get(null); - } - } - - return null; - } - - static class FieldsPair { - final Object o1; - final Object o2; - - public FieldsPair(Object o1, Object o2) { - this.o1 = o1; - this.o2 = o2; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - FieldsPair that = (FieldsPair) o; - return java.util.Objects.equals(o1, that.o1) && java.util.Objects.equals(o2, that.o2); - } - - @Override - public int hashCode() { - return java.util.Objects.hash(o1, o2); - } - } - - public static boolean deepEquals(Object o1, Object o2) { - return deepEquals(o1, o2, new java.util.HashSet<>()); - } - - private static boolean deepEquals(Object o1, Object o2, java.util.Set visited) { - visited.add(new FieldsPair(o1, o2)); - - if (o1 == o2) { - return true; - } - - if (o1 == null || o2 == null) { - return false; - } - - if (o1 instanceof Iterable) { - if (!(o2 instanceof Iterable)) { - return false; - } - - return iterablesDeepEquals((Iterable) o1, (Iterable) o2, visited); - } - - if (o2 instanceof Iterable) { - return false; - } - - if (o1 instanceof java.util.stream.BaseStream) { - if (!(o2 instanceof java.util.stream.BaseStream)) { - return false; - } - - return streamsDeepEquals((java.util.stream.BaseStream) o1, (java.util.stream.BaseStream) o2, visited); - } - - if (o2 instanceof java.util.stream.BaseStream) { - return false; - } - - if (o1 instanceof java.util.Map) { - if (!(o2 instanceof java.util.Map)) { - return false; - } - - return mapsDeepEquals((java.util.Map) o1, (java.util.Map) o2, visited); - } - - if (o2 instanceof java.util.Map) { - return false; - } - - Class firstClass = o1.getClass(); - if (firstClass.isArray()) { - if (!o2.getClass().isArray()) { - return false; - } - - // Primitive arrays should not appear here - return arraysDeepEquals(o1, o2, visited); - } - - // common classes - - // check if class has custom equals method (including wrappers and strings) - // It is very important to check it here but not earlier because iterables and maps also have custom equals - // based on elements equals - if (hasCustomEquals(firstClass)) { - return o1.equals(o2); - } - - // common classes without custom equals, use comparison by fields - final java.util.List fields = new java.util.ArrayList<>(); - while (firstClass != Object.class) { - fields.addAll(java.util.Arrays.asList(firstClass.getDeclaredFields())); - // Interface should not appear here - firstClass = firstClass.getSuperclass(); - } - - for (java.lang.reflect.Field field : fields) { - field.setAccessible(true); - try { - final Object field1 = field.get(o1); - final Object field2 = field.get(o2); - if (!visited.contains(new FieldsPair(field1, field2)) && !deepEquals(field1, field2, visited)) { - return false; - } - } catch (IllegalArgumentException e) { - return false; - } catch (IllegalAccessException e) { - // should never occur because field was set accessible - return false; - } - } - - return true; - } - - public static boolean arraysDeepEquals(Object arr1, Object arr2, java.util.Set visited) { - final int length = java.lang.reflect.Array.getLength(arr1); - if (length != java.lang.reflect.Array.getLength(arr2)) { - return false; - } - - for (int i = 0; i < length; i++) { - if (!deepEquals(java.lang.reflect.Array.get(arr1, i), java.lang.reflect.Array.get(arr2, i), visited)) { - return false; - } - } - - return true; - } - - public static boolean iterablesDeepEquals(Iterable i1, Iterable i2, java.util.Set visited) { - final java.util.Iterator firstIterator = i1.iterator(); - final java.util.Iterator secondIterator = i2.iterator(); - while (firstIterator.hasNext() && secondIterator.hasNext()) { - if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { - return false; - } - } - - if (firstIterator.hasNext()) { - return false; - } - - return !secondIterator.hasNext(); - } - - public static boolean streamsDeepEquals( - java.util.stream.BaseStream s1, - java.util.stream.BaseStream s2, - java.util.Set visited - ) { - final java.util.Iterator firstIterator = s1.iterator(); - final java.util.Iterator secondIterator = s2.iterator(); - while (firstIterator.hasNext() && secondIterator.hasNext()) { - if (!deepEquals(firstIterator.next(), secondIterator.next(), visited)) { - return false; - } - } - - if (firstIterator.hasNext()) { - return false; - } - - return !secondIterator.hasNext(); - } - - public static boolean mapsDeepEquals( - java.util.Map m1, - java.util.Map m2, - java.util.Set visited - ) { - final java.util.Iterator> firstIterator = m1.entrySet().iterator(); - final java.util.Iterator> secondIterator = m2.entrySet().iterator(); - while (firstIterator.hasNext() && secondIterator.hasNext()) { - final java.util.Map.Entry firstEntry = firstIterator.next(); - final java.util.Map.Entry secondEntry = secondIterator.next(); - - if (!deepEquals(firstEntry.getKey(), secondEntry.getKey(), visited)) { - return false; - } - - if (!deepEquals(firstEntry.getValue(), secondEntry.getValue(), visited)) { - return false; - } - } - - if (firstIterator.hasNext()) { - return false; - } - - return !secondIterator.hasNext(); - } - - public static boolean hasCustomEquals(Class clazz) { - while (!Object.class.equals(clazz)) { - try { - clazz.getDeclaredMethod("equals", Object.class); - return true; - } catch (Exception e) { - // Interface should not appear here - clazz = clazz.getSuperclass(); - } - } - - return false; - } - - public static int getArrayLength(Object arr) { - return java.lang.reflect.Array.getLength(arr); - } - - public static void consumeBaseStream(java.util.stream.BaseStream stream) { - stream.iterator().forEachRemaining(value -> { - }); - } - - /** - * @param samType a class representing a functional interface. - * @param declaringClass a class where the lambda is declared. - * @param lambdaName a name of the synthetic method that represents a lambda. - * @param capturedArguments a vararg containing {@link CapturedArgument} instances representing - * values that the given lambda has captured. - * @return an {@link Object} that represents an instance of the given functional interface {@code samType} - * and implements its single abstract method with the behavior of the given lambda. - */ - public static Object buildStaticLambda( - Class samType, - Class declaringClass, - String lambdaName, - CapturedArgument... capturedArguments - ) throws Throwable { - // Create lookup for class where the lambda is declared in. - java.lang.invoke.MethodHandles.Lookup caller = getLookupIn(declaringClass); - - // Obtain the single abstract method of a functional interface whose instance we are building. - // For example, for `java.util.function.Predicate` it will be method `test`. - java.lang.reflect.Method singleAbstractMethod = getSingleAbstractMethod(samType); - String invokedName = singleAbstractMethod.getName(); - // Method type of single abstract method of the target functional interface. - java.lang.invoke.MethodType samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.getReturnType(), singleAbstractMethod.getParameterTypes()); - - java.lang.reflect.Method lambdaMethod = getLambdaMethod(declaringClass, lambdaName); - lambdaMethod.setAccessible(true); - java.lang.invoke.MethodType lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), lambdaMethod.getParameterTypes()); - java.lang.invoke.MethodHandle lambdaMethodHandle = caller.findStatic(declaringClass, lambdaName, lambdaMethodType); - - Class[] capturedArgumentTypes = getLambdaCapturedArgumentTypes(capturedArguments); - java.lang.invoke.MethodType invokedType = java.lang.invoke.MethodType.methodType(samType, capturedArgumentTypes); - java.lang.invoke.MethodType instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes); - - // Create a CallSite for the given lambda. - java.lang.invoke.CallSite site = java.lang.invoke.LambdaMetafactory.metafactory( - caller, - invokedName, - invokedType, - samMethodType, - lambdaMethodHandle, - instantiatedMethodType); - - Object[] capturedValues = getLambdaCapturedArgumentValues(capturedArguments); - - // Get MethodHandle and pass captured values to it to obtain an object - // that represents the target functional interface instance. - java.lang.invoke.MethodHandle handle = site.getTarget(); - return handle.invokeWithArguments(capturedValues); - } - - /** - * @param samType a class representing a functional interface. - * @param declaringClass a class where the lambda is declared. - * @param lambdaName a name of the synthetic method that represents a lambda. - * @param capturedReceiver an object of {@code declaringClass} that is captured by the lambda. - * When the synthetic lambda method is not static, it means that the lambda captures an instance - * of the class it is declared in. - * @param capturedArguments a vararg containing {@link CapturedArgument} instances representing - * values that the given lambda has captured. - * @return an {@link Object} that represents an instance of the given functional interface {@code samType} - * and implements its single abstract method with the behavior of the given lambda. - */ - public static Object buildLambda( - Class samType, - Class declaringClass, - String lambdaName, - Object capturedReceiver, - CapturedArgument... capturedArguments - ) throws Throwable { - // Create lookup for class where the lambda is declared in. - java.lang.invoke.MethodHandles.Lookup caller = getLookupIn(declaringClass); - - // Obtain the single abstract method of a functional interface whose instance we are building. - // For example, for `java.util.function.Predicate` it will be method `test`. - java.lang.reflect.Method singleAbstractMethod = getSingleAbstractMethod(samType); - String invokedName = singleAbstractMethod.getName(); - // Method type of single abstract method of the target functional interface. - java.lang.invoke.MethodType samMethodType = java.lang.invoke.MethodType.methodType(singleAbstractMethod.getReturnType(), singleAbstractMethod.getParameterTypes()); - - java.lang.reflect.Method lambdaMethod = getLambdaMethod(declaringClass, lambdaName); - lambdaMethod.setAccessible(true); - java.lang.invoke.MethodType lambdaMethodType = java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), lambdaMethod.getParameterTypes()); - java.lang.invoke.MethodHandle lambdaMethodHandle = caller.findVirtual(declaringClass, lambdaName, lambdaMethodType); - - Class[] capturedArgumentTypes = getLambdaCapturedArgumentTypes(capturedArguments); - java.lang.invoke.MethodType invokedType = java.lang.invoke.MethodType.methodType(samType, capturedReceiver.getClass(), capturedArgumentTypes); - java.lang.invoke.MethodType instantiatedMethodType = getInstantiatedMethodType(lambdaMethod, capturedArgumentTypes); - - // Create a CallSite for the given lambda. - java.lang.invoke.CallSite site = java.lang.invoke.LambdaMetafactory.metafactory( - caller, - invokedName, - invokedType, - samMethodType, - lambdaMethodHandle, - instantiatedMethodType); - - Object[] capturedArgumentValues = getLambdaCapturedArgumentValues(capturedArguments); - - // This array will contain the value of captured receiver - // (`this` instance of class where the lambda is declared) - // and the values of captured arguments. - Object[] capturedValues = new Object[capturedArguments.length + 1]; - - // Setting the captured receiver value. - capturedValues[0] = capturedReceiver; - - // Setting the captured argument values. - System.arraycopy(capturedArgumentValues, 0, capturedValues, 1, capturedArgumentValues.length); - - // Get MethodHandle and pass captured values to it to obtain an object - // that represents the target functional interface instance. - java.lang.invoke.MethodHandle handle = site.getTarget(); - return handle.invokeWithArguments(capturedValues); - } - - /** - * @param clazz a class to create lookup instance for. - * @return {@link java.lang.invoke.MethodHandles.Lookup} instance for the given {@code clazz}. - * It can be used, for example, to search methods of this {@code clazz}, even the {@code private} ones. - */ - private static java.lang.invoke.MethodHandles.Lookup getLookupIn(Class clazz) throws IllegalAccessException, NoSuchFieldException, NoSuchMethodException, java.lang.reflect.InvocationTargetException { - java.lang.invoke.MethodHandles.Lookup lookup = java.lang.invoke.MethodHandles.lookup().in(clazz); - - // Allow lookup to access all members of declaringClass, including the private ones. - // For example, it is useful to access private synthetic methods representing lambdas. - - java.lang.reflect.Field allowedModes; - java.lang.reflect.Field allModesField; - - java.lang.reflect.Method methodForGetDeclaredFields = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); - methodForGetDeclaredFields.setAccessible(true); - java.lang.reflect.Field[] allFieldsFromFieldClass952853338898200 = (java.lang.reflect.Field[]) methodForGetDeclaredFields.invoke(java.lang.invoke.MethodHandles.Lookup.class, false); - allowedModes = java.util.Arrays.stream(allFieldsFromFieldClass952853338898200).filter(field1 -> field1.getName().equals("allowedModes")).findFirst().get(); - - - java.lang.reflect.Method methodForGetDeclaredFields952853338908800 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class); - methodForGetDeclaredFields952853338908800.setAccessible(true); - java.lang.reflect.Field[] allFieldsFromFieldClass952853338909800 = (java.lang.reflect.Field[]) methodForGetDeclaredFields952853338908800.invoke(java.lang.invoke.MethodHandles.Lookup.class, false); - allModesField = java.util.Arrays.stream(allFieldsFromFieldClass952853338909800).filter(field1 -> field1.getName().equals("ALL_MODES")).findFirst().get(); - - allModesField.setAccessible(true); - allowedModes.setAccessible(true); - allowedModes.setInt(lookup, (Integer) allModesField.get(null)); - - return lookup; - } - - /** - * @param capturedArguments values captured by some lambda. Note that this argument does not contain - * the possibly captured instance of the class where the lambda is declared. - * It contains all the other captured values. They are represented as arguments of the synthetic method - * that the lambda is compiled into. Hence, the name of the argument. - * @return types of the given {@code capturedArguments}. - * These types are required to build {@code invokedType}, which represents - * the target functional interface with info about captured values' types. - * See {@link java.lang.invoke.LambdaMetafactory#metafactory} method documentation for more details on what {@code invokedType} is. - */ - private static Class[] getLambdaCapturedArgumentTypes(CapturedArgument... capturedArguments) { - Class[] capturedArgumentTypes = new Class[capturedArguments.length]; - for (int i = 0; i < capturedArguments.length; i++) { - capturedArgumentTypes[i] = capturedArguments[i].type; - } - return capturedArgumentTypes; - } - - /** - * Obtain captured values to be used as captured arguments in the lambda call. - */ - private static Object[] getLambdaCapturedArgumentValues(CapturedArgument... capturedArguments) { - return java.util.Arrays.stream(capturedArguments) - .map(argument -> argument.value) - .toArray(); - } - - /** - * @param lambdaMethod {@link java.lang.reflect.Method} that represents a synthetic method for lambda. - * @param capturedArgumentTypes types of values captured by lambda. - * @return {@link java.lang.invoke.MethodType} that represents the value of argument {@code instantiatedMethodType} - * of method {@link java.lang.invoke.LambdaMetafactory#metafactory}. - */ - private static java.lang.invoke.MethodType getInstantiatedMethodType(java.lang.reflect.Method lambdaMethod, Class[] capturedArgumentTypes) { - // Types of arguments of synthetic method (representing lambda) without the types of captured values. - java.util.List> instantiatedMethodParamTypeList = java.util.Arrays.stream(lambdaMethod.getParameterTypes()) - .skip(capturedArgumentTypes.length) - .collect(java.util.stream.Collectors.toList()); - - // The same types, but stored in an array. - Class[] instantiatedMethodParamTypes = new Class[instantiatedMethodParamTypeList.size()]; - for (int i = 0; i < instantiatedMethodParamTypeList.size(); i++) { - instantiatedMethodParamTypes[i] = instantiatedMethodParamTypeList.get(i); - } - - return java.lang.invoke.MethodType.methodType(lambdaMethod.getReturnType(), instantiatedMethodParamTypes); - } - - /** - * @param declaringClass class where a lambda is declared. - * @param lambdaName name of synthetic method that represents a lambda. - * @return {@link java.lang.reflect.Method} instance for the synthetic method that represent a lambda. - */ - private static java.lang.reflect.Method getLambdaMethod(Class declaringClass, String lambdaName) { - return java.util.Arrays.stream(declaringClass.getDeclaredMethods()) - .filter(method -> method.getName().equals(lambdaName)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("No lambda method named " + lambdaName + " was found in class: " + declaringClass.getCanonicalName())); - } - - private static java.lang.reflect.Method getSingleAbstractMethod(Class clazz) { - java.util.List abstractMethods = java.util.Arrays.stream(clazz.getMethods()) - .filter(method -> java.lang.reflect.Modifier.isAbstract(method.getModifiers())) - .collect(java.util.stream.Collectors.toList()); - - if (abstractMethods.isEmpty()) { - throw new IllegalArgumentException("No abstract methods found in class: " + clazz.getCanonicalName()); - } - if (abstractMethods.size() > 1) { - throw new IllegalArgumentException("More than one abstract method found in class: " + clazz.getCanonicalName()); - } - - return abstractMethods.get(0); - } - ///endregion -} 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 1a3c4a7587..ad5f78f6a7 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 @@ -906,17 +906,24 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m else -> {} } + useExperimentalEngine.isSelected = (settings.symbolicEngineSource == SymbolicEngineSource.Usvm + && 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) - useExperimentalEngine.isSelected = (settings.symbolicEngineSource == SymbolicEngineSource.Usvm - && model.projectType == ProjectType.PureJvm) + mockStrategies.isEnabled = true + staticsMocking.isEnabled = mockStrategies.item != MockStrategyApi.NO_MOCKS codegenLanguages.item = model.codegenLanguage @@ -944,8 +951,8 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m ProjectType.Python, ProjectType.JavaScript -> { } } - - mockStrategies.isEnabled = !isSpringConfigSelected() + mockStrategies.isEnabled = + settings.symbolicEngineSource == SymbolicEngineSource.UnitTestBot && !isSpringConfigSelected() updateStaticMockEnabled() updateMockStrategyList() 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 dc9909e118..f08cd19d4d 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 @@ -80,7 +80,9 @@ 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 { From 3b3ab15d9aa8a2c2cd46d7e811add5935764d777 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Wed, 20 Dec 2023 14:51:57 +0300 Subject: [PATCH 12/26] Fix JcContainer caching --- .../main/kotlin/org/utbot/contest/usvm/ContestUsvm.kt | 2 +- .../src/main/kotlin/org/utbot/usvm/jc/JcContainer.kt | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) 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 a9ca4fbe68..5271bcaaa7 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 @@ -255,7 +255,7 @@ fun runUsvmGeneration( fun createJcContainer( tmpDir: File, classpathFiles: List -) = JcContainer( +) = JcContainer.getOrCreate( usePersistence = false, persistenceDir = tmpDir, classpath = classpathFiles, 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 ead8a6ed2c..127177dc96 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 @@ -18,7 +18,13 @@ import kotlin.time.Duration.Companion.seconds private val logger = KotlinLogging.logger {} // TODO usvm-sbft-refactoring: copied from `usvm/usvm-jvm/test`, extract this class back to USVM project -class JcContainer private constructor( +/** + * 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, @@ -91,7 +97,7 @@ class JcContainer private constructor( private val cache = HashMap, JcContainer>() - operator fun invoke( + fun getOrCreate( usePersistence: Boolean, persistenceDir: File, classpath: List, From 3a0da82aea51c84e7d2ff3f895fbeaf4d3c414a8 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Thu, 21 Dec 2023 11:29:24 +0300 Subject: [PATCH 13/26] Apply discussion fixes. --- .../org/utbot/engine/UsvmSymbolicEngine.kt | 17 ++++- .../converter/JcToUtExecutionConverter.kt | 37 +++++----- .../kotlin/org/utbot/usvm/jc/JcContainer.kt | 8 ++- .../org/utbot/usvm/jc/JcTestExecutor.kt | 67 ++++++++++--------- 4 files changed, 77 insertions(+), 52 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt index 867edb71a0..fe6c29539d 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -9,6 +9,7 @@ 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 @@ -40,6 +41,7 @@ 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 @@ -58,6 +60,8 @@ object UsvmSymbolicEngine { timeoutMillis: Long ): List> { + JcContainer.specifyContainerTimeout(UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis) + val collectedExecutions = mutableListOf>() val classpathFiles = classpath.split(File.pathSeparator).map { File(it) } @@ -126,7 +130,7 @@ object UsvmSymbolicEngine { ): JcExecution { val executor = JcTestExecutor(jcContainer.cp, jcContainer.runner) - val realJcExecution = runCatching { + var realJcExecution = runCatching { executor.execute( method = state.entrypoint.typedMethod, state = state, @@ -139,8 +143,15 @@ object UsvmSymbolicEngine { null } - realJcExecution?.let { - return it + 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( 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 d90f732998..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 @@ -182,8 +182,13 @@ class JcToUtExecutionConverter( } 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()) { @@ -195,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 @@ -358,23 +364,18 @@ class JcToUtExecutionConverter( constructAssembleToCompositeModelMapper(), constructConstArrayModelMapper(), ) - private fun UtExecution.applyCommonMappings(): UtExecution { - var mappedExecution = this - commonMappers.forEach { mapper -> mappedExecution = mappedExecution.mapModels(mapper) } - return mappedExecution - } + private fun UtExecution.applyCommonMappings(): UtExecution = + commonMappers.fold(this) { mappedExecution, mapper -> + mappedExecution.mapModels(mapper) + } - private fun EnvironmentModels.applyCommonMappings(): EnvironmentModels { - var mappedEnvironmentalModels = this - commonMappers.forEach { mapper -> mappedEnvironmentalModels = mappedEnvironmentalModels.mapModels(mapper) } - return mappedEnvironmentalModels - } + private fun EnvironmentModels.applyCommonMappings(): EnvironmentModels = + commonMappers.fold(this) { mappedEnvironmentalModels, mapper -> + mappedEnvironmentalModels.mapModels(mapper) + } - private fun List.applyCommonMappings(): List { - var mappedInstrumentation = this - commonMappers.forEach { - mapper -> mappedInstrumentation = mappedInstrumentation.map { instr -> instr.mapModels(mapper) } + private fun List.applyCommonMappings(): List = + commonMappers.fold(this) { mappedInstrumentation, mapper -> + mappedInstrumentation.map { instr -> instr.mapModels(mapper) } } - return mappedInstrumentation - } } \ 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 127177dc96..061c658ce8 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 @@ -93,7 +93,13 @@ class JcContainer( } companion object : AutoCloseable { - val TEST_EXECUTION_TIMEOUT = 1.seconds + fun specifyContainerTimeout(timeout: Long) { + testExecutionTimeout = timeout.seconds + } + + private var testExecutionTimeout = 1.seconds + + val TEST_EXECUTION_TIMEOUT = testExecutionTimeout private val cache = HashMap, JcContainer>() 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 f08cd19d4d..45024f6ee5 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 @@ -55,6 +55,13 @@ class JcTestExecutor( val classpath: JcClasspath, private val runner: UTestConcreteExecutor ) { + private val memoryScopeCache = mutableMapOf() + data class MemoryScopeDescriptor( + val method: JcTypedMethod, + val state: JcState, + val stringConstants: Map, + val classConstants: Map, + ) /** * Resolves a [JcTest] from a [method] from a [state]. */ @@ -65,14 +72,6 @@ class JcTestExecutor( classConstants: Map, allowSymbolicResult: Boolean ): JcExecution? { - val model = state.models.first() - val mocker = state.memory.mocker as JcMocker - - val resolvedMethodMocks = mocker.symbols - .entries - .groupBy({ model.eval(it.key) }, { it.value }) - .mapValues { it.value.flatten() } - val uTest = createUTest(method, state, stringConstants, classConstants) val concreteResult = runCatching { @@ -85,20 +84,14 @@ class JcTestExecutor( } .getOrNull() + val memoryScopeDescriptor = MemoryScopeDescriptor(method, state, stringConstants, classConstants) + val symbolicResult by lazy { if (allowSymbolicResult) { when (val methodResult = state.methodResult) { is JcMethodResult.JcException -> UTestSymbolicExceptionResult(methodResult.type) is JcMethodResult.Success -> { - val resultScope = MemoryScope( - state.ctx, - model, - state.memory, - stringConstants, - classConstants, - resolvedMethodMocks, - method - ) + val resultScope = createMemoryScope(memoryScopeDescriptor) val resultExpr = resultScope.resolveExpr(methodResult.value, method.returnType) val resultInitializer = resultScope.decoderApi.initializerInstructions() UTestSymbolicSuccessResult(resultInitializer, resultExpr) @@ -145,23 +138,37 @@ class JcTestExecutor( stringConstants: Map, classConstants: Map, ): UTest { - 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 memoryScopeDescriptor = MemoryScopeDescriptor(method, state, stringConstants, classConstants) + val memoryScope = createMemoryScope(memoryScopeDescriptor) return memoryScope.createUTest() } + private fun createMemoryScope(descriptor: MemoryScopeDescriptor): MemoryScope = + memoryScopeCache.getOrPut(descriptor) { + val model = descriptor.state.models.first() + val ctx = descriptor.state.ctx + + val mocker = descriptor.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() } + + MemoryScope( + ctx, + model, + model, + descriptor.stringConstants, + descriptor.classConstants, + resolvedMethodMocks, + descriptor.method, + ) + } + @Suppress("UNUSED_PARAMETER") private fun resolveCoverage(method: JcTypedMethod, state: JcState): JcCoverage { // TODO: extract coverage From cf23242929bdc6922f04882c1c0c23cc8211c39e Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Thu, 21 Dec 2023 14:55:10 +0300 Subject: [PATCH 14/26] Fix enablement of useExperimentalEngine --- .../org/utbot/intellij/plugin/ui/GenerateTestsDialogWindow.kt | 1 + 1 file changed, 1 insertion(+) 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 ad5f78f6a7..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 @@ -908,6 +908,7 @@ class GenerateTestsDialogWindow(val model: GenerateTestsModel) : DialogWrapper(m useExperimentalEngine.isSelected = (settings.symbolicEngineSource == SymbolicEngineSource.Usvm && model.projectType == ProjectType.PureJvm) + useExperimentalEngine.isEnabled = model.projectType == ProjectType.PureJvm mockStrategies.item = when (model.projectType) { ProjectType.Spring -> From 0108b0506019161ad0ff42aef08bff8987287946 Mon Sep 17 00:00:00 2001 From: Kirill Shishin Date: Thu, 21 Dec 2023 22:38:33 +0300 Subject: [PATCH 15/26] Refactoring --- .../org/utbot/engine/UsvmSymbolicEngine.kt | 17 ++-- .../org/utbot/contest/usvm/ContestUsvm.kt | 4 +- .../kotlin/org/utbot/usvm/jc/JcContainer.kt | 14 ++-- .../org/utbot/usvm/jc/JcTestExecutor.kt | 77 +++++++++---------- 4 files changed, 52 insertions(+), 60 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt index fe6c29539d..82716267b2 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -44,6 +44,7 @@ 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.jc.MemoryScopeDescriptor import org.utbot.usvm.machine.analyzeAsync import java.io.File import java.util.concurrent.CancellationException @@ -60,8 +61,6 @@ object UsvmSymbolicEngine { timeoutMillis: Long ): List> { - JcContainer.specifyContainerTimeout(UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis) - val collectedExecutions = mutableListOf>() val classpathFiles = classpath.split(File.pathSeparator).map { File(it) } @@ -154,14 +153,16 @@ object UsvmSymbolicEngine { return realJcExecution } + val memoryScopeDescriptor = MemoryScopeDescriptor( + state.entrypoint.typedMethod, + state, + jcMachine.stringConstants, + jcMachine.classConstants, + ) + return JcExecution( method = state.entrypoint.typedMethod, - uTest = executor.createUTest( - method = state.entrypoint.typedMethod, - state = state, - stringConstants = jcMachine.stringConstants, - classConstants = jcMachine.classConstants, - ), + uTest = executor.createUTest(memoryScopeDescriptor), uTestExecutionResultWrappers = emptySequence(), coverage = JcCoverage(emptyMap()), ) 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 5271bcaaa7..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 @@ -50,7 +50,7 @@ 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 @@ -168,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 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 061c658ce8..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 @@ -12,8 +12,9 @@ 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 {} @@ -79,7 +80,7 @@ class JcContainer( pathToJava = javaHome.absolutePath, ), persistenceLocation, - TEST_EXECUTION_TIMEOUT + testExecutionTimeout ) runBlocking { db.awaitBackgroundJobs() @@ -93,13 +94,8 @@ class JcContainer( } companion object : AutoCloseable { - fun specifyContainerTimeout(timeout: Long) { - testExecutionTimeout = timeout.seconds - } - - private var testExecutionTimeout = 1.seconds - - val TEST_EXECUTION_TIMEOUT = testExecutionTimeout + val testExecutionTimeout + get() = UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis.milliseconds private val cache = HashMap, JcContainer>() 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 45024f6ee5..d33ed76661 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 @@ -45,6 +45,13 @@ import org.usvm.model.UModelBase private val logger = KotlinLogging.logger {} +data class MemoryScopeDescriptor( + val method: JcTypedMethod, + val state: JcState, + val stringConstants: Map, + val classConstants: Map, +) + /** * A class, responsible for resolving a single [JcExecution] for a specific method from a symbolic state. * @@ -55,13 +62,8 @@ class JcTestExecutor( val classpath: JcClasspath, private val runner: UTestConcreteExecutor ) { - private val memoryScopeCache = mutableMapOf() - data class MemoryScopeDescriptor( - val method: JcTypedMethod, - val state: JcState, - val stringConstants: Map, - val classConstants: Map, - ) + + /** * Resolves a [JcTest] from a [method] from a [state]. */ @@ -72,8 +74,9 @@ class JcTestExecutor( classConstants: Map, allowSymbolicResult: Boolean ): JcExecution? { - val uTest = createUTest(method, state, stringConstants, classConstants) + val memoryScopeDescriptor = MemoryScopeDescriptor(method, state, stringConstants, classConstants) + val uTest = createUTest(memoryScopeDescriptor) val concreteResult = runCatching { runBlocking { UTestConcreteExecutionResult(runner.executeAsync(uTest)) @@ -84,7 +87,6 @@ class JcTestExecutor( } .getOrNull() - val memoryScopeDescriptor = MemoryScopeDescriptor(method, state, stringConstants, classConstants) val symbolicResult by lazy { if (allowSymbolicResult) { @@ -132,42 +134,35 @@ class JcTestExecutor( ) } - fun createUTest( - method: JcTypedMethod, - state: JcState, - stringConstants: Map, - classConstants: Map, - ): UTest { - val memoryScopeDescriptor = MemoryScopeDescriptor(method, state, stringConstants, classConstants) + fun createUTest(memoryScopeDescriptor: MemoryScopeDescriptor): UTest { val memoryScope = createMemoryScope(memoryScopeDescriptor) - return memoryScope.createUTest() } - private fun createMemoryScope(descriptor: MemoryScopeDescriptor): MemoryScope = - memoryScopeCache.getOrPut(descriptor) { - val model = descriptor.state.models.first() - val ctx = descriptor.state.ctx - - val mocker = descriptor.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() } - - MemoryScope( - ctx, - model, - model, - descriptor.stringConstants, - descriptor.classConstants, - resolvedMethodMocks, - descriptor.method, - ) - } + private fun createMemoryScope(descriptor: MemoryScopeDescriptor): MemoryScope { + val model = descriptor.state.models.first() + val ctx = descriptor.state.ctx + + val mocker = descriptor.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() } + + return MemoryScope( + ctx, + model, + model, + descriptor.stringConstants, + descriptor.classConstants, + resolvedMethodMocks, + descriptor.method, + ) + } + @Suppress("UNUSED_PARAMETER") private fun resolveCoverage(method: JcTypedMethod, state: JcState): JcCoverage { From 06529ea370491177052e44434af8d847571958fd Mon Sep 17 00:00:00 2001 From: Kirill Shishin Date: Thu, 21 Dec 2023 23:11:24 +0300 Subject: [PATCH 16/26] Add assemble models in USVM symbolic engine --- .../main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt index 82716267b2..22ab401354 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -18,6 +18,7 @@ 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 @@ -28,6 +29,8 @@ 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 @@ -112,6 +115,13 @@ object UsvmSymbolicEngine { runStandardConcreteExecution(concreteExecutor, executableId, initialState) } + if(utResult is UtExecution) { + val assembleModelGenerator = AssembleModelGenerator(executableId.classId.packageName) + utResult.mapModels(UtModelDeepMapper { model -> + assembleModelGenerator.createAssembleModels(listOf(model)).getValue(model) + }) + } + utResult?.let { collectedExecutions.add(executableId to it) } From aba7c4f03cec44328f5348ff81bb902f33887d06 Mon Sep 17 00:00:00 2001 From: Kirill Shishin Date: Fri, 22 Dec 2023 02:08:33 +0300 Subject: [PATCH 17/26] Update utResult value --- .../src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt index 22ab401354..d93e093810 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -103,7 +103,7 @@ object UsvmSymbolicEngine { utilMethodProvider = UtilMethodProviderPlaceholder, ) - val utResult = runCatching { + var utResult = runCatching { executionConverter.convert() }.getOrElse { e -> logger.warn(e) { "JcToUtExecutionConverter.convert(${jcExecution.method.method}) failed" } @@ -117,7 +117,7 @@ object UsvmSymbolicEngine { if(utResult is UtExecution) { val assembleModelGenerator = AssembleModelGenerator(executableId.classId.packageName) - utResult.mapModels(UtModelDeepMapper { model -> + utResult = utResult.mapModels(UtModelDeepMapper { model -> assembleModelGenerator.createAssembleModels(listOf(model)).getValue(model) }) } From fe1cee7431f92d67e3d4d787b72c5848db4f7556 Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Fri, 22 Dec 2023 12:08:15 +0300 Subject: [PATCH 18/26] Resrtore Python IDE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0a58b6796a..b482a13060 100644 --- a/gradle.properties +++ b/gradle.properties @@ -20,7 +20,7 @@ buildType=NOJS # IDE types that supports appropriate language javaIde=IC,IU -pythonIde=PC,PY +pythonIde=IC,IU,PC,PY jsIde=IU,PY jsBuild=ALL goIde=IU,GO From 351c10dda0981439ebaf34bc61602fef0929fecd Mon Sep 17 00:00:00 2001 From: Egor Kulikov Date: Fri, 22 Dec 2023 12:38:55 +0300 Subject: [PATCH 19/26] Do not initialize Jc-flow if timeout = 0 --- .../src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt index d93e093810..2f625f0652 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -64,6 +64,10 @@ object UsvmSymbolicEngine { timeoutMillis: Long ): List> { + if (timeoutMillis == 0L) { + return emptyList() + } + val collectedExecutions = mutableListOf>() val classpathFiles = classpath.split(File.pathSeparator).map { File(it) } From aa84d2ac6eac5f21c28ffd243abea8116e87ee7a Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 22 Dec 2023 15:11:09 +0300 Subject: [PATCH 20/26] Fix `modificationsChain` mapping when removing `UtilMethodProviderPlaceholder` --- .../domain/builtin/UtilMethodProviderPlaceholder.kt | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) 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 index 2f70590b83..2810ea5274 100644 --- 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 @@ -20,13 +20,10 @@ val utilClassIdPlaceholder = utJavaUtilsClassId object UtilMethodProviderPlaceholder : UtilMethodProvider(utilClassIdPlaceholder) fun UtModel.shallowlyFixUtilClassIds(actualUtilClassId: ClassId) = when (this) { - is UtAssembleModel -> UtAssembleModel( - id = id, - classId = classId, - modelName = modelName, - instantiationCall = instantiationCall.shallowlyFixUtilClassId(actualUtilClassId), - origin = origin, - modificationsChainProvider = { modificationsChain.map { it.shallowlyFixUtilClassId(actualUtilClassId) } } + is UtAssembleModel -> copy( + modificationsChain = modificationsChain.map { statement -> + statement.shallowlyFixUtilClassId(actualUtilClassId) + } ) else -> this } From fd47bc7046010c1416636e8a9aebf2810eb647cd Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 22 Dec 2023 15:42:31 +0300 Subject: [PATCH 21/26] Make `UtSymbolicExecution.copy` method also copy `containsMocking` field --- .../src/main/kotlin/org/utbot/framework/plugin/api/Api.kt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) 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 0b5493d89a..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 @@ -281,7 +281,9 @@ class UtSymbolicExecution( summary = summary, testMethodName = testMethodName, displayName = displayName - ) + ).also { + it.containsMocking = containsMocking + } override fun toString(): String = buildString { append("UtSymbolicExecution(") @@ -328,7 +330,9 @@ class UtSymbolicExecution( summary = summary, testMethodName = testMethodName, displayName = displayName - ) + ).also { + it.containsMocking = containsMocking + } } /** From e30f88782b2d98b5e7d32ec7294c83db557a5572 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 22 Dec 2023 15:58:51 +0300 Subject: [PATCH 22/26] Set `zip64 = true` in `:utbot-cli:jar` Gradle task --- utbot-cli/build.gradle | 1 + 1 file changed, 1 insertion(+) 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' From c8fc4a592d472275a87882db03740c482e196330 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 22 Dec 2023 16:03:57 +0300 Subject: [PATCH 23/26] Revert redundant refactoring --- .../org/utbot/engine/UsvmSymbolicEngine.kt | 15 ++--- .../org/utbot/usvm/jc/JcTestExecutor.kt | 62 +++++++++---------- 2 files changed, 36 insertions(+), 41 deletions(-) diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt index 2f625f0652..49b5b123da 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UsvmSymbolicEngine.kt @@ -47,7 +47,6 @@ 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.jc.MemoryScopeDescriptor import org.utbot.usvm.machine.analyzeAsync import java.io.File import java.util.concurrent.CancellationException @@ -167,16 +166,14 @@ object UsvmSymbolicEngine { return realJcExecution } - val memoryScopeDescriptor = MemoryScopeDescriptor( - state.entrypoint.typedMethod, - state, - jcMachine.stringConstants, - jcMachine.classConstants, - ) - return JcExecution( method = state.entrypoint.typedMethod, - uTest = executor.createUTest(memoryScopeDescriptor), + uTest = executor.createUTest( + method = state.entrypoint.typedMethod, + state = state, + stringConstants = jcMachine.stringConstants, + classConstants = jcMachine.classConstants, + ), uTestExecutionResultWrappers = emptySequence(), coverage = JcCoverage(emptyMap()), ) 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 d33ed76661..f08cd19d4d 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 @@ -45,13 +45,6 @@ import org.usvm.model.UModelBase private val logger = KotlinLogging.logger {} -data class MemoryScopeDescriptor( - val method: JcTypedMethod, - val state: JcState, - val stringConstants: Map, - val classConstants: Map, -) - /** * A class, responsible for resolving a single [JcExecution] for a specific method from a symbolic state. * @@ -62,8 +55,6 @@ class JcTestExecutor( val classpath: JcClasspath, private val runner: UTestConcreteExecutor ) { - - /** * Resolves a [JcTest] from a [method] from a [state]. */ @@ -74,9 +65,16 @@ class JcTestExecutor( classConstants: Map, allowSymbolicResult: Boolean ): JcExecution? { - val memoryScopeDescriptor = MemoryScopeDescriptor(method, state, stringConstants, classConstants) + val model = state.models.first() + val mocker = state.memory.mocker as JcMocker + + val resolvedMethodMocks = mocker.symbols + .entries + .groupBy({ model.eval(it.key) }, { it.value }) + .mapValues { it.value.flatten() } + + val uTest = createUTest(method, state, stringConstants, classConstants) - val uTest = createUTest(memoryScopeDescriptor) val concreteResult = runCatching { runBlocking { UTestConcreteExecutionResult(runner.executeAsync(uTest)) @@ -87,13 +85,20 @@ class JcTestExecutor( } .getOrNull() - val symbolicResult by lazy { if (allowSymbolicResult) { when (val methodResult = state.methodResult) { is JcMethodResult.JcException -> UTestSymbolicExceptionResult(methodResult.type) is JcMethodResult.Success -> { - val resultScope = createMemoryScope(memoryScopeDescriptor) + val resultScope = MemoryScope( + state.ctx, + model, + state.memory, + stringConstants, + classConstants, + resolvedMethodMocks, + method + ) val resultExpr = resultScope.resolveExpr(methodResult.value, method.returnType) val resultInitializer = resultScope.decoderApi.initializerInstructions() UTestSymbolicSuccessResult(resultInitializer, resultExpr) @@ -134,16 +139,16 @@ class JcTestExecutor( ) } - fun createUTest(memoryScopeDescriptor: MemoryScopeDescriptor): UTest { - val memoryScope = createMemoryScope(memoryScopeDescriptor) - return memoryScope.createUTest() - } - - private fun createMemoryScope(descriptor: MemoryScopeDescriptor): MemoryScope { - val model = descriptor.state.models.first() - val ctx = descriptor.state.ctx + fun createUTest( + method: JcTypedMethod, + state: JcState, + stringConstants: Map, + classConstants: Map, + ): UTest { + val model = state.models.first() + val ctx = state.ctx - val mocker = descriptor.state.memory.mocker as JcMocker + val mocker = state.memory.mocker as JcMocker // val staticMethodMocks = mocker.statics TODO global mocks????????????????????????? val methodMocks = mocker.symbols @@ -152,17 +157,10 @@ class JcTestExecutor( .groupBy({ model.eval(it.key) }, { it.value }) .mapValues { it.value.flatten() } - return MemoryScope( - ctx, - model, - model, - descriptor.stringConstants, - descriptor.classConstants, - resolvedMethodMocks, - descriptor.method, - ) - } + val memoryScope = MemoryScope(ctx, model, model, stringConstants, classConstants, resolvedMethodMocks, method) + return memoryScope.createUTest() + } @Suppress("UNUSED_PARAMETER") private fun resolveCoverage(method: JcTypedMethod, state: JcState): JcCoverage { From c89b3b222f461f24f49da5c73119e7be38d679c3 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 22 Dec 2023 16:08:58 +0300 Subject: [PATCH 24/26] Use same `memoryScope` to resolve `uTest` and `symbolicResult`. --- .../org/utbot/usvm/jc/JcTestExecutor.kt | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) 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 f08cd19d4d..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 @@ -65,15 +65,8 @@ class JcTestExecutor( classConstants: Map, allowSymbolicResult: Boolean ): JcExecution? { - val model = state.models.first() - val mocker = state.memory.mocker as JcMocker - - val resolvedMethodMocks = mocker.symbols - .entries - .groupBy({ model.eval(it.key) }, { it.value }) - .mapValues { it.value.flatten() } - - val uTest = createUTest(method, state, stringConstants, classConstants) + val memoryScope = createMemoryScope(state, stringConstants, classConstants, method) + val uTest = memoryScope.createUTest() val concreteResult = runCatching { runBlocking { @@ -90,17 +83,8 @@ class JcTestExecutor( when (val methodResult = state.methodResult) { is JcMethodResult.JcException -> UTestSymbolicExceptionResult(methodResult.type) is JcMethodResult.Success -> { - val resultScope = MemoryScope( - state.ctx, - model, - state.memory, - stringConstants, - classConstants, - resolvedMethodMocks, - method - ) - val resultExpr = resultScope.resolveExpr(methodResult.value, method.returnType) - val resultInitializer = resultScope.decoderApi.initializerInstructions() + val resultExpr = memoryScope.resolveExpr(methodResult.value, method.returnType) + val resultInitializer = memoryScope.decoderApi.initializerInstructions() UTestSymbolicSuccessResult(resultInitializer, resultExpr) } @@ -145,6 +129,16 @@ class JcTestExecutor( 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 @@ -158,8 +152,7 @@ class JcTestExecutor( .mapValues { it.value.flatten() } val memoryScope = MemoryScope(ctx, model, model, stringConstants, classConstants, resolvedMethodMocks, method) - - return memoryScope.createUTest() + return memoryScope } @Suppress("UNUSED_PARAMETER") From 8831369e3a2372743881b7f7c05748c2cc76d6b7 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 22 Dec 2023 16:13:18 +0300 Subject: [PATCH 25/26] Preserve identity when mapping `MissingState` --- .../utbot/framework/plugin/api/mapper/Utils.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) 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 3e3a41c71a..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 From 392d7848573b992ff141d702584afc318319f932 Mon Sep 17 00:00:00 2001 From: IlyaMuravjov Date: Fri, 22 Dec 2023 16:52:18 +0300 Subject: [PATCH 26/26] Set `zip64 = true` in `:utbot-cli-go:jar`, `:utbot-cli-js:jar`, `:utbot-cli-python:jar` Gradle tasks --- utbot-cli-go/build.gradle | 1 + utbot-cli-js/build.gradle | 1 + utbot-cli-python/build.gradle | 1 + 3 files changed, 3 insertions(+) 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'