diff --git a/utbot-framework-test/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt b/utbot-framework-test/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt index 993f23059a..2a96a677de 100644 --- a/utbot-framework-test/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt +++ b/utbot-framework-test/src/test/kotlin/org/utbot/framework/minimization/MinimizationGreedyEssentialTest.kt @@ -68,4 +68,18 @@ class MinimizationGreedyEssentialTest { val minimizedExecutions = GreedyEssential.minimize(executions) assertEquals((1..size).toList(), minimizedExecutions.sorted()) } + + @Test + fun testExecutionPriority() { + val executions = mapOf( + 0 to listOf(0, 1, 2, 3, 4), + 1 to listOf(0, 1, 2, 3, 4) + ) + val executionToPriority = mapOf( + 0 to 1, + 1 to 0 + ) + val minimizedExecutions = GreedyEssential.minimize(executions, executionToPriority) + assertEquals(listOf(1), minimizedExecutions) + } } \ No newline at end of file diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/GreedyEssential.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/GreedyEssential.kt index bbbcafd17f..4f229e3712 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/GreedyEssential.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/GreedyEssential.kt @@ -10,7 +10,8 @@ private inline class LineNumber(val number: Int) * [Greedy essential algorithm](CONFLUENCE:Test+Minimization) */ class GreedyEssential private constructor( - executionToCoveredLines: Map> + executionToCoveredLines: Map>, + executionToPriority: Map ) { private val executionToUsefulLines: Map> = executionToCoveredLines @@ -23,8 +24,11 @@ class GreedyEssential private constructor( .mapValues { it.value.toMutableSet() } private val executionByPriority = - PriorityQueue(compareByDescending> { it.second }.thenBy { it.first.number }) - .apply { + PriorityQueue( + compareBy> { executionToPriority[it.first] } + .thenByDescending { it.second } + .thenBy { it.first.number } + ).apply { addAll( executionToCoveredLines .keys @@ -89,12 +93,16 @@ class GreedyEssential private constructor( * * @return retained execution ids. */ - fun minimize(executions: Map>): List { + fun minimize(executions: Map>, executionToPriority: Map = mapOf()): List { val convertedExecutions = executions .entries .associate { (execution, lines) -> ExecutionNumber(execution) to lines.map { LineNumber(it) } } - val prioritizer = GreedyEssential(convertedExecutions) + val convertedExecutionToPriority = executionToPriority + .entries + .associate { (execution, priority) -> ExecutionNumber(execution) to priority } + + val prioritizer = GreedyEssential(convertedExecutions, convertedExecutionToPriority) val list = mutableListOf() while (prioritizer.hasMore()) { list.add(prioritizer.getExecutionAndRemove()) diff --git a/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt b/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt index d3b2c19918..a34af60f79 100644 --- a/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt +++ b/utbot-framework/src/main/kotlin/org/utbot/framework/minimization/Minimization.kt @@ -18,6 +18,7 @@ import org.utbot.framework.plugin.api.UtModel import org.utbot.framework.plugin.api.UtNullModel import org.utbot.framework.plugin.api.UtPrimitiveModel import org.utbot.framework.plugin.api.UtStatementModel +import org.utbot.framework.plugin.api.UtSymbolicExecution import org.utbot.framework.plugin.api.UtVoidModel @@ -53,8 +54,11 @@ fun minimizeExecutions(executions: List): List { executions.indices.filter { executions[it].coverage?.coveredInstructions?.isEmpty() ?: true }.toSet() // ^^^ here we add executions with empty or null coverage, because it happens only if a concrete execution failed, // so we don't know the actual coverage for such executions - val mapping = buildMapping(executions.filterIndexed { idx, _ -> idx !in unknownCoverageExecutions }) - val usedExecutionIndexes = (GreedyEssential.minimize(mapping) + unknownCoverageExecutions).toSet() + + val filteredExecutions = executions.filterIndexed { idx, _ -> idx !in unknownCoverageExecutions } + val (mapping, executionToPriorityMapping) = buildMapping(filteredExecutions) + + val usedExecutionIndexes = (GreedyEssential.minimize(mapping, executionToPriorityMapping) + unknownCoverageExecutions).toSet() val usedMinimizedExecutions = executions.filterIndexed { idx, _ -> idx in usedExecutionIndexes } @@ -143,13 +147,14 @@ private fun groupExecutionsByTestSuite( executions.groupBy { executionToTestSuite(it) }.values /** - * Builds a mapping from execution id to edges id. + * Builds a mapping from execution id to edges id and from execution id to its priority. */ -private fun buildMapping(executions: List): Map> { +private fun buildMapping(executions: List): Pair>, Map> { // (inst1, instr2) -> edge id --- edge represents as a pair of instructions, which are connected by this edge val allCoveredEdges = mutableMapOf, Int>() val thrownExceptions = mutableMapOf() val mapping = mutableMapOf>() + val executionToPriorityMapping = mutableMapOf() executions.forEachIndexed { idx, execution -> @@ -169,11 +174,12 @@ private fun buildMapping(executions: List): Map> { edges += allCoveredEdges[instructions[i] to instructions[i + 1]]!! } mapping[idx] = edges + executionToPriorityMapping[idx] = execution.getExecutionPriority() } } } - return mapping + return Pair(mapping, executionToPriorityMapping) } /** @@ -264,4 +270,15 @@ private fun addExtraIfLastInstructionIsException( * Takes an exception name, a class name, a method signature and a line number from exception. */ private fun Throwable.exceptionToInfo(): String = - this::class.java.name + (this.stackTrace.firstOrNull()?.run { className + methodName + lineNumber } ?: "null") \ No newline at end of file + this::class.java.name + (this.stackTrace.firstOrNull()?.run { className + methodName + lineNumber } ?: "null") + +/** + * Returns an execution priority. [UtSymbolicExecution] has the highest priority + * over other executions like [UtFuzzedExecution], [UtFailedExecution], etc. + * + * See [https://github.com/UnitTestBot/UTBotJava/issues/1504] for more details. + */ +private fun UtExecution.getExecutionPriority(): Int = when (this) { + is UtSymbolicExecution -> 0 + else -> 1 +}