|
1 | 1 | package org.utbot.framework.minimization |
2 | 2 |
|
3 | 3 | import 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 |
9 | 4 | import 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 |
13 | 5 | import org.utbot.framework.plugin.api.UtExecution |
14 | 6 | import org.utbot.framework.plugin.api.UtExecutionFailure |
15 | 7 | import 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 |
22 | 8 | import 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.instrumentation.instrumentation.execution.constructors.UtModelConstructor |
24 | 11 |
|
25 | 12 |
|
26 | 13 | /** |
@@ -53,16 +40,21 @@ fun <T : Any> minimizeTestCase( |
53 | 40 |
|
54 | 41 | fun minimizeExecutions(executions: List<UtExecution>): List<UtExecution> { |
55 | 42 | val unknownCoverageExecutions = |
56 | | - executions.indices.filter { executions[it].coverage?.coveredInstructions?.isEmpty() ?: true }.toSet() |
| 43 | + executions.filter { it.coverage?.coveredInstructions.isNullOrEmpty() }.toSet() |
57 | 44 | // ^^^ here we add executions with empty or null coverage, because it happens only if a concrete execution failed, |
58 | 45 | // so we don't know the actual coverage for such executions |
59 | 46 |
|
60 | | - val filteredExecutions = executions.filterIndexed { idx, _ -> idx !in unknownCoverageExecutions } |
| 47 | + val filteredExecutions = executions |
| 48 | + .subtract(unknownCoverageExecutions) |
| 49 | + .groupBy { it.coverage }.values |
| 50 | + .map { executionsWithEqualCoverage -> executionsWithEqualCoverage.chooseOneExecution() } |
61 | 51 | val (mapping, executionToPriorityMapping) = buildMapping(filteredExecutions) |
62 | 52 |
|
63 | | - val usedExecutionIndexes = (GreedyEssential.minimize(mapping, executionToPriorityMapping) + unknownCoverageExecutions).toSet() |
| 53 | + val usedExecutionIndexes = GreedyEssential.minimize(mapping, executionToPriorityMapping) |
64 | 54 |
|
65 | | - val usedMinimizedExecutions = executions.filterIndexed { idx, _ -> idx in usedExecutionIndexes } |
| 55 | + val usedMinimizedExecutions = |
| 56 | + filteredExecutions.filterIndexed { idx, _ -> idx in usedExecutionIndexes } + |
| 57 | + unknownCoverageExecutions |
66 | 58 |
|
67 | 59 | return if (UtSettings.minimizeCrashExecutions) { |
68 | 60 | usedMinimizedExecutions.filteredCrashExecutions() |
@@ -192,55 +184,20 @@ private fun List<UtExecution>.filteredCrashExecutions(): List<UtExecution> { |
192 | 184 |
|
193 | 185 | val notCrashExecutions = filterNot { it.result is UtConcreteExecutionFailure } |
194 | 186 |
|
195 | | - return notCrashExecutions + crashExecutions.chooseMinimalCrashExecution() |
| 187 | + return notCrashExecutions + crashExecutions.chooseOneExecution() |
196 | 188 | } |
197 | 189 |
|
198 | 190 | /** |
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]. |
| 191 | + * Chooses one execution with the highest [execution priority][getExecutionPriority]. If multiple executions |
| 192 | + * have the same priority, then the one with the [smallest][calculateSize] [UtExecution.stateBefore] is chosen. |
| 193 | + * |
| 194 | + * Only [UtExecution.stateBefore] is considered, because [UtExecution.result] and [UtExecution.stateAfter] |
| 195 | + * don't represent true picture as they are limited by [construction depth][UtModelConstructor.maxDepth] and their |
| 196 | + * sizes can't be calculated for crushed executions. |
201 | 197 | */ |
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 | | - } |
| 198 | +private fun List<UtExecution>.chooseOneExecution(): UtExecution = minWithOrNull( |
| 199 | + compareBy({ it.getExecutionPriority() }, { it.stateBefore.calculateSize() }) |
| 200 | +) ?: error("Cannot find minimal execution within empty executions") |
244 | 201 |
|
245 | 202 | /** |
246 | 203 | * Extends the [instructionsWithoutExtra] with one extra instruction if the [result] is |
@@ -274,6 +231,8 @@ private fun Throwable.exceptionToInfo(): String = |
274 | 231 | * Returns an execution priority. [UtSymbolicExecution] has the highest priority |
275 | 232 | * over other executions like [UtFuzzedExecution], [UtFailedExecution], etc. |
276 | 233 | * |
| 234 | + * NOTE! Smaller number represents higher priority. |
| 235 | + * |
277 | 236 | * See [https://github.com/UnitTestBot/UTBotJava/issues/1504] for more details. |
278 | 237 | */ |
279 | 238 | private fun UtExecution.getExecutionPriority(): Int = when (this) { |
|
0 commit comments