[LL] Sort modules from the same KMP project in symbol providers

Sort dependency modules topologically if they belong to the same KMP
project, preserving their relative positions. Sorting all modules and
changing positions of unrelated modules can be harmful: in the case of
a classpath hell, IDE results can become different from runtime
behavior. Sorting in place can help to avoid this problem, because
conflicting declarations shouldn't be allowed in different source sets
of the same multiplatform project.

KTIJ-27569
This commit is contained in:
Pavel Kirpichenkov
2023-12-05 11:40:13 +02:00
committed by Space Team
parent 16b1caff7e
commit ce3c05500e
4 changed files with 255 additions and 34 deletions
@@ -0,0 +1,137 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.utils
import junit.framework.TestCase
import kotlin.test.assertFails
class TopologicalSortTest : TestCase() {
fun testEmptyGraph() {
assertEquals("", emptyList<Any>(), topologicalSort(emptyList<Any>()) { emptyList<Any>() })
}
fun testSingleNode() {
checkGraph(
"""
A
""".trimIndent(),
listOf("A")
)
}
fun testSimpleGraph() {
checkGraph(
"""
A; B; C
A > B; B > C; A > C
""".trimIndent(),
listOf("A", "B", "C")
)
}
fun testSimpleGraphShuffledNodes() {
checkGraph(
"""
C; A; B
A > B; B > C; A > C
""".trimIndent(),
listOf("A", "B", "C")
)
}
fun testBamboo() {
checkGraph(
"""
A; B; C
C > B; B > A
""".trimIndent(),
listOf("C", "B", "A")
)
}
fun testLongerPath() {
checkGraph(
"""
A; B; C; D; E
E > D; E > B; D > C; C > B; C > A; B > A
""".trimIndent(),
listOf("E", "D", "C", "B", "A")
)
}
fun testRepeatedEdges() {
checkGraph(
"""
A; B; C
C > B; C > B; B > A; C > A
""".trimIndent(),
listOf("C", "B", "A")
)
}
fun testSelfLoopReport() {
val graph = parseFromString(
"""
A
A > A
""".trimIndent()
)
assertFails { topologicalSort(graph.nodes, dependencies = { graph.edges[this].orEmpty() }) }
}
fun testDirectLoopReport() {
val graph = parseFromString(
"""
A; B
A > B; B > A
""".trimIndent()
)
assertFails { topologicalSort(graph.nodes, dependencies = { graph.edges[this].orEmpty() }) }
}
fun testLongLoopReport() {
val graph = parseFromString(
"""
A; B; C; D
D > C; C > B; C > A; A > D
""".trimIndent()
)
assertFails { topologicalSort(graph.nodes, dependencies = { graph.edges[this].orEmpty() }) }
}
private fun checkGraph(description: String, expected: List<String>) {
val graph = parseFromString(description)
assertEquals("Incorrect order of sorted nodes", expected, sortedNodes(graph))
}
private fun <T> sortedNodes(graph: Graph<T>): List<T> {
return topologicalSort(graph.nodes) { graph.edges[this].orEmpty() }
}
}
private class Graph<T>(
val nodes: Set<T>,
val edges: Map<T, List<T>>
)
/**
* Expected format: line with the list of nodes, line with the list of edges.
* [node[;...]]
* [node > node[;...]]
*/
private fun parseFromString(description: String): Graph<String> {
val (nodeStrings, edgeStrings) = description.lines()
val nodes = nodeStrings.split(";").map(String::trim).toSet()
val edges = buildMap<String, MutableList<String>> {
edgeStrings.takeIf { it.isNotBlank() }?.split(";")?.forEach { edgeDescription ->
val (from, to) = edgeDescription.split(">").map(String::trim)
getOrPut(from) { mutableListOf() }.add(to)
}
}
return Graph(nodes, edges)
}