diff --git a/.github/workflows/build-and-run-tests-from-branch.yml b/.github/workflows/build-and-run-tests-from-branch.yml index 021a991d4e..9d03aadd17 100644 --- a/.github/workflows/build-and-run-tests-from-branch.yml +++ b/.github/workflows/build-and-run-tests-from-branch.yml @@ -99,6 +99,7 @@ jobs: - name: Run monitoring # secret uploaded using base64 encoding to have one-line output: # cat file | base64 -w 0 + continue-on-error: true run: | chmod +x ./scripts/project/monitoring.sh ./scripts/project/monitoring.sh "${PUSHGATEWAY_HOSTNAME}" "${{ secrets.PUSHGATEWAY_USER }}" "${{ secrets.PUSHGATEWAY_PASSWORD }}" diff --git a/build.gradle.kts b/build.gradle.kts index 94161512a6..78612e3a6d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -53,6 +53,10 @@ allprojects { } } withType { + // uncomment if you want to see loggers output in console + // this is useful if you debug in docker + // testLogging.showStandardStreams = true + // testLogging.showStackTraces = true // set heap size for the test JVM(s) minHeapSize = "128m" maxHeapSize = "3072m" @@ -68,6 +72,9 @@ allprojects { override fun beforeTest(testDescriptor: TestDescriptor) {} override fun afterTest(testDescriptor: TestDescriptor, result: TestResult) { println("[$testDescriptor.classDisplayName] [$testDescriptor.displayName]: $result.resultType, length - ${(result.endTime - result.startTime) / 1000.0} sec") + if (result.resultType == TestResult.ResultType.FAILURE) { + println("Exception: " + result.exception?.stackTraceToString()) + } } override fun afterSuite(testDescriptor: TestDescriptor, result: TestResult) { diff --git a/docs/ResultAndErrorHandlingApiOfTheInstrumentedProcess.md b/docs/ResultAndErrorHandlingApiOfTheInstrumentedProcess.md new file mode 100644 index 0000000000..bc36bfc5d9 --- /dev/null +++ b/docs/ResultAndErrorHandlingApiOfTheInstrumentedProcess.md @@ -0,0 +1,55 @@ +# Result & Error Handling API of the Instrumented Process + +## Terminology + +- The _instrumented process_ is an external process used for the isolated invocation. +- The `ConcreteExecutor` is a class which provides smooth and concise interaction with the _instrumented process_. It works in the _main process_. +- A client is an object which directly uses the `ConcreteExecutor`, so it works in the _main process_ as well. +- An _Instrumentation_ is an object which has to be passed to the `ConcreteExecutor`. It defines the logic of invocation and bytecode instrumentation in the _instrumented process_. + +## Common + +Basically, if any exception happens inside the _instrumented process_, it is rethrown to the client process via RD. +- Errors which do not cause the termination of the _instrumented process_ are wrapped in `InstrumentedProcessError`. Process won't be restarted, so client's requests will be handled by the same process. We believe, that the state of the _instrumented process_ is consistent, but in some tricky situations it **may be not**. Such situations should be reported as bugs. +- Some of the errors lead to the instant death of the _instrumented process_. Such errors are wrapped in `InstrumentedProcessDeathException`. Before processing the next request, the _instrumented process_ will be restarted automatically, but it can take some time. + +The extra logic of error and result handling depends on the provided instrumentation. + +## UtExecutionInstrumentation + +The next sections are related only to the `UtExecutionInstrumentation` passed to the _instrumented process_. + +The calling of `ConcreteExecutor::executeAsync` instantiated by the `UtExecutionInstrumentation` can lead to the three possible situations: +- `InstrumentedProcessDeathException` occurs. Usually, this situation means there is an internal issue in the _instrumented process_, but, nevertheless, this exception should be handled by the client. +- `InstrumentedProcessError` occurs. It also means an internal issue and should be handled by the client. Sometimes it happens because the client provided the wrong configuration or parameters, but the _instrumented process_ **can't determine exactly** what's wrong with the client's data. The cause contains the description of the phase which threw the exception. +- No exception occurs, so the `UtConcreteExecutionResult` is returned. It means that everything went well during the invocation or something broke down because of the wrong input, and the _instrumented process_ **knows exactly** what's wrong with the client's input. The _instrumented process_ guarantees that the state **is consistent**. The exact reason of failure is a `UtConcreteExecutionResult::result` field. It includes: + - `UtSandboxFailure` --- violation of permissions. + - `UtTimeoutException` --- the test execution time exceeds the provided time limit (`UtConcreteExecutionData::timeout`). + - `UtExecutionSuccess` --- the test executed successfully. + - `UtExplicitlyThrownException` --- the target method threw exception explicitly (via `throw` instruction). + - `UtImplicitlyThrownException` --- the target method threw exception implicitly (`NPE`, `OOB`, etc. or it was thrown inside the system library) + - etc. + +### How the error handling works + +The pipeline of the `UtExecutionInstrumentation::invoke` consists of 6 phases: +- `ValueConstructionPhase` --- constructs values from the models. +- `PreparationPhase` --- prepares statics, etc. +- `InvocationPhase` --- invokes the target method. +- `StatisticsCollectionPhase` --- collects the coverage and execution-related data. +- `ModelConstructionPhase` --- constructs the result models from the heap objects (`Any?`). +- `PostprocessingPhase` --- restores statics, clear mocks, etc. + +Each phase can throw two kinds of exceptions: +- `ExecutionPhaseStop` --- indicates that the phase want to stop the invocation of the pipeline completely, because it's already has a result. The returned result is the `ExecutionPhaseStop::result` field. +- `ExecutionPhaseError` --- indicates that an unexpected error happened inside the phase execution, so it's rethrown to the main process. + +The `PhasesController::computeConcreteExecutionResult` then matches on the exception type and rethrows the exception if it's an `ExecutionPhaseError`, and returns the result if it's an `ExecutionPhaseStop`. + +### Timeout + +There is a time limit on the concrete execution, so the `UtExecutionInstrumentation::invoke` method must respect it. We wrap phases which can take a long time with the `executePhaseInTimeout` block, which internally keeps and updates the elapsed time. If any wrapped phase exceeds the timeout, we return the `TimeoutException` as the result of that phase. + +The clients cannot depend that cancellation request immediately breaks the invocation inside the _instrumented process_. The invocation is guaranteed to finish in the time of passed timeout. It may or **may not** finish earlier. Already started query in instrumented process is **uncancellable** - this is by design. + +Even if the `TimeoutException` occurs, the _instrumented process_ is ready to process new requests. diff --git a/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt b/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt index 83ec7bb00c..1c355317f9 100644 --- a/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt +++ b/utbot-core/src/main/kotlin/org/utbot/common/StopWatch.kt @@ -52,7 +52,7 @@ class StopWatch { } } - fun get(unit: TimeUnit) = lock.withLockInterruptibly { + fun get(unit: TimeUnit = TimeUnit.MILLISECONDS) = lock.withLockInterruptibly { unsafeUpdate() unit.convert(elapsedMillis, TimeUnit.MILLISECONDS) } diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt index 02bf1bc3f3..e3900a4982 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/UtSettings.kt @@ -277,7 +277,7 @@ object UtSettings : AbstractSettings(logger, defaultKeyForSettingsPath, defaultS /** * Timeout for specific concrete execution (in milliseconds). */ - var concreteExecutionTimeoutInInstrumentedProcess: Long by getLongProperty( + var concreteExecutionDefaultTimeoutInInstrumentedProcessMillis: Long by getLongProperty( DEFAULT_EXECUTION_TIMEOUT_IN_INSTRUMENTED_PROCESS_MS ) diff --git a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt index 20748404fa..f2c50278ca 100644 --- a/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt +++ b/utbot-framework-api/src/main/kotlin/org/utbot/framework/plugin/api/UtExecutionResult.kt @@ -1,108 +1,108 @@ -package org.utbot.framework.plugin.api - -import org.utbot.framework.plugin.api.visible.UtStreamConsumingException -import java.io.File -import java.util.LinkedList - -sealed class UtExecutionResult - -data class UtExecutionSuccess(val model: UtModel) : UtExecutionResult() { - override fun toString() = "$model" -} - -sealed class UtExecutionFailure : UtExecutionResult() { - abstract val exception: Throwable - - /** - * Represents the most inner exception in the failure. - * Often equals to [exception], but is wrapped exception in [UtStreamConsumingException]. - */ - open val rootCauseException: Throwable - get() = exception -} - -data class UtOverflowFailure( - override val exception: Throwable, -) : UtExecutionFailure() - -data class UtSandboxFailure( - override val exception: Throwable -) : UtExecutionFailure() - -data class UtStreamConsumingFailure( - override val exception: UtStreamConsumingException, -) : UtExecutionFailure() { - override val rootCauseException: Throwable - get() = exception.innerExceptionOrAny -} - -/** - * unexpectedFail (when exceptions such as NPE, IOBE, etc. appear, but not thrown by a user, applies both for function under test and nested calls ) - * expectedCheckedThrow (when function under test or nested call explicitly says that checked exception could be thrown and throws it) - * expectedUncheckedThrow (when there is a throw statement for unchecked exception inside of function under test) - * unexpectedUncheckedThrow (in case when there is unchecked exception thrown from nested call) - */ -data class UtExplicitlyThrownException( - override val exception: Throwable, - val fromNestedMethod: Boolean -) : UtExecutionFailure() - -data class UtImplicitlyThrownException( - override val exception: Throwable, - val fromNestedMethod: Boolean -) : UtExecutionFailure() - -class TimeoutException(s: String) : Exception(s) - -data class UtTimeoutException(override val exception: TimeoutException) : UtExecutionFailure() - -/** - * Indicates failure in concrete execution. - * For now it is explicitly throwing by ConcreteExecutor in case instrumented process death. - */ -class ConcreteExecutionFailureException(cause: Throwable, errorFile: File, val processStdout: List) : - Exception( - buildString { - appendLine() - appendLine("----------------------------------------") - appendLine("The instrumented process is dead") - appendLine("Cause:\n${cause.message}") - appendLine("Last 1000 lines of the error log ${errorFile.absolutePath}:") - appendLine("----------------------------------------") - errorFile.useLines { lines -> - val lastLines = LinkedList() - for (line in lines) { - lastLines.add(line) - if (lastLines.size > 1000) { - lastLines.removeFirst() - } - } - lastLines.forEach { appendLine(it) } - } - appendLine("----------------------------------------") - }, - cause - ) - -data class UtConcreteExecutionFailure(override val exception: ConcreteExecutionFailureException) : UtExecutionFailure() - -val UtExecutionResult.isSuccess: Boolean - get() = this is UtExecutionSuccess - -val UtExecutionResult.isFailure: Boolean - get() = this is UtExecutionFailure - -inline fun UtExecutionResult.onSuccess(action: (model: UtModel) -> Unit): UtExecutionResult { - if (this is UtExecutionSuccess) action(model) - return this -} - -inline fun UtExecutionResult.onFailure(action: (exception: Throwable) -> Unit): UtExecutionResult { - if (this is UtExecutionFailure) action(rootCauseException) - return this -} - -fun UtExecutionResult.exceptionOrNull(): Throwable? = when (this) { - is UtExecutionFailure -> rootCauseException - is UtExecutionSuccess -> null -} +package org.utbot.framework.plugin.api + +import org.utbot.framework.plugin.api.visible.UtStreamConsumingException +import java.io.File +import java.util.LinkedList + +sealed class UtExecutionResult + +data class UtExecutionSuccess(val model: UtModel) : UtExecutionResult() { + override fun toString() = "$model" +} + +sealed class UtExecutionFailure : UtExecutionResult() { + abstract val exception: Throwable + + /** + * Represents the most inner exception in the failure. + * Often equals to [exception], but is wrapped exception in [UtStreamConsumingException]. + */ + open val rootCauseException: Throwable + get() = exception +} + +data class UtOverflowFailure( + override val exception: Throwable, +) : UtExecutionFailure() + +data class UtSandboxFailure( + override val exception: Throwable +) : UtExecutionFailure() + +data class UtStreamConsumingFailure( + override val exception: UtStreamConsumingException, +) : UtExecutionFailure() { + override val rootCauseException: Throwable + get() = exception.innerExceptionOrAny +} + +/** + * unexpectedFail (when exceptions such as NPE, IOBE, etc. appear, but not thrown by a user, applies both for function under test and nested calls ) + * expectedCheckedThrow (when function under test or nested call explicitly says that checked exception could be thrown and throws it) + * expectedUncheckedThrow (when there is a throw statement for unchecked exception inside of function under test) + * unexpectedUncheckedThrow (in case when there is unchecked exception thrown from nested call) + */ +data class UtExplicitlyThrownException( + override val exception: Throwable, + val fromNestedMethod: Boolean +) : UtExecutionFailure() + +data class UtImplicitlyThrownException( + override val exception: Throwable, + val fromNestedMethod: Boolean +) : UtExecutionFailure() + +class TimeoutException(s: String) : Exception(s) + +data class UtTimeoutException(override val exception: TimeoutException) : UtExecutionFailure() + +/** + * Indicates failure in concrete execution. + * For now it is explicitly throwing by ConcreteExecutor in case instrumented process death. + */ +class InstrumentedProcessDeathException(cause: Throwable, errorFile: File, val processStdout: List) : + Exception( + buildString { + appendLine() + appendLine("----------------------------------------") + appendLine("The instrumented process is dead") + appendLine("Cause:\n${cause.message}") + appendLine("Last 1000 lines of the error log ${errorFile.absolutePath}:") + appendLine("----------------------------------------") + errorFile.useLines { lines -> + val lastLines = LinkedList() + for (line in lines) { + lastLines.add(line) + if (lastLines.size > 1000) { + lastLines.removeFirst() + } + } + lastLines.forEach { appendLine(it) } + } + appendLine("----------------------------------------") + }, + cause + ) + +data class UtConcreteExecutionFailure(override val exception: InstrumentedProcessDeathException) : UtExecutionFailure() + +val UtExecutionResult.isSuccess: Boolean + get() = this is UtExecutionSuccess + +val UtExecutionResult.isFailure: Boolean + get() = this is UtExecutionFailure + +inline fun UtExecutionResult.onSuccess(action: (model: UtModel) -> Unit): UtExecutionResult { + if (this is UtExecutionSuccess) action(model) + return this +} + +inline fun UtExecutionResult.onFailure(action: (exception: Throwable) -> Unit): UtExecutionResult { + if (this is UtExecutionFailure) action(rootCauseException) + return this +} + +fun UtExecutionResult.exceptionOrNull(): Throwable? = when (this) { + is UtExecutionFailure -> rootCauseException + is UtExecutionSuccess -> null +} diff --git a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt index 3ae7ca31d8..ce360c7d9a 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/Traverser.kt @@ -7,6 +7,7 @@ import kotlinx.collections.immutable.persistentSetOf import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentMap import kotlinx.collections.immutable.toPersistentSet +import mu.KotlinLogging import org.utbot.common.WorkaroundReason.HACK import org.utbot.framework.UtSettings.ignoreStaticsFromTrustedLibraries import org.utbot.common.WorkaroundReason.IGNORE_STATICS_FROM_TRUSTED_LIBRARIES @@ -224,6 +225,7 @@ import java.lang.reflect.TypeVariable import java.lang.reflect.WildcardType private val CAUGHT_EXCEPTION = LocalVariable("@caughtexception") +private val logger = KotlinLogging.logger {} class Traverser( private val methodUnderTest: ExecutableId, 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 10a9138a8c..f7fc5ca2cf 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/engine/UtBotSymbolicEngine.kt @@ -48,9 +48,10 @@ import org.utbot.instrumentation.instrumentation.execution.UtExecutionInstrument import soot.jimple.Stmt import soot.tagkit.ParamNamesTag import java.lang.reflect.Method +import kotlin.math.min import kotlin.system.measureTimeMillis -val logger = KotlinLogging.logger {} +private val logger = KotlinLogging.logger {} val pathLogger = KotlinLogging.logger(logger.name + ".path") //in future we should put all timeouts here @@ -247,7 +248,7 @@ class UtBotSymbolicEngine( try { val concreteExecutionResult = - concreteExecutor.executeConcretely(methodUnderTest, stateBefore, instrumentation) + concreteExecutor.executeConcretely(methodUnderTest, stateBefore, instrumentation, UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis) if (concreteExecutionResult.violatesUtMockAssumption()) { logger.debug { "Generated test case violates the UtMock assumption: $concreteExecutionResult" } @@ -268,7 +269,7 @@ class UtBotSymbolicEngine( logger.debug { "concolicStrategy<${methodUnderTest}>: returned $concreteUtExecution" } } catch (e: CancellationException) { logger.debug(e) { "Cancellation happened" } - } catch (e: ConcreteExecutionFailureException) { + } catch (e: InstrumentedProcessDeathException) { emitFailedConcreteExecutionResult(stateBefore, e) } catch (e: Throwable) { emit(UtError("Concrete execution failed", e)) @@ -353,7 +354,14 @@ class UtBotSymbolicEngine( names, listOf(transform(ValueProvider.of(defaultValueProviders(defaultIdGenerator)))) ) { thisInstance, descr, values -> - if (controller.job?.isActive == false || System.currentTimeMillis() >= until) { + val diff = until - System.currentTimeMillis() + val thresholdMillisForFuzzingOperation = 0 // may be better use 10-20 millis as it might not be possible + // to concretely execute that values because request to instrumentation process involves + // 1. serializing/deserializing it with kryo + // 2. sending over rd + // 3. concrete execution itself + // 4. analyzing concrete result + if (controller.job?.isActive == false || diff <= thresholdMillisForFuzzingOperation) { logger.info { "Fuzzing overtime: $methodUnderTest" } logger.info { "Test created by fuzzer: $testEmittedByFuzzer" } return@runJavaFuzzing BaseFeedback(result = Trie.emptyNode(), control = Control.STOP) @@ -362,10 +370,11 @@ class UtBotSymbolicEngine( val initialEnvironmentModels = EnvironmentModels(thisInstance?.model, values.map { it.model }, mapOf()) val concreteExecutionResult: UtConcreteExecutionResult? = try { - concreteExecutor.executeConcretely(methodUnderTest, initialEnvironmentModels, listOf()) + val timeoutMillis = min(UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis, diff) + concreteExecutor.executeConcretely(methodUnderTest, initialEnvironmentModels, listOf(), timeoutMillis) } catch (e: CancellationException) { logger.debug { "Cancelled by timeout" }; null - } catch (e: ConcreteExecutionFailureException) { + } catch (e: InstrumentedProcessDeathException) { emitFailedConcreteExecutionResult(initialEnvironmentModels, e); null } catch (e: Throwable) { emit(UtError("Default concrete execution failed", e)); null @@ -418,7 +427,7 @@ class UtBotSymbolicEngine( private suspend fun FlowCollector.emitFailedConcreteExecutionResult( stateBefore: EnvironmentModels, - e: ConcreteExecutionFailureException + e: InstrumentedProcessDeathException ) { val failedConcreteExecution = UtFailedExecution( stateBefore = stateBefore, @@ -508,7 +517,8 @@ class UtBotSymbolicEngine( val concreteExecutionResult = concreteExecutor.executeConcretely( methodUnderTest, stateBefore, - instrumentation + instrumentation, + UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis ) if (concreteExecutionResult.violatesUtMockAssumption()) { @@ -525,8 +535,12 @@ class UtBotSymbolicEngine( emit(concolicUtExecution) logger.debug { "processResult<${methodUnderTest}>: returned $concolicUtExecution" } } - } catch (e: ConcreteExecutionFailureException) { + } catch (e: InstrumentedProcessDeathException) { emitFailedConcreteExecutionResult(stateBefore, e) + } catch (e: CancellationException) { + logger.debug(e) { "Cancellation happened" } + } catch (e: Throwable) { + emit(UtError("Default concrete execution failed", e)); } } @@ -559,14 +573,16 @@ private fun ResolvedModels.constructStateForMethod(methodUnderTest: ExecutableId private suspend fun ConcreteExecutor.executeConcretely( methodUnderTest: ExecutableId, stateBefore: EnvironmentModels, - instrumentation: List + instrumentation: List, + timeoutInMillis: Long ): UtConcreteExecutionResult = executeAsync( methodUnderTest.classId.name, methodUnderTest.signature, arrayOf(), parameters = UtConcreteExecutionData( stateBefore, - instrumentation + instrumentation, + timeoutInMillis ) ).convertToAssemble(methodUnderTest.classId.packageName) diff --git a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt index d3bf6bfbe4..ec8a869fc1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/external/api/UtBotJavaApi.kt @@ -221,7 +221,8 @@ object UtBotJavaApi { arrayOf(), parameters = UtConcreteExecutionData( testInfo.initialState, - instrumentation = emptyList() + instrumentation = emptyList(), + UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis ) ).result } else { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt index ebc78ea8e2..60e5f763f1 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/codegen/tree/CgMethodConstructor.kt @@ -67,7 +67,7 @@ import org.utbot.framework.plugin.api.BuiltinClassId import org.utbot.framework.plugin.api.BuiltinMethodId import org.utbot.framework.plugin.api.ClassId import org.utbot.framework.plugin.api.CodegenLanguage -import org.utbot.framework.plugin.api.ConcreteExecutionFailureException +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException import org.utbot.framework.plugin.api.ConstructorId import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.FieldId @@ -343,7 +343,7 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte } } CRASH -> when (expectedException) { - is ConcreteExecutionFailureException -> { + is InstrumentedProcessDeathException -> { writeWarningAboutCrash() methodInvocationBlock() } @@ -425,7 +425,7 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte protected fun shouldTestPassWithException(execution: UtExecution, exception: Throwable): Boolean { if (exception is AccessControlException) return false // tests with timeout or crash should be processed differently - if (exception is TimeoutException || exception is ConcreteExecutionFailureException) return false + if (exception is TimeoutException || exception is InstrumentedProcessDeathException) return false if (exception is ArtificialError) return false if (UtSettings.treatAssertAsErrorSuite && exception is AssertionError) return false @@ -1844,7 +1844,7 @@ open class CgMethodConstructor(val context: CgContext) : CgContextOwner by conte shouldTestPassWithTimeoutException(currentExecution, exception) -> TIMEOUT else -> when (exception) { is ArtificialError -> ARTIFICIAL - is ConcreteExecutionFailureException -> CRASH + is InstrumentedProcessDeathException -> CRASH is AccessControlException -> CRASH // exception from sandbox else -> FAILING } 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 c86c96e274..f3e04bd7b6 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 @@ -180,7 +180,10 @@ open class TestCaseGenerator( } method2executions.getValue(method) += it } - is UtError -> method2errors.getValue(method).merge(it.description, 1, Int::plus) + is UtError -> { + method2errors.getValue(method).merge(it.description, 1, Int::plus) + logger.error(it.error) { "UtError occurred" } + } } } } catch (e: Exception) { diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/EngineUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/EngineUtils.kt index 1fc405c6d3..3b72edb13c 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/EngineUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/EngineUtils.kt @@ -1,5 +1,7 @@ package org.utbot.framework.util +import mu.KotlinLogging +import org.utbot.common.logException import org.utbot.framework.plugin.api.util.constructor.ValueConstructor import org.utbot.framework.plugin.api.ExecutableId import org.utbot.framework.plugin.api.MissingState @@ -16,6 +18,8 @@ import java.util.concurrent.atomic.AtomicInteger import soot.SootMethod +private val logger = KotlinLogging.logger { } + /** * Gets method or constructor id of SootMethod. */ @@ -52,5 +56,5 @@ fun UtSymbolicExecution.hasThisInstance(): Boolean = when { // An execution must either have this instance or not. // This instance cannot be absent before execution and appear after // as well as it cannot be present before execution and disappear after. - else -> error("Execution configuration must not change between states") + else -> logger.logException { error("Execution configuration must not change between states") } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt index 6b9eedcd73..2ed5c100eb 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/util/UtConcreteExecutionResultUtils.kt @@ -2,6 +2,7 @@ package org.utbot.framework.util import org.utbot.framework.assemble.AssembleModelGenerator import org.utbot.framework.plugin.api.EnvironmentModels +import org.utbot.framework.plugin.api.MissingState import org.utbot.framework.plugin.api.UtExecutionSuccess import org.utbot.framework.plugin.api.UtModel import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult @@ -12,7 +13,7 @@ private fun UtConcreteExecutionResult.updateWithAssembleModels( ): UtConcreteExecutionResult { val toAssemble: (UtModel) -> UtModel = { assembledUtModels.getOrDefault(it, it) } - val resolvedStateAfter = EnvironmentModels( + val resolvedStateAfter = if (stateAfter is MissingState) MissingState else EnvironmentModels( stateAfter.thisInstance?.let { toAssemble(it) }, stateAfter.parameters.map { toAssemble(it) }, stateAfter.statics.mapValues { toAssemble(it.value) } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt index c86ddd0206..422a0bd069 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/ConcreteExecutor.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import mu.KotlinLogging -import org.utbot.framework.plugin.api.ConcreteExecutionFailureException +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException import org.utbot.framework.plugin.api.FieldId import org.utbot.framework.plugin.api.util.UtContext import org.utbot.framework.plugin.api.util.signature @@ -178,7 +178,7 @@ class ConcreteExecutor> p suspend fun withProcess(exclusively: Boolean = false, block: suspend InstrumentedProcess.() -> T): T { fun throwConcreteIfDead(e: Throwable, proc: InstrumentedProcess?) { if (proc?.lifetime?.isAlive != true) { - throw ConcreteExecutionFailureException(e, + throw InstrumentedProcessDeathException(e, instrumentedProcessRunner.errorLogFile, try { proc?.run { process.inputStream.bufferedReader().lines().toList() } ?: emptyList() diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt index 3ab2051f72..9e54e1da7b 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/UtExecutionInstrumentation.kt @@ -4,17 +4,12 @@ import java.security.ProtectionDomain import java.util.IdentityHashMap import kotlin.reflect.jvm.javaMethod import org.utbot.framework.UtSettings +import org.utbot.framework.plugin.api.* import org.utbot.instrumentation.instrumentation.execution.constructors.ConstructOnlyUserClassesOrCachedObjectsStrategy import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor import org.utbot.instrumentation.instrumentation.execution.mock.InstrumentationContext import org.utbot.instrumentation.instrumentation.execution.phases.PhasesController import org.utbot.instrumentation.instrumentation.execution.phases.start -import org.utbot.framework.plugin.api.Coverage -import org.utbot.framework.plugin.api.EnvironmentModels -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.UtExecutionResult -import org.utbot.framework.plugin.api.UtInstrumentation -import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.util.singleExecutableId import org.utbot.instrumentation.instrumentation.ArgumentList import org.utbot.instrumentation.instrumentation.Instrumentation @@ -29,12 +24,12 @@ import org.utbot.instrumentation.instrumentation.mock.MockClassVisitor * @property [stateBefore] is necessary for construction of parameters of a concrete call. * @property [instrumentation] is necessary for mocking static methods and new instances. * @property [timeout] is timeout for specific concrete execution (in milliseconds). - * By default is initialized from [UtSettings.concreteExecutionTimeoutInInstrumentedProcess] + * By default is initialized from [UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis] */ data class UtConcreteExecutionData( val stateBefore: EnvironmentModels, val instrumentation: List, - val timeout: Long = UtSettings.concreteExecutionTimeoutInInstrumentedProcess + val timeout: Long ) class UtConcreteExecutionResult( @@ -86,41 +81,47 @@ object UtExecutionInstrumentation : Instrumentation { return PhasesController( instrumentationContext, traceHandler, - delegateInstrumentation + delegateInstrumentation, + timeout ).computeConcreteExecutionResult { - // construction - val (params, statics, cache) = valueConstructionContext.start { - val params = constructParameters(stateBefore) - val statics = constructStatics(stateBefore) + try { + val (params, statics, cache) = this.executePhaseInTimeout(valueConstructionPhase) { + val params = constructParameters(stateBefore) + val statics = constructStatics(stateBefore) - mock(instrumentations) + // here static methods and instances are mocked + mock(instrumentations) - Triple(params, statics, getCache()) - } + Triple(params, statics, getCache()) + } - // preparation - val savedStatics = preparationContext.start { - val savedStatics = setStaticFields(statics) - resetTrace() - savedStatics - } + // invariants: + // 1. phase must always complete if started as static reset relies on it + // 2. phase must be fast as there are no incremental changes + postprocessingPhase.savedStatics = preparationPhase.start { + val result = setStaticFields(statics) + resetTrace() + result + } - try { // invocation - val concreteResult = invocationContext.start { - invoke(clazz, methodSignature, params.map { it.value }, timeout) + val concreteResult = executePhaseInTimeout(invocationPhase) { + invoke(clazz, methodSignature, params.map { it.value }) } // statistics collection - val coverage = statisticsCollectionContext.start { + val coverage = executePhaseInTimeout(statisticsCollectionPhase) { getCoverage(clazz) } // model construction - val (executionResult, stateAfter) = modelConstructionContext.start { + val (executionResult, stateAfter) = executePhaseInTimeout(modelConstructionPhase) { configureConstructor { this.cache = cache - strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy(pathsToUserClasses, cache) + strategy = ConstructOnlyUserClassesOrCachedObjectsStrategy( + pathsToUserClasses, + cache + ) } val executionResult = convertToExecutionResult(concreteResult, returnClassId) @@ -143,9 +144,9 @@ object UtExecutionInstrumentation : Instrumentation { coverage ) } finally { - // postprocessing - postprocessingContext.start { - resetStaticFields(savedStatics) + postprocessingPhase.start { + resetStaticFields() + valueConstructionPhase.resetMockMethods() } } } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt index ba6f49c717..53fc6a0482 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/constructors/MockValueConstructor.kt @@ -68,7 +68,7 @@ import org.utbot.instrumentation.process.runSandbox // TODO: JIRA:1379 -- Refactor ValueConstructor and MockValueConstructor class MockValueConstructor( private val instrumentationContext: InstrumentationContext -) : Closeable { +) { private val classLoader: ClassLoader get() = utContext.classLoader @@ -533,7 +533,7 @@ class MockValueConstructor( } } - override fun close() { + fun resetMockedMethods() { controllers.forEach { it.close() } } } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ExecutionPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ExecutionPhase.kt new file mode 100644 index 0000000000..bcff67b0f8 --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ExecutionPhase.kt @@ -0,0 +1,30 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import com.jetbrains.rd.util.getLogger +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.rd.logMeasure + +private val logger = getLogger("ExecutionPhase") + +abstract class ExecutionPhaseException(override val message: String) : Exception() + +// Execution will be stopped, exception will be thrown in engine process +class ExecutionPhaseError(phase: String, override val cause: Throwable) : ExecutionPhaseException(phase) + +// Execution will be stopped, but considered successful, result will be returned +class ExecutionPhaseStop(phase: String, val result: UtConcreteExecutionResult) : ExecutionPhaseException(phase) + +interface ExecutionPhase { + fun wrapError(e: Throwable): ExecutionPhaseException +} + +fun T.start(block: T.() -> R): R = + try { + logger.logMeasure(this.javaClass.simpleName) { + this.block() + } + } catch (e: ExecutionPhaseStop) { + throw e + } catch (e: Throwable) { + throw this.wrapError(e) + } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/InvocationContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/InvocationContext.kt deleted file mode 100644 index 5c301e96e0..0000000000 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/InvocationContext.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.utbot.instrumentation.instrumentation.execution.phases - -import org.utbot.common.StopWatch -import org.utbot.common.ThreadBasedExecutor -import org.utbot.framework.plugin.api.TimeoutException -import org.utbot.framework.plugin.api.util.UtContext -import org.utbot.framework.plugin.api.util.utContext -import org.utbot.framework.plugin.api.util.withUtContext -import org.utbot.instrumentation.instrumentation.Instrumentation - -class InvocationPhaseError(cause: Throwable) : PhaseError( - message = "Error during user's code invocation phase", - cause -) - -/** - * This phase is about invoking user's code using [delegateInstrumentation]. - */ -class InvocationContext( - private val delegateInstrumentation: Instrumentation> -) : PhaseContext { - - override fun wrapError(error: Throwable): InvocationPhaseError = - InvocationPhaseError(error) - - fun invoke( - clazz: Class<*>, - methodSignature: String, - params: List, - timeout: Long, - ): Result<*> { - val stopWatch = StopWatch() - val context = UtContext(utContext.classLoader, stopWatch) - val concreteResult = ThreadBasedExecutor.threadLocal.invokeWithTimeout(timeout, stopWatch) { - withUtContext(context) { - delegateInstrumentation.invoke(clazz, methodSignature, params) - } - }?.getOrThrow() as? Result<*> ?: Result.failure(TimeoutException("Timeout $timeout elapsed")) - return concreteResult - } - -} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/InvocationPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/InvocationPhase.kt new file mode 100644 index 0000000000..91ca9faa0d --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/InvocationPhase.kt @@ -0,0 +1,32 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import org.utbot.framework.plugin.api.Coverage +import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.TimeoutException +import org.utbot.framework.plugin.api.UtTimeoutException +import org.utbot.instrumentation.instrumentation.Instrumentation +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult + + +/** + * This phase is about invoking user's code using [delegateInstrumentation]. + */ +class InvocationPhase( + private val delegateInstrumentation: Instrumentation> +) : ExecutionPhase { + + override fun wrapError(e: Throwable): ExecutionPhaseException { + val message = this.javaClass.simpleName + return when(e) { + is TimeoutException -> ExecutionPhaseStop(message, UtConcreteExecutionResult(MissingState, UtTimeoutException(e), Coverage())) + else -> ExecutionPhaseError(message, e) + } + } + + + fun invoke( + clazz: Class<*>, + methodSignature: String, + params: List, + ): Result<*> = delegateInstrumentation.invoke(clazz, methodSignature, params) +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt similarity index 77% rename from utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionContext.kt rename to utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt index 12d57ed0be..29d380104d 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionContext.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ModelConstructionPhase.kt @@ -3,42 +3,30 @@ package org.utbot.instrumentation.instrumentation.execution.phases import java.security.AccessControlException import java.util.IdentityHashMap import org.utbot.common.withAccessibility +import org.utbot.framework.plugin.api.* import org.utbot.instrumentation.instrumentation.execution.constructors.UtCompositeModelStrategy import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor -import org.utbot.framework.plugin.api.ClassId -import org.utbot.framework.plugin.api.EnvironmentModels -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.TimeoutException -import org.utbot.framework.plugin.api.UtConcreteValue -import org.utbot.framework.plugin.api.UtExecutionFailure -import org.utbot.framework.plugin.api.UtExecutionResult -import org.utbot.framework.plugin.api.UtExecutionSuccess -import org.utbot.framework.plugin.api.UtExplicitlyThrownException -import org.utbot.framework.plugin.api.UtImplicitlyThrownException -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtSandboxFailure -import org.utbot.framework.plugin.api.UtStreamConsumingFailure -import org.utbot.framework.plugin.api.UtTimeoutException import org.utbot.framework.plugin.api.util.id import org.utbot.framework.plugin.api.util.jField import org.utbot.framework.plugin.api.visible.UtStreamConsumingException import org.utbot.instrumentation.instrumentation.et.ExplicitThrowInstruction import org.utbot.instrumentation.instrumentation.et.TraceHandler - -class ModelConstructionPhaseError(cause: Throwable) : PhaseError( - message = "Error during model construction phase", - cause -) +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult /** * This phase of model construction from concrete values. */ -class ModelConstructionContext( +class ModelConstructionPhase( private val traceHandler: TraceHandler -) : PhaseContext { +) : ExecutionPhase { - override fun wrapError(error: Throwable): ModelConstructionPhaseError = - ModelConstructionPhaseError(error) + override fun wrapError(e: Throwable): ExecutionPhaseException { + val message = this.javaClass.simpleName + return when(e) { + is TimeoutException -> ExecutionPhaseStop(message, UtConcreteExecutionResult(MissingState, UtTimeoutException(e), Coverage())) + else -> ExecutionPhaseError(message, e) + } + } private lateinit var constructor: UtModelConstructor diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhaseContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhaseContext.kt deleted file mode 100644 index d1beaaa21b..0000000000 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhaseContext.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.utbot.instrumentation.instrumentation.execution.phases - -abstract class PhaseError(message: String, override val cause: Throwable) : Exception(message) -interface PhaseContext { - fun wrapError(error: Throwable): E -} - -inline fun > T.start(block: T.() -> R): R = - try { - block() - } catch (e: Throwable) { - throw wrapError(e) - } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt index 58617a3a61..02a4902988 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PhasesController.kt @@ -1,53 +1,70 @@ package org.utbot.instrumentation.instrumentation.execution.phases -import java.io.Closeable -import java.security.AccessControlException -import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult -import org.utbot.instrumentation.instrumentation.execution.mock.InstrumentationContext +import org.utbot.common.StopWatch +import org.utbot.common.ThreadBasedExecutor import org.utbot.framework.plugin.api.Coverage import org.utbot.framework.plugin.api.MissingState +import org.utbot.framework.plugin.api.TimeoutException import org.utbot.framework.plugin.api.UtSandboxFailure +import org.utbot.framework.plugin.api.util.UtContext +import org.utbot.framework.plugin.api.util.utContext +import org.utbot.framework.plugin.api.util.withUtContext import org.utbot.instrumentation.instrumentation.Instrumentation import org.utbot.instrumentation.instrumentation.et.TraceHandler +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult +import org.utbot.instrumentation.instrumentation.execution.mock.InstrumentationContext +import java.security.AccessControlException class PhasesController( instrumentationContext: InstrumentationContext, traceHandler: TraceHandler, delegateInstrumentation: Instrumentation>, -) : Closeable { + private val timeout: Long +) { + private var currentlyElapsed = 0L + val valueConstructionPhase = ValueConstructionPhase(instrumentationContext) - val valueConstructionContext = ValueConstructionContext(instrumentationContext) + val preparationPhase = PreparationPhase(traceHandler) - val preparationContext = PreparationContext(traceHandler) + val invocationPhase = InvocationPhase(delegateInstrumentation) - val invocationContext = InvocationContext(delegateInstrumentation) + val statisticsCollectionPhase = StatisticsCollectionPhase(traceHandler) - val statisticsCollectionContext = StatisticsCollectionContext(traceHandler) + val modelConstructionPhase = ModelConstructionPhase(traceHandler) - val modelConstructionContext = ModelConstructionContext(traceHandler) - - val postprocessingContext = PostprocessingContext() + val postprocessingPhase = PostprocessingPhase() inline fun computeConcreteExecutionResult(block: PhasesController.() -> UtConcreteExecutionResult): UtConcreteExecutionResult { - return use { - try { - block() - } catch (e: PhaseError) { - if (e.cause.cause is AccessControlException) { - return@use UtConcreteExecutionResult( - MissingState, - UtSandboxFailure(e.cause.cause!!), - Coverage() - ) - } - // TODO: make failure results from different phase errors - throw e + try { + return this.block() + } catch (e: ExecutionPhaseStop) { + return e.result + } catch (e: ExecutionPhaseError) { + if (e.cause.cause is AccessControlException) { + return UtConcreteExecutionResult( + MissingState, + UtSandboxFailure(e.cause.cause!!), + Coverage() + ) } + + throw e } } - override fun close() { - valueConstructionContext.close() - } + fun executePhaseInTimeout(phase: R, block: R.() -> T): T = phase.start { + val stopWatch = StopWatch() + val context = UtContext(utContext.classLoader, stopWatch) + val timeoutForCurrentPhase = timeout - currentlyElapsed + val result = ThreadBasedExecutor.threadLocal.invokeWithTimeout(timeout - currentlyElapsed, stopWatch) { + withUtContext(context) { + phase.block() + } + } ?: throw TimeoutException("Timeout $timeoutForCurrentPhase ms for phase ${phase.javaClass.simpleName} elapsed, controller timeout - $timeout") + + val blockElapsed = stopWatch.get() + currentlyElapsed += blockElapsed + return@start result.getOrThrow() as T + } } \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PostprocessingContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PostprocessingContext.kt deleted file mode 100644 index 9ddd7f318d..0000000000 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PostprocessingContext.kt +++ /dev/null @@ -1,30 +0,0 @@ -package org.utbot.instrumentation.instrumentation.execution.phases - -import org.utbot.common.withAccessibility -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.util.jField - -class PostprocessingPhaseError(cause: Throwable) : PhaseError( - message = "Error during postprocessing phase", - cause -) - -/** - * The responsibility of this phase is resetting environment to the initial state. - */ -class PostprocessingContext : PhaseContext { - - override fun wrapError(error: Throwable): PostprocessingPhaseError = - PostprocessingPhaseError(error) - - fun resetStaticFields(staticFields: Map) { - staticFields.forEach { (fieldId, value) -> - fieldId.jField.run { - withAccessibility { - set(null, value) - } - } - } - } - -} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PostprocessingPhase.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PostprocessingPhase.kt new file mode 100644 index 0000000000..9e8b7d127c --- /dev/null +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PostprocessingPhase.kt @@ -0,0 +1,33 @@ +package org.utbot.instrumentation.instrumentation.execution.phases + +import org.utbot.common.withAccessibility +import org.utbot.framework.plugin.api.FieldId +import org.utbot.framework.plugin.api.util.jField + + +/** + * The responsibility of this phase is resetting environment to the initial state. + */ +class PostprocessingPhase : ExecutionPhase { + + private var savedStaticsInstance: Map? = null + + var savedStatics: Map + get() = savedStaticsInstance!! + set(value) { + savedStaticsInstance = value + } + + override fun wrapError(e: Throwable): ExecutionPhaseException = ExecutionPhaseError(this.javaClass.simpleName, e) + + fun resetStaticFields() { + savedStatics.forEach { (fieldId, value) -> + fieldId.jField.run { + withAccessibility { + set(null, value) + } + } + } + } + +} \ No newline at end of file diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PreparationContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PreparationPhase.kt similarity index 76% rename from utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PreparationContext.kt rename to utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PreparationPhase.kt index 78b90bef1a..1576947380 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PreparationContext.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/PreparationPhase.kt @@ -6,20 +6,16 @@ import org.utbot.framework.plugin.api.UtConcreteValue import org.utbot.framework.plugin.api.util.jField import org.utbot.instrumentation.instrumentation.et.TraceHandler -class PreparationPhaseError(cause: Throwable) : PhaseError( - message = "Error during environment preparation phase", - cause -) /** * The responsibility of this phase is environment preparation before execution. */ -class PreparationContext( +class PreparationPhase( private val traceHandler: TraceHandler -) : PhaseContext { +) : ExecutionPhase { - override fun wrapError(error: Throwable): PreparationPhaseError = - PreparationPhaseError(error) + override fun wrapError(e: Throwable): ExecutionPhaseException = + ExecutionPhaseError(this.javaClass.simpleName, e) fun setStaticFields(staticFieldsValues: Map>): Map { val savedStaticFields = mutableMapOf() diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/StatisticsCollectionContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/StatisticsCollectionPhase.kt similarity index 65% rename from utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/StatisticsCollectionContext.kt rename to utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/StatisticsCollectionPhase.kt index 797f2a2f7a..d3264cbf70 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/StatisticsCollectionContext.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/StatisticsCollectionPhase.kt @@ -1,25 +1,25 @@ package org.utbot.instrumentation.instrumentation.execution.phases import org.objectweb.asm.Type -import org.utbot.framework.plugin.api.Coverage -import org.utbot.framework.plugin.api.Instruction +import org.utbot.framework.plugin.api.* import org.utbot.instrumentation.instrumentation.et.EtInstruction import org.utbot.instrumentation.instrumentation.et.TraceHandler - -class StatisticsCollectionPhaseError(cause: Throwable) : PhaseError( - message = "Error during statistics collection phase", - cause -) +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult /** * This phase is about collection statistics such as coverage. */ -class StatisticsCollectionContext( +class StatisticsCollectionPhase( private val traceHandler: TraceHandler -) : PhaseContext { +) : ExecutionPhase { - override fun wrapError(error: Throwable): StatisticsCollectionPhaseError = - StatisticsCollectionPhaseError(error) + override fun wrapError(e: Throwable): ExecutionPhaseException { + val message = this.javaClass.simpleName + return when(e) { + is TimeoutException -> ExecutionPhaseStop(message, UtConcreteExecutionResult(MissingState, UtTimeoutException(e), Coverage())) + else -> ExecutionPhaseError(message, e) + } + } fun getCoverage(clazz: Class<*>): Coverage { return traceHandler diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionContext.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionPhase.kt similarity index 60% rename from utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionContext.kt rename to utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionPhase.kt index aaa5732f25..7cfe3e19c4 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionContext.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/instrumentation/execution/phases/ValueConstructionPhase.kt @@ -1,45 +1,44 @@ package org.utbot.instrumentation.instrumentation.execution.phases +import org.utbot.framework.plugin.api.* import java.io.Closeable import java.util.IdentityHashMap import org.utbot.instrumentation.instrumentation.execution.constructors.MockValueConstructor import org.utbot.instrumentation.instrumentation.execution.mock.InstrumentationContext -import org.utbot.framework.plugin.api.EnvironmentModels -import org.utbot.framework.plugin.api.FieldId -import org.utbot.framework.plugin.api.UtConcreteValue -import org.utbot.framework.plugin.api.UtInstrumentation -import org.utbot.framework.plugin.api.UtModel -import org.utbot.framework.plugin.api.UtNewInstanceInstrumentation -import org.utbot.framework.plugin.api.UtStaticMethodInstrumentation import org.utbot.framework.plugin.api.util.isInaccessibleViaReflection +import org.utbot.instrumentation.instrumentation.execution.UtConcreteExecutionResult -class ValueConstructionPhaseError(cause: Throwable) : PhaseError( - message = "Error during phase of constructing values from models", - cause -) +typealias ConstructedParameters = List> +typealias ConstructedStatics = Map> +typealias ConstructedCache = IdentityHashMap /** * This phase of values instantiation from given models. */ -class ValueConstructionContext( +class ValueConstructionPhase( instrumentationContext: InstrumentationContext -) : PhaseContext, Closeable { +) : ExecutionPhase { - override fun wrapError(error: Throwable): ValueConstructionPhaseError = - ValueConstructionPhaseError(error) + override fun wrapError(e: Throwable): ExecutionPhaseException { + val message = this.javaClass.simpleName + return when(e) { + is TimeoutException -> ExecutionPhaseStop(message, UtConcreteExecutionResult(MissingState, UtTimeoutException(e), Coverage())) + else -> ExecutionPhaseError(message, e) + } + } private val constructor = MockValueConstructor(instrumentationContext) - fun getCache(): IdentityHashMap { + fun getCache(): ConstructedCache { return constructor.objectToModelCache } - fun constructParameters(state: EnvironmentModels): List> { + fun constructParameters(state: EnvironmentModels): ConstructedParameters { val parametersModels = listOfNotNull(state.thisInstance) + state.parameters return constructor.constructMethodParameters(parametersModels) } - fun constructStatics(state: EnvironmentModels): Map> = + fun constructStatics(state: EnvironmentModels): ConstructedStatics = constructor.constructStatics( state.statics.filterKeys { !it.isInaccessibleViaReflection } ) @@ -59,8 +58,7 @@ class ValueConstructionContext( constructor.mockNewInstances(newInstanceInstrumentation) } - override fun close() { - constructor.close() + fun resetMockMethods() { + constructor.resetMockedMethods() } - } diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt index 3b452f479c..96db99322a 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/process/InstrumentedProcessMain.kt @@ -3,6 +3,7 @@ package org.utbot.instrumentation.process import com.jetbrains.rd.util.* import com.jetbrains.rd.util.lifetime.Lifetime import kotlinx.coroutines.* +import org.mockito.Mockito import org.utbot.common.* import org.utbot.framework.plugin.api.util.UtContext import org.utbot.instrumentation.agent.Agent @@ -63,6 +64,21 @@ fun logLevelArgument(level: LogLevel): String { return "$ENABLE_LOGS_OPTION=$level" } +interface DummyForMockitoWarmup { + fun method1() +} + +/** + * Mockito initialization take ~0.5-1 sec, which forces first `invoke` request to timeout + * it is crucial in tests as we start process just for 1-2 such requests + */ +fun warmupMockito() { + try { + val unused = Mockito.mock(DummyForMockitoWarmup::class.java) + } catch (ignored: Throwable) { + } +} + private fun findLogLevel(args: Array): LogLevel { val logArgument = args.find{ it.contains(ENABLE_LOGS_OPTION) } ?: return LogLevel.Fatal @@ -95,6 +111,7 @@ fun main(args: Array) = runBlocking { try { ClientProtocolBuilder().withProtocolTimeout(messageFromMainTimeout).start(port) { + this.protocol.scheduler.queue { warmupMockito() } val kryoHelper = KryoHelper(lifetime) logger.info { "setup started" } AbstractSettings.setupFactory(RdSettingsContainerFactory(protocol.settingsModel)) diff --git a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/InstrumentationException.kt b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/InstrumentationException.kt index 09a6be548f..6ab36d3ea3 100644 --- a/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/InstrumentationException.kt +++ b/utbot-instrumentation/src/main/kotlin/org/utbot/instrumentation/util/InstrumentationException.kt @@ -1,34 +1,36 @@ -package org.utbot.instrumentation.util - -// TODO: refactor this - -/** - * Base class for instrumentation exceptions. - */ -open class InstrumentationException(msg: String?, cause: Throwable? = null) : Exception(msg, cause) - -class NoProbesArrayException(clazz: Class<*>, arrayName: String) : - InstrumentationException( - "No probes array found\n\t" + - "Clazz: $clazz\n\t" + - "Probes array name: $arrayName\n\t" + - "All fields: ${clazz.fields.joinToString { it.name }}" - ) - -class CastProbesArrayException : - InstrumentationException("Can't cast probes array to Boolean array") - -class ReadingFromKryoException(e: Throwable) : - InstrumentationException("Reading from Kryo exception |> ${e.stackTraceToString()}", e) - -class WritingToKryoException(e: Throwable) : - InstrumentationException("Writing to Kryo exception |> ${e.stackTraceToString()}", e) - -/** - * this exception is thrown only in main process. - * currently it means that {e: Throwable} happened in instrumented process, - * but instrumented process still can operate and not dead. - * on instrumented process death - ConcreteExecutionFailureException is thrown -*/ -class InstrumentedProcessError(e: Throwable) : +package org.utbot.instrumentation.util + +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException + +// TODO: refactor this + +/** + * Base class for instrumentation exceptions. + */ +open class InstrumentationException(msg: String?, cause: Throwable? = null) : Exception(msg, cause) + +class NoProbesArrayException(clazz: Class<*>, arrayName: String) : + InstrumentationException( + "No probes array found\n\t" + + "Clazz: $clazz\n\t" + + "Probes array name: $arrayName\n\t" + + "All fields: ${clazz.fields.joinToString { it.name }}" + ) + +class CastProbesArrayException : + InstrumentationException("Can't cast probes array to Boolean array") + +class ReadingFromKryoException(e: Throwable) : + InstrumentationException("Reading from Kryo exception |> ${e.stackTraceToString()}", e) + +class WritingToKryoException(e: Throwable) : + InstrumentationException("Writing to Kryo exception |> ${e.stackTraceToString()}", e) + +/** + * this exception is thrown only in main process. + * currently it means that {e: Throwable} happened in instrumented process, + * but instrumented process still can operate and not dead. + * on instrumented process death - [InstrumentedProcessDeathException] is thrown +*/ +class InstrumentedProcessError(e: Throwable) : InstrumentationException("Error in the instrumented process |> ${e.stackTraceToString()}", e) \ No newline at end of file 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 1d05fe6c5b..e7f9911d75 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 @@ -139,7 +139,7 @@ object UtTestsDialogProcessor { return } - UtSettings.concreteExecutionTimeoutInInstrumentedProcess = model.hangingTestsTimeout.timeoutMs + UtSettings.concreteExecutionDefaultTimeoutInInstrumentedProcessMillis = model.hangingTestsTimeout.timeoutMs UtSettings.useCustomJavaDocTags = model.commentStyle == JavaDocCommentStyle.CUSTOM_JAVADOC_TAGS UtSettings.summaryGenerationType = model.summariesGenerationType diff --git a/utbot-junit-contest/build.gradle b/utbot-junit-contest/build.gradle index dd40d660f7..7bbd07a8d1 100644 --- a/utbot-junit-contest/build.gradle +++ b/utbot-junit-contest/build.gradle @@ -55,6 +55,7 @@ dependencies { implementation project(":utbot-framework") implementation project(":utbot-analytics") + implementation("org.unittestbot.soot:soot-utbot-fork:${sootVersion}") { exclude group:'com.google.guava', module:'guava' } diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/UtRdUtil.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/UtRdUtil.kt index 1bfa0ca51d..535c294032 100644 --- a/utbot-rd/src/main/kotlin/org/utbot/rd/UtRdUtil.kt +++ b/utbot-rd/src/main/kotlin/org/utbot/rd/UtRdUtil.kt @@ -1,9 +1,10 @@ package org.utbot.rd import com.jetbrains.rd.framework.* -import com.jetbrains.rd.framework.impl.RdCall import com.jetbrains.rd.framework.util.NetUtils import com.jetbrains.rd.framework.util.synchronizeWith +import com.jetbrains.rd.util.Logger +import com.jetbrains.rd.util.debug import com.jetbrains.rd.util.lifetime.Lifetime import com.jetbrains.rd.util.lifetime.LifetimeDefinition import com.jetbrains.rd.util.lifetime.throwIfNotAlive @@ -123,4 +124,19 @@ suspend fun startUtProcessWithRdServer( it ) } +} + +inline fun Logger.logMeasure(tag: String = "", block: () -> T): T { + val start = System.currentTimeMillis() + this.debug { "started evaluating $tag" } + try { + return block() + } + catch (e: Throwable) { + this.debug { "exception during evaluating $tag: ${e.stackTraceToString()}" } + throw e + } + finally { + this.debug { "evaluating $tag took ${System.currentTimeMillis() - start} ms" } + } } \ No newline at end of file diff --git a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationModelRoot.Generated.kt b/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationModelRoot.Generated.kt deleted file mode 100644 index 32b75c35ed..0000000000 --- a/utbot-rd/src/main/kotlin/org/utbot/rd/generated/SynchronizationModelRoot.Generated.kt +++ /dev/null @@ -1,58 +0,0 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE","EXPERIMENTAL_UNSIGNED_LITERALS","PackageDirectoryMismatch","UnusedImport","unused","LocalVariableName","CanBeVal","PropertyName","EnumEntryName","ClassName","ObjectPropertyName","UnnecessaryVariable","SpellCheckingInspection") -package org.utbot.rd.generated - -import com.jetbrains.rd.framework.* -import com.jetbrains.rd.framework.base.* -import com.jetbrains.rd.framework.impl.* - -import com.jetbrains.rd.util.lifetime.* -import com.jetbrains.rd.util.reactive.* -import com.jetbrains.rd.util.string.* -import com.jetbrains.rd.util.* -import kotlin.reflect.KClass -import kotlin.jvm.JvmStatic - - - -/** - * #### Generated from [SynchronizationModel.kt:5] - */ -class SynchronizationModelRoot private constructor( -) : RdExtBase() { - //companion - - companion object : ISerializersOwner { - - override fun registerSerializersCore(serializers: ISerializers) { - SynchronizationModelRoot.register(serializers) - SynchronizationModel.register(serializers) - } - - - - - - const val serializationHash = -1304011640135373779L - - } - override val serializersOwner: ISerializersOwner get() = SynchronizationModelRoot - override val serializationHash: Long get() = SynchronizationModelRoot.serializationHash - - //fields - //methods - //initializer - //secondary constructor - //equals trait - //hash code trait - //pretty print - override fun print(printer: PrettyPrinter) { - printer.println("SynchronizationModelRoot (") - printer.print(")") - } - //deepClone - override fun deepClone(): SynchronizationModelRoot { - return SynchronizationModelRoot( - ) - } - //contexts -} diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/symbolic/SimpleCommentBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/symbolic/SimpleCommentBuilder.kt index 003d3c0116..9f8208f1bd 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/symbolic/SimpleCommentBuilder.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/comment/classic/symbolic/SimpleCommentBuilder.kt @@ -1,405 +1,405 @@ -package org.utbot.summary.comment.classic.symbolic - -import com.github.javaparser.ast.body.MethodDeclaration -import com.github.javaparser.ast.stmt.CatchClause -import com.github.javaparser.ast.stmt.ForStmt -import com.github.javaparser.ast.stmt.IfStmt -import com.github.javaparser.ast.stmt.Statement -import com.github.javaparser.ast.stmt.SwitchStmt -import com.github.javaparser.ast.stmt.ThrowStmt -import org.utbot.framework.plugin.api.ConcreteExecutionFailureException -import org.utbot.framework.plugin.api.DocPreTagStatement -import org.utbot.framework.plugin.api.DocRegularStmt -import org.utbot.framework.plugin.api.DocStatement -import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.exceptionOrNull -import org.utbot.summary.AbstractTextBuilder -import org.utbot.summary.SummarySentenceConstants.CARRIAGE_RETURN -import org.utbot.summary.ast.JimpleToASTMap -import org.utbot.summary.comment.* -import org.utbot.summary.comment.customtags.getMethodReferenceForSymbolicTest -import org.utbot.summary.tag.BasicTypeTag -import org.utbot.summary.tag.CallOrderTag -import org.utbot.summary.tag.StatementTag -import org.utbot.summary.tag.TraceTagWithoutExecution -import org.utbot.summary.tag.UniquenessTag -import soot.SootMethod -import soot.Type -import soot.jimple.Stmt -import soot.jimple.internal.JAssignStmt -import soot.jimple.internal.JInvokeStmt -import soot.jimple.internal.JVirtualInvokeExpr - -private const val JVM_CRASH_REASON = "JVM crash" -const val EMPTY_STRING = "" - -open class SimpleCommentBuilder( - traceTag: TraceTagWithoutExecution, - sootToAST: MutableMap, - val stringTemplates: StringsTemplatesInterface = StringsTemplatesSingular() -) : - AbstractTextBuilder(traceTag, sootToAST) { - - /** - * Creates String from SimpleSentenceBlock - */ - open fun buildString(currentMethod: SootMethod): String { - val root = SimpleSentenceBlock(stringTemplates = stringTemplates) - buildThrownExceptionInfo(root, currentMethod) - skippedIterations() - buildSentenceBlock(traceTag.rootStatementTag, root, currentMethod) - var sentence = toSentence(root) - - if (sentence.isEmpty()) { - return EMPTY_STRING - } - - sentence = splitLongSentence(sentence) - sentence = lastCommaToDot(sentence) - - return "
\n$sentence
".replace(CARRIAGE_RETURN, "") - } - - private fun buildThrownExceptionInfo( - root: SimpleSentenceBlock, - currentMethod: SootMethod - ) { - traceTag.result.exceptionOrNull()?.let { - val exceptionName = it.javaClass.simpleName - val reason = findExceptionReason(currentMethod, it) - root.exceptionThrow = "$exceptionName $reason" - } - } - - /** - * Creates List<[DocStatement]> from [SimpleSentenceBlock]. - */ - open fun buildDocStmts(currentMethod: SootMethod): List { - val sentenceBlock = buildSentenceBlock(currentMethod) - val docStmts = toDocStmts(sentenceBlock) - - if (docStmts.isEmpty()) { - return emptyList() - } -// sentence = splitLongSentence(sentence) //TODO SAT-1309 -// sentence = lastCommaToDot(sentence) //TODO SAT-1309 - - return listOf(DocPreTagStatement(docStmts)) - } - - private fun buildSentenceBlock(currentMethod: SootMethod): SimpleSentenceBlock { - val rootSentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) - buildThrownExceptionInfo(rootSentenceBlock, currentMethod) - skippedIterations() - buildSentenceBlock(traceTag.rootStatementTag, rootSentenceBlock, currentMethod) - return rootSentenceBlock - } - - /** - * Transforms rootSentenceBlock into String - */ - protected fun toSentence(rootSentenceBlock: SimpleSentenceBlock): String { - rootSentenceBlock.squashStmtText() - val buildSentence = rootSentenceBlock.toSentence() - if (buildSentence.isEmpty()) return "" - return "${stringTemplates.sentenceBeginning} $buildSentence" - } - - /** - * Transforms rootSentenceBlock into List - */ - protected fun toDocStmts(rootSentenceBlock: SimpleSentenceBlock): List { - val stmts = mutableListOf() - - rootSentenceBlock.squashStmtText() - stmts += rootSentenceBlock.toDocStmt() - if (stmts.isEmpty()) return emptyList() - - stmts.add(0, DocRegularStmt("${stringTemplates.sentenceBeginning} ")) - return stmts - } - - protected fun findExceptionReason(currentMethod: SootMethod, thrownException: Throwable): String { - val path = traceTag.path - if (path.isEmpty()) { - if (thrownException is ConcreteExecutionFailureException) { - return JVM_CRASH_REASON - } - - error("Cannot find last path step for exception $thrownException") - } - - return findExceptionReason(path.last(), currentMethod) - } - - /** - * Tries to find ast node where exception was thrown - * or condition if exception was thrown manually in function body - */ - protected fun findExceptionReason(step: Step, currentMethod: SootMethod): String { - var reason = "" - val exceptionStmt = step.stmt - val jimpleToASTMap = sootToAST[currentMethod] ?: return "" - var exceptionNode = jimpleToASTMap.stmtToASTNode[exceptionStmt] - if (exceptionNode is ThrowStmt) { - exceptionNode = getExceptionReason(exceptionNode) - reason += "after condition: " - } else reason += "in: " - - //special case if reason is MethodDeclaration -> exception was thrown after body execution, not after condition - if (exceptionNode is MethodDeclaration) return "in ${exceptionNode.name} function body" - //node is SwitchStmt only when jimple stmt is inside selector - if (exceptionNode is SwitchStmt) exceptionNode = exceptionNode.selector - - if (exceptionNode == null) return "" - - reason += when { - exceptionNode is IfStmt -> exceptionNode.condition.toString() - isLoopStatement(exceptionNode) -> getTextIterationDescription(exceptionNode) - exceptionNode is SwitchStmt -> textSwitchCase(step, jimpleToASTMap) - else -> exceptionNode.toString() - } - - return reason.replace(CARRIAGE_RETURN, "") - } - - /** - * Sentence blocks are built based on unique and partly unique statement tags. - */ - open fun buildSentenceBlock( - statementTag: StatementTag?, - sentenceBlock: SimpleSentenceBlock, - currentMethod: SootMethod - ) { - val jimpleToASTMap = sootToAST[currentMethod] - if (statementTag == null) return - if (jimpleToASTMap == null) return - val recursion = statementTag.recursion - val stmt = statementTag.step.stmt - val invoke = statementTag.invoke - var createNextBlock = false - - val localNoIterations = statementTagSkippedIteration(statementTag, currentMethod) - if (localNoIterations.isNotEmpty()) { - sentenceBlock.notExecutedIterations = localNoIterations - methodToNoIterationDescription[currentMethod]?.removeAll(localNoIterations) - } - - val invokeSootMethod = statementTag.invokeSootMethod() - var invokeRegistered = false - if (invoke != null && invokeSootMethod != null) { - val className = invokeSootMethod.declaringClass.name - val methodName = invokeSootMethod.name - val methodParameterTypes = invokeSootMethod.parameterTypes - val sentenceInvoke = SimpleSentenceBlock(stringTemplates = sentenceBlock.stringTemplates) - buildSentenceBlock(invoke, sentenceInvoke, invokeSootMethod) - sentenceInvoke.squashStmtText() - if (!sentenceInvoke.isEmpty()) { - sentenceBlock.invokeSentenceBlock = - Pair( - getMethodReferenceForSymbolicTest(className, methodName, methodParameterTypes, invokeSootMethod.isPrivate), - sentenceInvoke - ) - createNextBlock = true - invokeRegistered = true - } - } - if (statementTag.basicTypeTag == BasicTypeTag.Invoke && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { - if (statementTag.executionFrequency <= 1) { - addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) - } - if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { - addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) - } - } - - if (statementTag.basicTypeTag == BasicTypeTag.RecursionAssignment && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { - if (statementTag.executionFrequency <= 1) { - addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) - } - if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { - addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) - } - } - - if (jimpleToASTMap[statementTag.step.stmt] !is ForStmt) { - - if (statementTag.basicTypeTag == BasicTypeTag.Condition && statementTag.callOrderTag == CallOrderTag.First) { - if (statementTag.uniquenessTag == UniquenessTag.Unique) { - val conditionText = textCondition(statementTag, jimpleToASTMap) - if (conditionText != null) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.Condition, conditionText)) - } - } - if (statementTag.uniquenessTag == UniquenessTag.Partly && statementTag.executionFrequency == 1) { - val conditionText = textCondition(statementTag, jimpleToASTMap) - if (conditionText != null) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.Condition, conditionText)) - } - } - } - - if (statementTag.basicTypeTag == BasicTypeTag.SwitchCase && statementTag.uniquenessTag == UniquenessTag.Unique) { - val switchCase = textSwitchCase(statementTag.step, jimpleToASTMap) - if (switchCase != null) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.SwitchCase, switchCase)) - } - } - if (statementTag.basicTypeTag == BasicTypeTag.CaughtException) { - jimpleToASTMap[stmt].let { - if (it is CatchClause) { - sentenceBlock.stmtTexts.add(StmtDescription(StmtType.CaughtException, it.parameter.toString())) - } - } - } - if (statementTag.basicTypeTag == BasicTypeTag.Return) { - textReturn(statementTag, sentenceBlock, stmt, jimpleToASTMap) - } - } - - if (statementTag.iterations.isNotEmpty()) { - val iterationSentenceBlock = buildIterationsBlock(statementTag.iterations, statementTag.step, currentMethod) - sentenceBlock.iterationSentenceBlocks.add(iterationSentenceBlock) - createNextBlock = true - } - - if (recursion != null) { - if (stmt is JAssignStmt) { - val name = (stmt.rightOp as JVirtualInvokeExpr).method.name - val sentenceRecursionBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) - buildSentenceBlock(recursion, sentenceRecursionBlock, currentMethod) - sentenceBlock.recursion = Pair(name, sentenceRecursionBlock) - createNextBlock = true - } - if (stmt is JInvokeStmt) { - val name = stmt.invokeExpr.method.name - val sentenceRecursion = SimpleSentenceBlock(stringTemplates = stringTemplates) - buildSentenceBlock(recursion, sentenceRecursion, currentMethod) - sentenceBlock.recursion = Pair(name, sentenceRecursion) - createNextBlock = true - } - } - - if (createNextBlock) { - val nextSentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) - sentenceBlock.nextBlock = nextSentenceBlock - buildSentenceBlock(statementTag.next, nextSentenceBlock, currentMethod) - } else { - buildSentenceBlock(statementTag.next, sentenceBlock, currentMethod) - } - } - - /** - * Adds RecursionAssignment into sentenceBlock.stmtTexts - */ - protected fun addTextRecursion(sentenceBlock: SimpleSentenceBlock, stmt: Stmt, frequency: Int) { - if (stmt is JAssignStmt || stmt is JInvokeStmt) { - val methodName = stmt.invokeExpr.method.name - addTextRecursion(sentenceBlock, methodName, frequency) - } - } - - /** - * Adds Invoke into sentenceBlock.stmtTexts - */ - protected fun addTextInvoke(sentenceBlock: SimpleSentenceBlock, stmt: Stmt, frequency: Int) { - if (stmt is JAssignStmt || stmt is JInvokeStmt) { - val className = stmt.invokeExpr.methodRef.declaringClass.name - val methodName = stmt.invokeExpr.method.name - val methodParameterTypes = stmt.invokeExpr.method.parameterTypes - val isPrivate = stmt.invokeExpr.method.isPrivate - addTextInvoke( - sentenceBlock, - className, - methodName, - methodParameterTypes, - isPrivate, - frequency - ) - } - } - - /** - * Adds Invoke into sentenceBlock.stmtTexts - */ - protected fun addTextInvoke( - sentenceBlock: SimpleSentenceBlock, - className: String, - methodName: String, - methodParameterTypes: List, - isPrivate: Boolean, - frequency: Int - ) { - if (!shouldSkipInvoke(methodName)) - sentenceBlock.stmtTexts.add( - StmtDescription( - StmtType.Invoke, - getMethodReferenceForSymbolicTest(className, methodName, methodParameterTypes, isPrivate), - frequency - ) - ) - } - - /** - * Adds RecursionAssignment into sentenceBlock.stmtTexts - */ - protected fun addTextRecursion( - sentenceBlock: SimpleSentenceBlock, - methodName: String, - frequency: Int - ) { - if (!shouldSkipInvoke(methodName)) - sentenceBlock.stmtTexts.add( - StmtDescription( - StmtType.RecursionAssignment, - methodName, - frequency - ) - ) - } - - protected fun buildIterationsBlock( - iterations: List, - activatedStep: Step, - currentMethod: SootMethod - ): Pair> { - val result = mutableListOf() - val jimpleToASTMap = sootToAST[currentMethod] - iterations.forEach { - val sentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) - buildSentenceBlock(it, sentenceBlock, currentMethod) - result.add(sentenceBlock) - } - val firstStmtNode = jimpleToASTMap?.get(iterations.first().step.stmt) - val activatedNode = jimpleToASTMap?.get(activatedStep.stmt) - val line = iterations.first().line - - var iterationDescription = "" - if (firstStmtNode is Statement) { - iterationDescription = getTextIterationDescription(firstStmtNode) - } - // getTextIterationDescription can return empty description, - // that is why if else is not used here. - if (iterationDescription.isEmpty() && activatedNode is Statement) { - iterationDescription = getTextIterationDescription(activatedNode) - } - //heh, we are still looking for loop txt - if (iterationDescription.isEmpty()) { - val nearestNode = jimpleToASTMap?.nearestIterationNode(activatedNode, line) - if (nearestNode != null) { - iterationDescription = getTextIterationDescription(nearestNode) - } - } - - if (iterationDescription.isEmpty()) { - val nearestNode = jimpleToASTMap?.nearestIterationNode(firstStmtNode, line) - if (nearestNode != null) { - iterationDescription = getTextIterationDescription(nearestNode) - } - } - - return Pair(iterationDescription, result) - } -} - -data class IterationDescription(val from: Int, val to: Int, val description: String, val typeDescription: String) +package org.utbot.summary.comment.classic.symbolic + +import com.github.javaparser.ast.body.MethodDeclaration +import com.github.javaparser.ast.stmt.CatchClause +import com.github.javaparser.ast.stmt.ForStmt +import com.github.javaparser.ast.stmt.IfStmt +import com.github.javaparser.ast.stmt.Statement +import com.github.javaparser.ast.stmt.SwitchStmt +import com.github.javaparser.ast.stmt.ThrowStmt +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException +import org.utbot.framework.plugin.api.DocPreTagStatement +import org.utbot.framework.plugin.api.DocRegularStmt +import org.utbot.framework.plugin.api.DocStatement +import org.utbot.framework.plugin.api.Step +import org.utbot.framework.plugin.api.exceptionOrNull +import org.utbot.summary.AbstractTextBuilder +import org.utbot.summary.SummarySentenceConstants.CARRIAGE_RETURN +import org.utbot.summary.ast.JimpleToASTMap +import org.utbot.summary.comment.* +import org.utbot.summary.comment.customtags.getMethodReferenceForSymbolicTest +import org.utbot.summary.tag.BasicTypeTag +import org.utbot.summary.tag.CallOrderTag +import org.utbot.summary.tag.StatementTag +import org.utbot.summary.tag.TraceTagWithoutExecution +import org.utbot.summary.tag.UniquenessTag +import soot.SootMethod +import soot.Type +import soot.jimple.Stmt +import soot.jimple.internal.JAssignStmt +import soot.jimple.internal.JInvokeStmt +import soot.jimple.internal.JVirtualInvokeExpr + +private const val JVM_CRASH_REASON = "JVM crash" +const val EMPTY_STRING = "" + +open class SimpleCommentBuilder( + traceTag: TraceTagWithoutExecution, + sootToAST: MutableMap, + val stringTemplates: StringsTemplatesInterface = StringsTemplatesSingular() +) : + AbstractTextBuilder(traceTag, sootToAST) { + + /** + * Creates String from SimpleSentenceBlock + */ + open fun buildString(currentMethod: SootMethod): String { + val root = SimpleSentenceBlock(stringTemplates = stringTemplates) + buildThrownExceptionInfo(root, currentMethod) + skippedIterations() + buildSentenceBlock(traceTag.rootStatementTag, root, currentMethod) + var sentence = toSentence(root) + + if (sentence.isEmpty()) { + return EMPTY_STRING + } + + sentence = splitLongSentence(sentence) + sentence = lastCommaToDot(sentence) + + return "
\n$sentence
".replace(CARRIAGE_RETURN, "") + } + + private fun buildThrownExceptionInfo( + root: SimpleSentenceBlock, + currentMethod: SootMethod + ) { + traceTag.result.exceptionOrNull()?.let { + val exceptionName = it.javaClass.simpleName + val reason = findExceptionReason(currentMethod, it) + root.exceptionThrow = "$exceptionName $reason" + } + } + + /** + * Creates List<[DocStatement]> from [SimpleSentenceBlock]. + */ + open fun buildDocStmts(currentMethod: SootMethod): List { + val sentenceBlock = buildSentenceBlock(currentMethod) + val docStmts = toDocStmts(sentenceBlock) + + if (docStmts.isEmpty()) { + return emptyList() + } +// sentence = splitLongSentence(sentence) //TODO SAT-1309 +// sentence = lastCommaToDot(sentence) //TODO SAT-1309 + + return listOf(DocPreTagStatement(docStmts)) + } + + private fun buildSentenceBlock(currentMethod: SootMethod): SimpleSentenceBlock { + val rootSentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) + buildThrownExceptionInfo(rootSentenceBlock, currentMethod) + skippedIterations() + buildSentenceBlock(traceTag.rootStatementTag, rootSentenceBlock, currentMethod) + return rootSentenceBlock + } + + /** + * Transforms rootSentenceBlock into String + */ + protected fun toSentence(rootSentenceBlock: SimpleSentenceBlock): String { + rootSentenceBlock.squashStmtText() + val buildSentence = rootSentenceBlock.toSentence() + if (buildSentence.isEmpty()) return "" + return "${stringTemplates.sentenceBeginning} $buildSentence" + } + + /** + * Transforms rootSentenceBlock into List + */ + protected fun toDocStmts(rootSentenceBlock: SimpleSentenceBlock): List { + val stmts = mutableListOf() + + rootSentenceBlock.squashStmtText() + stmts += rootSentenceBlock.toDocStmt() + if (stmts.isEmpty()) return emptyList() + + stmts.add(0, DocRegularStmt("${stringTemplates.sentenceBeginning} ")) + return stmts + } + + protected fun findExceptionReason(currentMethod: SootMethod, thrownException: Throwable): String { + val path = traceTag.path + if (path.isEmpty()) { + if (thrownException is InstrumentedProcessDeathException) { + return JVM_CRASH_REASON + } + + error("Cannot find last path step for exception $thrownException") + } + + return findExceptionReason(path.last(), currentMethod) + } + + /** + * Tries to find ast node where exception was thrown + * or condition if exception was thrown manually in function body + */ + protected fun findExceptionReason(step: Step, currentMethod: SootMethod): String { + var reason = "" + val exceptionStmt = step.stmt + val jimpleToASTMap = sootToAST[currentMethod] ?: return "" + var exceptionNode = jimpleToASTMap.stmtToASTNode[exceptionStmt] + if (exceptionNode is ThrowStmt) { + exceptionNode = getExceptionReason(exceptionNode) + reason += "after condition: " + } else reason += "in: " + + //special case if reason is MethodDeclaration -> exception was thrown after body execution, not after condition + if (exceptionNode is MethodDeclaration) return "in ${exceptionNode.name} function body" + //node is SwitchStmt only when jimple stmt is inside selector + if (exceptionNode is SwitchStmt) exceptionNode = exceptionNode.selector + + if (exceptionNode == null) return "" + + reason += when { + exceptionNode is IfStmt -> exceptionNode.condition.toString() + isLoopStatement(exceptionNode) -> getTextIterationDescription(exceptionNode) + exceptionNode is SwitchStmt -> textSwitchCase(step, jimpleToASTMap) + else -> exceptionNode.toString() + } + + return reason.replace(CARRIAGE_RETURN, "") + } + + /** + * Sentence blocks are built based on unique and partly unique statement tags. + */ + open fun buildSentenceBlock( + statementTag: StatementTag?, + sentenceBlock: SimpleSentenceBlock, + currentMethod: SootMethod + ) { + val jimpleToASTMap = sootToAST[currentMethod] + if (statementTag == null) return + if (jimpleToASTMap == null) return + val recursion = statementTag.recursion + val stmt = statementTag.step.stmt + val invoke = statementTag.invoke + var createNextBlock = false + + val localNoIterations = statementTagSkippedIteration(statementTag, currentMethod) + if (localNoIterations.isNotEmpty()) { + sentenceBlock.notExecutedIterations = localNoIterations + methodToNoIterationDescription[currentMethod]?.removeAll(localNoIterations) + } + + val invokeSootMethod = statementTag.invokeSootMethod() + var invokeRegistered = false + if (invoke != null && invokeSootMethod != null) { + val className = invokeSootMethod.declaringClass.name + val methodName = invokeSootMethod.name + val methodParameterTypes = invokeSootMethod.parameterTypes + val sentenceInvoke = SimpleSentenceBlock(stringTemplates = sentenceBlock.stringTemplates) + buildSentenceBlock(invoke, sentenceInvoke, invokeSootMethod) + sentenceInvoke.squashStmtText() + if (!sentenceInvoke.isEmpty()) { + sentenceBlock.invokeSentenceBlock = + Pair( + getMethodReferenceForSymbolicTest(className, methodName, methodParameterTypes, invokeSootMethod.isPrivate), + sentenceInvoke + ) + createNextBlock = true + invokeRegistered = true + } + } + if (statementTag.basicTypeTag == BasicTypeTag.Invoke && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { + if (statementTag.executionFrequency <= 1) { + addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) + } + if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { + addTextInvoke(sentenceBlock, stmt, statementTag.executionFrequency) + } + } + + if (statementTag.basicTypeTag == BasicTypeTag.RecursionAssignment && statementTag.uniquenessTag == UniquenessTag.Unique && !invokeRegistered) { + if (statementTag.executionFrequency <= 1) { + addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) + } + if (statementTag.executionFrequency > 1 && statementTag.callOrderTag == CallOrderTag.First) { + addTextRecursion(sentenceBlock, stmt, statementTag.executionFrequency) + } + } + + if (jimpleToASTMap[statementTag.step.stmt] !is ForStmt) { + + if (statementTag.basicTypeTag == BasicTypeTag.Condition && statementTag.callOrderTag == CallOrderTag.First) { + if (statementTag.uniquenessTag == UniquenessTag.Unique) { + val conditionText = textCondition(statementTag, jimpleToASTMap) + if (conditionText != null) { + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.Condition, conditionText)) + } + } + if (statementTag.uniquenessTag == UniquenessTag.Partly && statementTag.executionFrequency == 1) { + val conditionText = textCondition(statementTag, jimpleToASTMap) + if (conditionText != null) { + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.Condition, conditionText)) + } + } + } + + if (statementTag.basicTypeTag == BasicTypeTag.SwitchCase && statementTag.uniquenessTag == UniquenessTag.Unique) { + val switchCase = textSwitchCase(statementTag.step, jimpleToASTMap) + if (switchCase != null) { + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.SwitchCase, switchCase)) + } + } + if (statementTag.basicTypeTag == BasicTypeTag.CaughtException) { + jimpleToASTMap[stmt].let { + if (it is CatchClause) { + sentenceBlock.stmtTexts.add(StmtDescription(StmtType.CaughtException, it.parameter.toString())) + } + } + } + if (statementTag.basicTypeTag == BasicTypeTag.Return) { + textReturn(statementTag, sentenceBlock, stmt, jimpleToASTMap) + } + } + + if (statementTag.iterations.isNotEmpty()) { + val iterationSentenceBlock = buildIterationsBlock(statementTag.iterations, statementTag.step, currentMethod) + sentenceBlock.iterationSentenceBlocks.add(iterationSentenceBlock) + createNextBlock = true + } + + if (recursion != null) { + if (stmt is JAssignStmt) { + val name = (stmt.rightOp as JVirtualInvokeExpr).method.name + val sentenceRecursionBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) + buildSentenceBlock(recursion, sentenceRecursionBlock, currentMethod) + sentenceBlock.recursion = Pair(name, sentenceRecursionBlock) + createNextBlock = true + } + if (stmt is JInvokeStmt) { + val name = stmt.invokeExpr.method.name + val sentenceRecursion = SimpleSentenceBlock(stringTemplates = stringTemplates) + buildSentenceBlock(recursion, sentenceRecursion, currentMethod) + sentenceBlock.recursion = Pair(name, sentenceRecursion) + createNextBlock = true + } + } + + if (createNextBlock) { + val nextSentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) + sentenceBlock.nextBlock = nextSentenceBlock + buildSentenceBlock(statementTag.next, nextSentenceBlock, currentMethod) + } else { + buildSentenceBlock(statementTag.next, sentenceBlock, currentMethod) + } + } + + /** + * Adds RecursionAssignment into sentenceBlock.stmtTexts + */ + protected fun addTextRecursion(sentenceBlock: SimpleSentenceBlock, stmt: Stmt, frequency: Int) { + if (stmt is JAssignStmt || stmt is JInvokeStmt) { + val methodName = stmt.invokeExpr.method.name + addTextRecursion(sentenceBlock, methodName, frequency) + } + } + + /** + * Adds Invoke into sentenceBlock.stmtTexts + */ + protected fun addTextInvoke(sentenceBlock: SimpleSentenceBlock, stmt: Stmt, frequency: Int) { + if (stmt is JAssignStmt || stmt is JInvokeStmt) { + val className = stmt.invokeExpr.methodRef.declaringClass.name + val methodName = stmt.invokeExpr.method.name + val methodParameterTypes = stmt.invokeExpr.method.parameterTypes + val isPrivate = stmt.invokeExpr.method.isPrivate + addTextInvoke( + sentenceBlock, + className, + methodName, + methodParameterTypes, + isPrivate, + frequency + ) + } + } + + /** + * Adds Invoke into sentenceBlock.stmtTexts + */ + protected fun addTextInvoke( + sentenceBlock: SimpleSentenceBlock, + className: String, + methodName: String, + methodParameterTypes: List, + isPrivate: Boolean, + frequency: Int + ) { + if (!shouldSkipInvoke(methodName)) + sentenceBlock.stmtTexts.add( + StmtDescription( + StmtType.Invoke, + getMethodReferenceForSymbolicTest(className, methodName, methodParameterTypes, isPrivate), + frequency + ) + ) + } + + /** + * Adds RecursionAssignment into sentenceBlock.stmtTexts + */ + protected fun addTextRecursion( + sentenceBlock: SimpleSentenceBlock, + methodName: String, + frequency: Int + ) { + if (!shouldSkipInvoke(methodName)) + sentenceBlock.stmtTexts.add( + StmtDescription( + StmtType.RecursionAssignment, + methodName, + frequency + ) + ) + } + + protected fun buildIterationsBlock( + iterations: List, + activatedStep: Step, + currentMethod: SootMethod + ): Pair> { + val result = mutableListOf() + val jimpleToASTMap = sootToAST[currentMethod] + iterations.forEach { + val sentenceBlock = SimpleSentenceBlock(stringTemplates = stringTemplates) + buildSentenceBlock(it, sentenceBlock, currentMethod) + result.add(sentenceBlock) + } + val firstStmtNode = jimpleToASTMap?.get(iterations.first().step.stmt) + val activatedNode = jimpleToASTMap?.get(activatedStep.stmt) + val line = iterations.first().line + + var iterationDescription = "" + if (firstStmtNode is Statement) { + iterationDescription = getTextIterationDescription(firstStmtNode) + } + // getTextIterationDescription can return empty description, + // that is why if else is not used here. + if (iterationDescription.isEmpty() && activatedNode is Statement) { + iterationDescription = getTextIterationDescription(activatedNode) + } + //heh, we are still looking for loop txt + if (iterationDescription.isEmpty()) { + val nearestNode = jimpleToASTMap?.nearestIterationNode(activatedNode, line) + if (nearestNode != null) { + iterationDescription = getTextIterationDescription(nearestNode) + } + } + + if (iterationDescription.isEmpty()) { + val nearestNode = jimpleToASTMap?.nearestIterationNode(firstStmtNode, line) + if (nearestNode != null) { + iterationDescription = getTextIterationDescription(nearestNode) + } + } + + return Pair(iterationDescription, result) + } +} + +data class IterationDescription(val from: Int, val to: Int, val description: String, val typeDescription: String) diff --git a/utbot-summary/src/main/kotlin/org/utbot/summary/name/SimpleNameBuilder.kt b/utbot-summary/src/main/kotlin/org/utbot/summary/name/SimpleNameBuilder.kt index 2f0b2e6c99..de8ec07763 100644 --- a/utbot-summary/src/main/kotlin/org/utbot/summary/name/SimpleNameBuilder.kt +++ b/utbot-summary/src/main/kotlin/org/utbot/summary/name/SimpleNameBuilder.kt @@ -1,466 +1,466 @@ -package org.utbot.summary.name - -import com.github.javaparser.ast.stmt.CatchClause -import com.github.javaparser.ast.stmt.ForStmt -import com.github.javaparser.ast.stmt.ThrowStmt -import org.utbot.framework.plugin.api.ConcreteExecutionFailureException -import org.utbot.framework.plugin.api.Step -import org.utbot.framework.plugin.api.exceptionOrNull -import org.utbot.framework.plugin.api.isFailure -import org.utbot.summary.AbstractTextBuilder -import org.utbot.summary.SummarySentenceConstants.FROM_TO_NAMES_TRANSITION -import org.utbot.summary.ast.JimpleToASTMap -import org.utbot.summary.comment.getExceptionReason -import org.utbot.summary.comment.getTextTypeIterationDescription -import org.utbot.summary.comment.shouldSkipInvoke -import org.utbot.summary.name.NodeConvertor.Companion.convertNodeToDisplayNameString -import org.utbot.summary.tag.BasicTypeTag -import org.utbot.summary.tag.CallOrderTag -import org.utbot.summary.tag.StatementTag -import org.utbot.summary.tag.TraceTagWithoutExecution -import org.utbot.summary.tag.UniquenessTag -import soot.SootMethod -import soot.jimple.internal.JAssignStmt -import soot.jimple.internal.JInvokeStmt -import soot.jimple.internal.JVirtualInvokeExpr - -class SimpleNameBuilder( - traceTag: TraceTagWithoutExecution, - sootToAST: MutableMap, - val methodUnderTest: SootMethod -) : - AbstractTextBuilder(traceTag, sootToAST) { - - private var testNameDescription: TestNameDescription? = null - private val testNames: MutableList = collectCandidateNames() - val fromToName = fromToName() - val name = buildMethodName() - val displayName = buildDisplayName() - - - private fun collectCandidateNames(): MutableList { - val testNames = mutableListOf() - collectTags(traceTag.rootStatementTag, testNames, methodUnderTest) - exceptionThrow(testNames) - return testNames - } - - /** - * Collects Tags and chooses node that will be used in name of the test case - * @return name of the test case - */ - private fun buildMethodName(): String { - val methodName = methodUnderTest.name.capitalize() - testNames.sortDescending() - testNameDescription = testNames.firstOrNull() - val subName = testNameDescription?.name - return if (subName != null) { - "test${methodName}_$subName" - } else { - "test$methodName" - } - } - - /** - * Should be run after build function, else testNameDescription is null and displayName will be empty - * - * @return string with the node that is used to create name of the function - * Such description can use special symbols and spaces - */ - private fun buildDisplayName(): String { - val nameDescription = testNameDescription - val sootMethod = testNameDescription?.method - val jimpleToASTMap = sootMethod?.let { sootToAST[it] } - var res = "" - if (nameDescription != null && jimpleToASTMap != null) { - val index = nameDescription.index - val step = traceTag.path[index] - val astNode = jimpleToASTMap[step.stmt] - - if (astNode != null) { - if (traceTag.result.isFailure) { - res += "Throw ${traceTag.result.exceptionOrNull()?.let { it::class.simpleName }}" - res += exceptionPlace(jimpleToASTMap) - } else if (index > 0) { - return convertNodeToDisplayNameString(astNode, step) - } - } - } - return res - } - - private fun exceptionPlace( - jimpleToASTMap: JimpleToASTMap, - placeAfter: String = " after condition: ", - placeIn: String = " in: " - ): String { - if (traceTag.path.isEmpty()) return "" - - if (traceTag.result.isFailure) { - val lastStep = traceTag.path.last() - val lastNode = jimpleToASTMap[lastStep.stmt] - if (lastNode is ThrowStmt) { - val exceptionReason = getExceptionReason(lastNode) ?: return "" - return placeAfter + convertNodeToDisplayNameString(exceptionReason, lastStep) - } else if (lastNode != null) { - return placeIn + convertNodeToDisplayNameString(lastNode, lastStep) - } - } - return "" - } - - private fun fromToName(): String { - val jimpleToASTMap = sootToAST[methodUnderTest] - val maxDepth = testNames.maxOfOrNull { it.depth } ?: 0 - - val candidateNames = testNames.returnsToUnique().filter { it.depth == maxDepth } - .filter { - it.nameType != NameType.StartIteration && it.nameType != NameType.NoIteration - }.mapNotNull { nameDescription -> - fromNameDescriptionToCandidateSimpleName(nameDescription) - }.toMutableList() - - if (traceTag.result.isFailure && jimpleToASTMap != null) { - val throwPlace = exceptionPlace(jimpleToASTMap, placeAfter = "", placeIn = "") - candidateNames.add( - 0, - DisplayNameCandidate( - throwPlace, - UniquenessTag.Unique, - traceTag.path.size - ) - ) - } - - val chosenNames = choosePairFromToNames(candidateNames) - if (chosenNames != null) { - val firstName = chosenNames.first - val secondName = chosenNames.second - if (firstName != null) { - return "$firstName $FROM_TO_NAMES_TRANSITION $secondName" - } else { - return "$FROM_TO_NAMES_TRANSITION $secondName" - } - } - return "" - } - - private fun fromNameDescriptionToCandidateSimpleName(nameDescription: TestNameDescription): DisplayNameCandidate? { - if (nameDescription.nameType == NameType.ThrowsException) { - return DisplayNameCandidate( - nameDescription.name, - nameDescription.uniquenessTag, - traceTag.path.size + 1 - ) - } - if (nameDescription.nameType == NameType.Invoke) { - return DisplayNameCandidate( - nameDescription.name, - nameDescription.uniquenessTag, - nameDescription.index - ) - } - val node = sootToAST[nameDescription.method]?.get(nameDescription.step.stmt) - if (node is CatchClause) { - return DisplayNameCandidate( - "Catch (${node.parameter})", - nameDescription.uniquenessTag, - nameDescription.index - ) - } - if (node != null) { - val name = convertNodeToDisplayNameString(node, nameDescription.step) - return DisplayNameCandidate( - name, - nameDescription.uniquenessTag, - nameDescription.index - ) - } else { - return null - } - } - - /* - * First, the method tries to find two unique tags which - * can represented as From unique tag -> To unique tag. - * Example: Unique condition -> Unique return. - * If the method didn't find two different and unique tags then - * it will try to build names in the following priority: - * 2. Partly Unique Tag -> Unique Tag - * 3. Partly Unique Tag -> Partly Unique Tag - * 4. Unique Tag -> Partly Unique Tag - * 4. Unique Tag -> Any Tag - * 5. Any Tag -> Unique Tag - * 6. Any Tag -> Partly Unique Tag - * 7. -> Unique Tag - * 8. -> Partly Unique Tag - * 9. -> Any last Tag - * otherwise, returns null - */ - private fun choosePairFromToNames(candidates: List): Pair? { - - val fromNameUnique = candidates.firstOrNull { it.uniquenessTag == UniquenessTag.Unique } - val toNameUnique = candidates.lastOrNull { it.uniquenessTag == UniquenessTag.Unique } - // from unique tag -> to unique tag - buildCandidate(fromNameUnique, toNameUnique, null)?.let { - return it - } - val fromNamePartly = candidates.firstOrNull { it.uniquenessTag == UniquenessTag.Partly } - val toNamePartly = candidates.lastOrNull { it.uniquenessTag == UniquenessTag.Partly } - // from partly unique tag -> to unique tag - // from partly unique tag -> to partly unique tag - buildCandidate(fromNamePartly, toNameUnique, toNamePartly)?.let { - return it - } - val toNameAny = candidates.lastOrNull() - // from unique tag -> to partly unique - // from unique tag -> to any - buildCandidate(fromNameUnique, toNamePartly, toNameAny)?.let { - return it - } - val fromNameAny = candidates.firstOrNull() - // from any tag -> to unique tag - // from any tag -> to partly unique tag - buildCandidate(fromNameAny, toNameUnique, toNamePartly)?.let { - return it - } - - if (toNameUnique != null) { - return Pair(null, toNameUnique.name) - } - if (toNamePartly != null) { - return Pair(null, toNamePartly.name) - } - if (toNameAny != null) { - return Pair(null, toNameAny.name) - } - return null - } - - /** - * The method tries to build a pair name with an attempt order: - * 1. from candidate name -> to candidate name 1 - * 2. from candidate name -> to candidate name 2 - * otherwise, returns null - */ - fun buildCandidate( - fromCandidateName: DisplayNameCandidate?, - toCandidateName1: DisplayNameCandidate?, - toCandidateName2: DisplayNameCandidate? - ): Pair? { - if (fromCandidateName != null && toCandidateName1 != null - && fromCandidateName.name != toCandidateName1.name - && fromCandidateName.index < toCandidateName1.index - ) { - return Pair(fromCandidateName.name, toCandidateName1.name) - } - if (fromCandidateName != null && toCandidateName2 != null - && fromCandidateName.name != toCandidateName2.name - && fromCandidateName.index < toCandidateName2.index - ) { - return Pair(fromCandidateName.name, toCandidateName2.name) - } - return null - } - - /** - * [TraceTagWithoutExecution.path] could be empty in case exception is thrown not in source code but in engine - * (for example, [ConcreteExecutionFailureException]). - */ - private fun exceptionThrow(testNames: MutableList) { - val throwsException = traceTag.result.exceptionOrNull()?.let { it::class.simpleName } - if (!(throwsException.isNullOrEmpty() || traceTag.path.isEmpty())) { - testNames.add(TestNameDescription( - "Throw$throwsException", - testNames.maxOfOrNull { it.depth } ?: 0, - testNames.maxOfOrNull { it.line } ?: 0, - UniquenessTag.Unique, - NameType.ThrowsException, - testNames.maxOfOrNull { it.index } ?: 0, - traceTag.path.last(), - methodUnderTest - )) - } - } - - private fun collectTags( - statementTag: StatementTag?, - testNames: MutableList, - currentMethod: SootMethod - ) { - val jimpleToASTMap = sootToAST[currentMethod] - if (statementTag == null) return - if (jimpleToASTMap == null) return - val recursion = statementTag.recursion - val stmt = statementTag.step.stmt - val depth = statementTag.step.depth - val line = statementTag.line - val invoke = statementTag.invoke - - val localNoIterations = statementTagSkippedIteration(statementTag, currentMethod) - if (localNoIterations.isNotEmpty()) { - localNoIterations.forEach { - testNames.add( - TestNameDescription( - "NoIteration${it.typeDescription}", - depth, - it.from, - UniquenessTag.Unique, - NameType.NoIteration, - statementTag.index, - statementTag.step, - currentMethod - ) - ) - methodToNoIterationDescription[currentMethod]?.remove(it) - } - } - - val invokeSootMethod = statementTag.invokeSootMethod() - if (invoke != null && invokeSootMethod != null) { - val beforeInvokeNumberNames = testNames.size - collectTags(invoke, testNames, invokeSootMethod) - if (testNames.size != beforeInvokeNumberNames) { - testNames.add( - beforeInvokeNumberNames, - TestNameDescription( - invokeSootMethod.name, - depth + 1, - 0, - UniquenessTag.Common, - NameType.Invoke, - statementTag.index, - statementTag.step, - invokeSootMethod - ) - ) - } - } - - if (jimpleToASTMap[statementTag.step.stmt] !is ForStmt) { - val nodeAST = jimpleToASTMap[stmt] - if (nodeAST != null) { - if (statementTag.basicTypeTag == BasicTypeTag.Condition && statementTag.callOrderTag == CallOrderTag.First) { - var conditionName: String? = null - if (statementTag.uniquenessTag == UniquenessTag.Unique - || (statementTag.uniquenessTag == UniquenessTag.Partly && statementTag.executionFrequency == 1) - ) { - conditionName = NodeConvertor.convertNodeToString(nodeAST, statementTag.step) - } - conditionName?.let { - testNames.add( - TestNameDescription( - it, - depth, - line, - statementTag.uniquenessTag, - NameType.Condition, - statementTag.index, - statementTag.step, - currentMethod - ) - ) - } - } - - - var prefix = "" - var name = NodeConvertor.convertNodeToString(nodeAST, statementTag.step) - var type: NameType? = null - - if (statementTag.basicTypeTag == BasicTypeTag.SwitchCase && statementTag.uniquenessTag == UniquenessTag.Unique) { - type = NameType.SwitchCase - } else if (statementTag.basicTypeTag == BasicTypeTag.CaughtException) { - type = NameType.CaughtException - prefix = "Catch" - } else if (statementTag.basicTypeTag == BasicTypeTag.Return) { - type = NameType.Return - prefix = "Return" - name = name ?: "" - } else if (statementTag.basicTypeTag == BasicTypeTag.Invoke && statementTag.uniquenessTag == UniquenessTag.Unique) { - val methodName = stmt.invokeExpr.method.name.capitalize() - if (!shouldSkipInvoke(methodName)) { - if (stmt is JAssignStmt || stmt is JInvokeStmt) { - type = NameType.Invoke - prefix += stmt.invokeExpr.methodRef.declaringClass.javaStyleName.substringBefore('$') //class name - prefix += methodName - name = - "" //todo change var name to val name, everything should be mapped through .convertNodeToString - } - } - } - - if (type != null) { - testNames.add( - TestNameDescription( - prefix + name, - depth, - line, - statementTag.uniquenessTag, - type, - statementTag.index, - statementTag.step, - currentMethod - ) - ) - } - } - } - - if (statementTag.iterations.isNotEmpty()) { - val firstNode = jimpleToASTMap[statementTag.iterations.first().step.stmt] - - val iterationDescription = if (firstNode != null) getTextTypeIterationDescription(firstNode) else null - - if (iterationDescription != null && iterationDescription.isNotEmpty()) { - testNames.add( - TestNameDescription( - "Iterate$iterationDescription", - depth, - line, - UniquenessTag.Partly, - NameType.StartIteration, - statementTag.index, - statementTag.step, - currentMethod - ) - ) - } - - statementTag.iterations.forEach { - collectTags(it, testNames, currentMethod) - } - } - - if (recursion != null) { - val name = when (stmt) { - is JAssignStmt -> "Recursion" + (stmt.rightOp as JVirtualInvokeExpr).method.name //todo through .convertNodeToString - is JInvokeStmt -> "Recursion" + stmt.invokeExpr.method.name //todo through .convertNodeToString - else -> "" - } - if (name.isNotEmpty()) { - testNames.add( - TestNameDescription( - name, - depth, - line, - statementTag.uniquenessTag, - NameType.Invoke, - statementTag.index, - statementTag.step, - currentMethod - ) - ) - collectTags(recursion, testNames, currentMethod) - } - } - collectTags(statementTag.next, testNames, currentMethod) - } - - override fun conditionStep(step: Step, reversed: Boolean, jimpleToASTMap: JimpleToASTMap): String { - val nodeAST = jimpleToASTMap[step.stmt] - return if (nodeAST != null) { - NodeConvertor.convertNodeToString(nodeAST, step) ?: "" - } else "" - } +package org.utbot.summary.name + +import com.github.javaparser.ast.stmt.CatchClause +import com.github.javaparser.ast.stmt.ForStmt +import com.github.javaparser.ast.stmt.ThrowStmt +import org.utbot.framework.plugin.api.InstrumentedProcessDeathException +import org.utbot.framework.plugin.api.Step +import org.utbot.framework.plugin.api.exceptionOrNull +import org.utbot.framework.plugin.api.isFailure +import org.utbot.summary.AbstractTextBuilder +import org.utbot.summary.SummarySentenceConstants.FROM_TO_NAMES_TRANSITION +import org.utbot.summary.ast.JimpleToASTMap +import org.utbot.summary.comment.getExceptionReason +import org.utbot.summary.comment.getTextTypeIterationDescription +import org.utbot.summary.comment.shouldSkipInvoke +import org.utbot.summary.name.NodeConvertor.Companion.convertNodeToDisplayNameString +import org.utbot.summary.tag.BasicTypeTag +import org.utbot.summary.tag.CallOrderTag +import org.utbot.summary.tag.StatementTag +import org.utbot.summary.tag.TraceTagWithoutExecution +import org.utbot.summary.tag.UniquenessTag +import soot.SootMethod +import soot.jimple.internal.JAssignStmt +import soot.jimple.internal.JInvokeStmt +import soot.jimple.internal.JVirtualInvokeExpr + +class SimpleNameBuilder( + traceTag: TraceTagWithoutExecution, + sootToAST: MutableMap, + val methodUnderTest: SootMethod +) : + AbstractTextBuilder(traceTag, sootToAST) { + + private var testNameDescription: TestNameDescription? = null + private val testNames: MutableList = collectCandidateNames() + val fromToName = fromToName() + val name = buildMethodName() + val displayName = buildDisplayName() + + + private fun collectCandidateNames(): MutableList { + val testNames = mutableListOf() + collectTags(traceTag.rootStatementTag, testNames, methodUnderTest) + exceptionThrow(testNames) + return testNames + } + + /** + * Collects Tags and chooses node that will be used in name of the test case + * @return name of the test case + */ + private fun buildMethodName(): String { + val methodName = methodUnderTest.name.capitalize() + testNames.sortDescending() + testNameDescription = testNames.firstOrNull() + val subName = testNameDescription?.name + return if (subName != null) { + "test${methodName}_$subName" + } else { + "test$methodName" + } + } + + /** + * Should be run after build function, else testNameDescription is null and displayName will be empty + * + * @return string with the node that is used to create name of the function + * Such description can use special symbols and spaces + */ + private fun buildDisplayName(): String { + val nameDescription = testNameDescription + val sootMethod = testNameDescription?.method + val jimpleToASTMap = sootMethod?.let { sootToAST[it] } + var res = "" + if (nameDescription != null && jimpleToASTMap != null) { + val index = nameDescription.index + val step = traceTag.path[index] + val astNode = jimpleToASTMap[step.stmt] + + if (astNode != null) { + if (traceTag.result.isFailure) { + res += "Throw ${traceTag.result.exceptionOrNull()?.let { it::class.simpleName }}" + res += exceptionPlace(jimpleToASTMap) + } else if (index > 0) { + return convertNodeToDisplayNameString(astNode, step) + } + } + } + return res + } + + private fun exceptionPlace( + jimpleToASTMap: JimpleToASTMap, + placeAfter: String = " after condition: ", + placeIn: String = " in: " + ): String { + if (traceTag.path.isEmpty()) return "" + + if (traceTag.result.isFailure) { + val lastStep = traceTag.path.last() + val lastNode = jimpleToASTMap[lastStep.stmt] + if (lastNode is ThrowStmt) { + val exceptionReason = getExceptionReason(lastNode) ?: return "" + return placeAfter + convertNodeToDisplayNameString(exceptionReason, lastStep) + } else if (lastNode != null) { + return placeIn + convertNodeToDisplayNameString(lastNode, lastStep) + } + } + return "" + } + + private fun fromToName(): String { + val jimpleToASTMap = sootToAST[methodUnderTest] + val maxDepth = testNames.maxOfOrNull { it.depth } ?: 0 + + val candidateNames = testNames.returnsToUnique().filter { it.depth == maxDepth } + .filter { + it.nameType != NameType.StartIteration && it.nameType != NameType.NoIteration + }.mapNotNull { nameDescription -> + fromNameDescriptionToCandidateSimpleName(nameDescription) + }.toMutableList() + + if (traceTag.result.isFailure && jimpleToASTMap != null) { + val throwPlace = exceptionPlace(jimpleToASTMap, placeAfter = "", placeIn = "") + candidateNames.add( + 0, + DisplayNameCandidate( + throwPlace, + UniquenessTag.Unique, + traceTag.path.size + ) + ) + } + + val chosenNames = choosePairFromToNames(candidateNames) + if (chosenNames != null) { + val firstName = chosenNames.first + val secondName = chosenNames.second + if (firstName != null) { + return "$firstName $FROM_TO_NAMES_TRANSITION $secondName" + } else { + return "$FROM_TO_NAMES_TRANSITION $secondName" + } + } + return "" + } + + private fun fromNameDescriptionToCandidateSimpleName(nameDescription: TestNameDescription): DisplayNameCandidate? { + if (nameDescription.nameType == NameType.ThrowsException) { + return DisplayNameCandidate( + nameDescription.name, + nameDescription.uniquenessTag, + traceTag.path.size + 1 + ) + } + if (nameDescription.nameType == NameType.Invoke) { + return DisplayNameCandidate( + nameDescription.name, + nameDescription.uniquenessTag, + nameDescription.index + ) + } + val node = sootToAST[nameDescription.method]?.get(nameDescription.step.stmt) + if (node is CatchClause) { + return DisplayNameCandidate( + "Catch (${node.parameter})", + nameDescription.uniquenessTag, + nameDescription.index + ) + } + if (node != null) { + val name = convertNodeToDisplayNameString(node, nameDescription.step) + return DisplayNameCandidate( + name, + nameDescription.uniquenessTag, + nameDescription.index + ) + } else { + return null + } + } + + /* + * First, the method tries to find two unique tags which + * can represented as From unique tag -> To unique tag. + * Example: Unique condition -> Unique return. + * If the method didn't find two different and unique tags then + * it will try to build names in the following priority: + * 2. Partly Unique Tag -> Unique Tag + * 3. Partly Unique Tag -> Partly Unique Tag + * 4. Unique Tag -> Partly Unique Tag + * 4. Unique Tag -> Any Tag + * 5. Any Tag -> Unique Tag + * 6. Any Tag -> Partly Unique Tag + * 7. -> Unique Tag + * 8. -> Partly Unique Tag + * 9. -> Any last Tag + * otherwise, returns null + */ + private fun choosePairFromToNames(candidates: List): Pair? { + + val fromNameUnique = candidates.firstOrNull { it.uniquenessTag == UniquenessTag.Unique } + val toNameUnique = candidates.lastOrNull { it.uniquenessTag == UniquenessTag.Unique } + // from unique tag -> to unique tag + buildCandidate(fromNameUnique, toNameUnique, null)?.let { + return it + } + val fromNamePartly = candidates.firstOrNull { it.uniquenessTag == UniquenessTag.Partly } + val toNamePartly = candidates.lastOrNull { it.uniquenessTag == UniquenessTag.Partly } + // from partly unique tag -> to unique tag + // from partly unique tag -> to partly unique tag + buildCandidate(fromNamePartly, toNameUnique, toNamePartly)?.let { + return it + } + val toNameAny = candidates.lastOrNull() + // from unique tag -> to partly unique + // from unique tag -> to any + buildCandidate(fromNameUnique, toNamePartly, toNameAny)?.let { + return it + } + val fromNameAny = candidates.firstOrNull() + // from any tag -> to unique tag + // from any tag -> to partly unique tag + buildCandidate(fromNameAny, toNameUnique, toNamePartly)?.let { + return it + } + + if (toNameUnique != null) { + return Pair(null, toNameUnique.name) + } + if (toNamePartly != null) { + return Pair(null, toNamePartly.name) + } + if (toNameAny != null) { + return Pair(null, toNameAny.name) + } + return null + } + + /** + * The method tries to build a pair name with an attempt order: + * 1. from candidate name -> to candidate name 1 + * 2. from candidate name -> to candidate name 2 + * otherwise, returns null + */ + fun buildCandidate( + fromCandidateName: DisplayNameCandidate?, + toCandidateName1: DisplayNameCandidate?, + toCandidateName2: DisplayNameCandidate? + ): Pair? { + if (fromCandidateName != null && toCandidateName1 != null + && fromCandidateName.name != toCandidateName1.name + && fromCandidateName.index < toCandidateName1.index + ) { + return Pair(fromCandidateName.name, toCandidateName1.name) + } + if (fromCandidateName != null && toCandidateName2 != null + && fromCandidateName.name != toCandidateName2.name + && fromCandidateName.index < toCandidateName2.index + ) { + return Pair(fromCandidateName.name, toCandidateName2.name) + } + return null + } + + /** + * [TraceTagWithoutExecution.path] could be empty in case exception is thrown not in source code but in engine + * (for example, [InstrumentedProcessDeathException]). + */ + private fun exceptionThrow(testNames: MutableList) { + val throwsException = traceTag.result.exceptionOrNull()?.let { it::class.simpleName } + if (!(throwsException.isNullOrEmpty() || traceTag.path.isEmpty())) { + testNames.add(TestNameDescription( + "Throw$throwsException", + testNames.maxOfOrNull { it.depth } ?: 0, + testNames.maxOfOrNull { it.line } ?: 0, + UniquenessTag.Unique, + NameType.ThrowsException, + testNames.maxOfOrNull { it.index } ?: 0, + traceTag.path.last(), + methodUnderTest + )) + } + } + + private fun collectTags( + statementTag: StatementTag?, + testNames: MutableList, + currentMethod: SootMethod + ) { + val jimpleToASTMap = sootToAST[currentMethod] + if (statementTag == null) return + if (jimpleToASTMap == null) return + val recursion = statementTag.recursion + val stmt = statementTag.step.stmt + val depth = statementTag.step.depth + val line = statementTag.line + val invoke = statementTag.invoke + + val localNoIterations = statementTagSkippedIteration(statementTag, currentMethod) + if (localNoIterations.isNotEmpty()) { + localNoIterations.forEach { + testNames.add( + TestNameDescription( + "NoIteration${it.typeDescription}", + depth, + it.from, + UniquenessTag.Unique, + NameType.NoIteration, + statementTag.index, + statementTag.step, + currentMethod + ) + ) + methodToNoIterationDescription[currentMethod]?.remove(it) + } + } + + val invokeSootMethod = statementTag.invokeSootMethod() + if (invoke != null && invokeSootMethod != null) { + val beforeInvokeNumberNames = testNames.size + collectTags(invoke, testNames, invokeSootMethod) + if (testNames.size != beforeInvokeNumberNames) { + testNames.add( + beforeInvokeNumberNames, + TestNameDescription( + invokeSootMethod.name, + depth + 1, + 0, + UniquenessTag.Common, + NameType.Invoke, + statementTag.index, + statementTag.step, + invokeSootMethod + ) + ) + } + } + + if (jimpleToASTMap[statementTag.step.stmt] !is ForStmt) { + val nodeAST = jimpleToASTMap[stmt] + if (nodeAST != null) { + if (statementTag.basicTypeTag == BasicTypeTag.Condition && statementTag.callOrderTag == CallOrderTag.First) { + var conditionName: String? = null + if (statementTag.uniquenessTag == UniquenessTag.Unique + || (statementTag.uniquenessTag == UniquenessTag.Partly && statementTag.executionFrequency == 1) + ) { + conditionName = NodeConvertor.convertNodeToString(nodeAST, statementTag.step) + } + conditionName?.let { + testNames.add( + TestNameDescription( + it, + depth, + line, + statementTag.uniquenessTag, + NameType.Condition, + statementTag.index, + statementTag.step, + currentMethod + ) + ) + } + } + + + var prefix = "" + var name = NodeConvertor.convertNodeToString(nodeAST, statementTag.step) + var type: NameType? = null + + if (statementTag.basicTypeTag == BasicTypeTag.SwitchCase && statementTag.uniquenessTag == UniquenessTag.Unique) { + type = NameType.SwitchCase + } else if (statementTag.basicTypeTag == BasicTypeTag.CaughtException) { + type = NameType.CaughtException + prefix = "Catch" + } else if (statementTag.basicTypeTag == BasicTypeTag.Return) { + type = NameType.Return + prefix = "Return" + name = name ?: "" + } else if (statementTag.basicTypeTag == BasicTypeTag.Invoke && statementTag.uniquenessTag == UniquenessTag.Unique) { + val methodName = stmt.invokeExpr.method.name.capitalize() + if (!shouldSkipInvoke(methodName)) { + if (stmt is JAssignStmt || stmt is JInvokeStmt) { + type = NameType.Invoke + prefix += stmt.invokeExpr.methodRef.declaringClass.javaStyleName.substringBefore('$') //class name + prefix += methodName + name = + "" //todo change var name to val name, everything should be mapped through .convertNodeToString + } + } + } + + if (type != null) { + testNames.add( + TestNameDescription( + prefix + name, + depth, + line, + statementTag.uniquenessTag, + type, + statementTag.index, + statementTag.step, + currentMethod + ) + ) + } + } + } + + if (statementTag.iterations.isNotEmpty()) { + val firstNode = jimpleToASTMap[statementTag.iterations.first().step.stmt] + + val iterationDescription = if (firstNode != null) getTextTypeIterationDescription(firstNode) else null + + if (iterationDescription != null && iterationDescription.isNotEmpty()) { + testNames.add( + TestNameDescription( + "Iterate$iterationDescription", + depth, + line, + UniquenessTag.Partly, + NameType.StartIteration, + statementTag.index, + statementTag.step, + currentMethod + ) + ) + } + + statementTag.iterations.forEach { + collectTags(it, testNames, currentMethod) + } + } + + if (recursion != null) { + val name = when (stmt) { + is JAssignStmt -> "Recursion" + (stmt.rightOp as JVirtualInvokeExpr).method.name //todo through .convertNodeToString + is JInvokeStmt -> "Recursion" + stmt.invokeExpr.method.name //todo through .convertNodeToString + else -> "" + } + if (name.isNotEmpty()) { + testNames.add( + TestNameDescription( + name, + depth, + line, + statementTag.uniquenessTag, + NameType.Invoke, + statementTag.index, + statementTag.step, + currentMethod + ) + ) + collectTags(recursion, testNames, currentMethod) + } + } + collectTags(statementTag.next, testNames, currentMethod) + } + + override fun conditionStep(step: Step, reversed: Boolean, jimpleToASTMap: JimpleToASTMap): String { + val nodeAST = jimpleToASTMap[step.stmt] + return if (nodeAST != null) { + NodeConvertor.convertNodeToString(nodeAST, step) ?: "" + } else "" + } } \ No newline at end of file