11package org.utbot.framework.minimization
22
33import org.utbot.framework.UtSettings
4- import org.utbot.framework.plugin.api.EnvironmentModels
5- import org.utbot.framework.plugin.api.UtArrayModel
6- import org.utbot.framework.plugin.api.UtAssembleModel
7- import org.utbot.framework.plugin.api.UtClassRefModel
8- import org.utbot.framework.plugin.api.UtCompositeModel
94import org.utbot.framework.plugin.api.UtConcreteExecutionFailure
10- import org.utbot.framework.plugin.api.UtDirectSetFieldModel
11- import org.utbot.framework.plugin.api.UtEnumConstantModel
12- import org.utbot.framework.plugin.api.UtExecutableCallModel
135import org.utbot.framework.plugin.api.UtExecution
146import org.utbot.framework.plugin.api.UtExecutionFailure
157import org.utbot.framework.plugin.api.UtExecutionResult
16- import org.utbot.framework.plugin.api.UtLambdaModel
17- import org.utbot.framework.plugin.api.UtModel
18- import org.utbot.framework.plugin.api.UtNullModel
19- import org.utbot.framework.plugin.api.UtPrimitiveModel
20- import org.utbot.framework.plugin.api.UtStatementCallModel
21- import org.utbot.framework.plugin.api.UtStatementModel
228import org.utbot.framework.plugin.api.UtSymbolicExecution
23- import org.utbot.framework.plugin.api.UtVoidModel
9+ import org.utbot.framework.util.calculateSize
10+ import org.utbot.fuzzer.UtFuzzedExecution
11+ import org.utbot.instrumentation.instrumentation.execution.constructors.UtModelConstructor
2412
2513
2614/* *
@@ -53,16 +41,17 @@ fun <T : Any> minimizeTestCase(
5341
5442fun minimizeExecutions (executions : List <UtExecution >): List <UtExecution > {
5543 val unknownCoverageExecutions =
56- executions.indices. filter { executions[it] .coverage?.coveredInstructions?.isEmpty() ? : true }.toSet()
44+ executions.filter { it .coverage?.coveredInstructions.isNullOrEmpty() }.toSet()
5745 // ^^^ here we add executions with empty or null coverage, because it happens only if a concrete execution failed,
5846 // so we don't know the actual coverage for such executions
5947
60- val filteredExecutions = executions.filterIndexed { idx, _ -> idx !in unknownCoverageExecutions }
48+ val filteredExecutions = filterOutDuplicateCoverages( executions - unknownCoverageExecutions)
6149 val (mapping, executionToPriorityMapping) = buildMapping(filteredExecutions)
6250
63- val usedExecutionIndexes = (GreedyEssential .minimize(mapping, executionToPriorityMapping) + unknownCoverageExecutions).toSet()
51+ val usedFilteredExecutionIndexes = GreedyEssential .minimize(mapping, executionToPriorityMapping).toSet()
52+ val usedFilteredExecutions = filteredExecutions.filterIndexed { idx, _ -> idx in usedFilteredExecutionIndexes }
6453
65- val usedMinimizedExecutions = executions.filterIndexed { idx, _ -> idx in usedExecutionIndexes }
54+ val usedMinimizedExecutions = usedFilteredExecutions + unknownCoverageExecutions
6655
6756 return if (UtSettings .minimizeCrashExecutions) {
6857 usedMinimizedExecutions.filteredCrashExecutions()
@@ -71,6 +60,18 @@ fun minimizeExecutions(executions: List<UtExecution>): List<UtExecution> {
7160 }
7261}
7362
63+ private fun filterOutDuplicateCoverages (executions : List <UtExecution >): List <UtExecution > {
64+ val (executionIdxToCoveredEdgesMap, _) = buildMapping(executions)
65+ return executions
66+ .withIndex()
67+ // we need to group by coveredEdges and not just Coverage to not miss exceptional edges that buildMapping() function adds
68+ .groupBy(
69+ keySelector = { indexedExecution -> executionIdxToCoveredEdgesMap[indexedExecution.index] },
70+ valueTransform = { indexedExecution -> indexedExecution.value }
71+ ).values
72+ .map { executionsWithEqualCoverage -> executionsWithEqualCoverage.chooseOneExecution() }
73+ }
74+
7475/* *
7576 * Groups the [executions] by their `paths` on `first` [branchInstructionsNumber] `branch` instructions.
7677 *
@@ -192,55 +193,20 @@ private fun List<UtExecution>.filteredCrashExecutions(): List<UtExecution> {
192193
193194 val notCrashExecutions = filterNot { it.result is UtConcreteExecutionFailure }
194195
195- return notCrashExecutions + crashExecutions.chooseMinimalCrashExecution ()
196+ return notCrashExecutions + crashExecutions.chooseOneExecution ()
196197}
197198
198199/* *
199- * As for now crash execution can only be produced by Concrete Executor, it does not have [UtExecution.stateAfter] and
200- * [UtExecution.result] is [UtExecutionFailure], so we check only [UtExecution.stateBefore].
200+ * Chooses one execution with the highest [execution priority][getExecutionPriority]. If multiple executions
201+ * have the same priority, then the one with the [smallest][calculateSize] [UtExecution.stateBefore] is chosen.
202+ *
203+ * Only [UtExecution.stateBefore] is considered, because [UtExecution.result] and [UtExecution.stateAfter]
204+ * don't represent true picture as they are limited by [construction depth][UtModelConstructor.maxDepth] and their
205+ * sizes can't be calculated for crushed executions.
201206 */
202- private fun List<UtExecution>.chooseMinimalCrashExecution (): UtExecution = minByOrNull {
203- it.stateBefore.calculateSize()
204- } ? : error(" Cannot find minimal crash execution within empty executions" )
205-
206- private fun EnvironmentModels.calculateSize (): Int {
207- val thisInstanceSize = thisInstance?.calculateSize() ? : 0
208- val parametersSize = parameters.sumOf { it.calculateSize() }
209- val staticsSize = statics.values.sumOf { it.calculateSize() }
210-
211- return thisInstanceSize + parametersSize + staticsSize
212- }
213-
214- /* *
215- * We assume that "size" for "common" models is 1, 0 for [UtVoidModel] (as they do not return anything) and
216- * [UtPrimitiveModel] and [UtNullModel] (we use them as literals in codegen), summarising for all statements for [UtAssembleModel] and
217- * summarising for all fields and mocks for [UtCompositeModel]. As [UtCompositeModel] could be recursive, we need to
218- * store it in [used]. Moreover, if we already calculate size for [this], it means that we will use already created
219- * variable by this model and do not need to create it again, so size should be equal to 0.
220- */
221- private fun UtModel.calculateSize (used : MutableSet <UtModel > = mutableSetOf()): Int {
222- if (this in used) return 0
223-
224- used + = this
225-
226- return when (this ) {
227- is UtNullModel , is UtPrimitiveModel , UtVoidModel -> 0
228- is UtClassRefModel , is UtEnumConstantModel , is UtArrayModel -> 1
229- is UtAssembleModel -> {
230- 1 + instantiationCall.calculateSize(used) + modificationsChain.sumOf { it.calculateSize(used) }
231- }
232- is UtCompositeModel -> 1 + fields.values.sumOf { it.calculateSize(used) }
233- is UtLambdaModel -> 1 + capturedValues.sumOf { it.calculateSize(used) }
234- // PythonModel, JsUtModel, UtSpringContextModel may be here
235- else -> 0
236- }
237- }
238-
239- private fun UtStatementModel.calculateSize (used : MutableSet <UtModel > = mutableSetOf()): Int =
240- when (this ) {
241- is UtDirectSetFieldModel -> 1 + fieldModel.calculateSize(used) + instance.calculateSize(used)
242- is UtStatementCallModel -> 1 + params.sumOf { it.calculateSize(used) } + (instance?.calculateSize(used) ? : 0 )
243- }
207+ private fun List<UtExecution>.chooseOneExecution (): UtExecution = minWithOrNull(
208+ compareBy({ it.getExecutionPriority() }, { it.stateBefore.calculateSize() })
209+ ) ? : error(" Cannot find minimal execution within empty executions" )
244210
245211/* *
246212 * Extends the [instructionsWithoutExtra] with one extra instruction if the [result] is
@@ -274,6 +240,8 @@ private fun Throwable.exceptionToInfo(): String =
274240 * Returns an execution priority. [UtSymbolicExecution] has the highest priority
275241 * over other executions like [UtFuzzedExecution], [UtFailedExecution], etc.
276242 *
243+ * NOTE! Smaller number represents higher priority.
244+ *
277245 * See [https://github.com/UnitTestBot/UTBotJava/issues/1504] for more details.
278246 */
279247private fun UtExecution.getExecutionPriority (): Int = when (this ) {
0 commit comments