Skip to content

Make symbolic execs preferable in minimization process #1514

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ private inline class LineNumber(val number: Int)
* [Greedy essential algorithm](CONFLUENCE:Test+Minimization)
*/
class GreedyEssential private constructor(
executionToCoveredLines: Map<ExecutionNumber, List<LineNumber>>
executionToCoveredLines: Map<ExecutionNumber, List<LineNumber>>,
executionToPriority: Map<ExecutionNumber, Int>
) {
private val executionToUsefulLines: Map<ExecutionNumber, MutableSet<LineNumber>> =
executionToCoveredLines
Expand All @@ -23,8 +24,11 @@ class GreedyEssential private constructor(
.mapValues { it.value.toMutableSet() }

private val executionByPriority =
PriorityQueue(compareByDescending<Pair<ExecutionNumber, Int>> { it.second }.thenBy { it.first.number })
.apply {
PriorityQueue(
compareBy<Pair<ExecutionNumber, Int>> { executionToPriority[it.first] }
.thenByDescending { it.second }
.thenBy { it.first.number }
).apply {
addAll(
executionToCoveredLines
.keys
Expand Down Expand Up @@ -89,12 +93,16 @@ class GreedyEssential private constructor(
*
* @return retained execution ids.
*/
fun minimize(executions: Map<Int, List<Int>>): List<Int> {
fun minimize(executions: Map<Int, List<Int>>, executionToPriority: Map<Int, Int> = mapOf()): List<Int> {
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<ExecutionNumber>()
while (prioritizer.hasMore()) {
list.add(prioritizer.getExecutionAndRemove())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand Down Expand Up @@ -53,8 +54,11 @@ fun minimizeExecutions(executions: List<UtExecution>): List<UtExecution> {
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 }

Expand Down Expand Up @@ -143,13 +147,14 @@ private fun <T : Any> 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<UtExecution>): Map<Int, List<Int>> {
private fun buildMapping(executions: List<UtExecution>): Pair<Map<Int, List<Int>>, Map<Int, Int>> {
// (inst1, instr2) -> edge id --- edge represents as a pair of instructions, which are connected by this edge
val allCoveredEdges = mutableMapOf<Pair<Long, Long>, Int>()
val thrownExceptions = mutableMapOf<String, Long>()
val mapping = mutableMapOf<Int, List<Int>>()
val executionToPriorityMapping = mutableMapOf<Int, Int>()


executions.forEachIndexed { idx, execution ->
Expand All @@ -169,11 +174,12 @@ private fun buildMapping(executions: List<UtExecution>): Map<Int, List<Int>> {
edges += allCoveredEdges[instructions[i] to instructions[i + 1]]!!
}
mapping[idx] = edges
executionToPriorityMapping[idx] = execution.getExecutionPriority()
}
}
}

return mapping
return Pair(mapping, executionToPriorityMapping)
}

/**
Expand Down Expand Up @@ -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")
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
}