[AA] Tests: Remove topological ordering from KtTestModule creation

- The test infrastructure already requires test modules to be properly
  ordered regarding dependencies, so a module A which is a dependency
  for a module B must always be ordered before B. Hence, it makes no
  sense to also order modules topologically.
- Reordering modules may cause the resulting `KtTestModule`s to be
  out-of-order with the test modules, because the topological order
  might be different from the test module order, even if the dependency
  relationship already holds in the original order.
  `KtTestModuleProjectStructure` requires the same ordering as test
  modules to ensure the same order during iteration of `KtTestModule`s.

^KT-65960
^KT-64994
This commit is contained in:
Marco Pennekamp
2024-02-26 15:56:46 +01:00
committed by Space Team
parent c7ccfcc785
commit 5663521a36
2 changed files with 19 additions and 53 deletions
@@ -20,8 +20,8 @@ fun interface KtModuleFactory : TestService {
* @param contextModule a module to use as a context module. Some kinds of modules (such as dangling file modules) require a
* context module. Modules representing code fragments also require a context element. That is why the [KtTestModule] is passed
* instead of a plain [KtModule][org.jetbrains.kotlin.analysis.project.structure.KtModule].
*
* @param dependencyPaths Paths of directories containing dependency libraries. The paths will be set as class paths for compilation.
* @param dependencyPaths The binary roots of [testModule]'s binary library dependencies. This allows avoiding unresolved symbol issues
* when compiling test binary libraries that depend on other test binary libraries.
*/
fun createModule(
testModule: TestModule,
@@ -12,6 +12,7 @@ import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analysis.api.standalone.base.project.structure.StandaloneProjectFactory
import org.jetbrains.kotlin.analysis.project.structure.KtBinaryModule
import org.jetbrains.kotlin.analysis.project.structure.KtDanglingFileModule
import org.jetbrains.kotlin.analysis.project.structure.KtLibraryModule
import org.jetbrains.kotlin.analysis.project.structure.KtModule
import org.jetbrains.kotlin.analysis.project.structure.KtNotUnderContentRootModule
import org.jetbrains.kotlin.analysis.test.framework.AnalysisApiTestDirectives
@@ -40,31 +41,6 @@ private typealias LibraryCache = MutableMap<Set<Path>, KtBinaryModule>
private typealias ModulesByName = Map<String, KtTestModule>
/**
* A function to run the topological sort (or post-order sort) for [TestModule]s based on the dependency graph.
* This function guarantees:
* - For [TestModule] A and B, where A has dependency on B, A will never appears earlier than B in the result list.
*/
private fun sortInDependencyPostOrder(testModules: List<TestModule>): List<TestModule> {
val namesToModules = testModules.associateBy { it.name }
val notVisited = testModules.toMutableSet()
val sortedModules = mutableListOf<TestModule>()
fun dfsWalk(module: TestModule) {
notVisited.remove(module)
for (dependency in module.regularDependencies) {
val dependencyAsModule = namesToModules[dependency.moduleName] ?: error("Module ${dependency.moduleName} is missing")
if (dependencyAsModule in notVisited) dfsWalk(dependencyAsModule)
}
sortedModules.add(module)
}
while (notVisited.isNotEmpty()) {
dfsWalk(notVisited.first())
}
return sortedModules
}
object TestModuleStructureFactory {
fun createProjectStructureByTestStructure(
moduleStructure: TestModuleStructure,
@@ -86,14 +62,10 @@ object TestModuleStructureFactory {
}
/**
* A function to create [KtModuleWithFiles] for the given [moduleStructure].
* The test infrastructure ensures that the given [moduleStructure] contains properly ordered dependencies: a [TestModule] can only
* depend on test modules which precede it. Hence, this function does not need to order dependencies itself.
*
* This function guarantees:
* - For [TestModule] A and B, where A has dependency on B,
* - B will always be created earlier than A.
* - The class path of B will be given to the creation of A's module.
*
* In particular, it handles unresolved symbol issues caused by building binary libraries.
* @return A list of [KtTestModule]s in the same order as [TestModuleStructure.modules].
*/
private fun createModules(
moduleStructure: TestModuleStructure,
@@ -101,30 +73,24 @@ object TestModuleStructureFactory {
project: Project,
): List<KtTestModule> {
val moduleCount = moduleStructure.modules.size
val existingModules = HashMap<String, KtTestModule>(moduleCount)
val result = ArrayList<KtTestModule>(moduleCount)
val processedModules = HashMap<String, KtTestModule>(moduleCount)
val processedDependencies = mutableMapOf<String, Collection<Path>>()
for (testModule in sortInDependencyPostOrder(moduleStructure.modules)) {
for (testModule in moduleStructure.modules) {
val contextModuleName = testModule.directives.singleOrZeroValue(AnalysisApiTestDirectives.CONTEXT_MODULE)
val contextModule = contextModuleName?.let(processedModules::getValue)
val contextModule = contextModuleName?.let(existingModules::getValue)
val dependencyPaths = testModule.regularDependencies
.mapNotNull { processedDependencies[it.moduleName] }
.flatten()
val moduleWithFiles = testServices
.getKtModuleFactoryForTestModule(testModule)
.createModule(testModule, contextModule, dependencyPaths, testServices, project)
processedModules[testModule.name] = moduleWithFiles
result.add(moduleWithFiles)
val libraryModule = moduleWithFiles.ktModule
if (libraryModule is KtLibraryModuleImpl) {
processedDependencies[testModule.name] = libraryModule.getBinaryRoots()
val dependencyBinaryRoots = testModule.regularDependencies.flatMap { dependency ->
val libraryModule = existingModules.getValue(dependency.moduleName).ktModule as? KtLibraryModule
libraryModule?.getBinaryRoots().orEmpty()
}
val ktTestModule = testServices
.getKtModuleFactoryForTestModule(testModule)
.createModule(testModule, contextModule, dependencyBinaryRoots, testServices, project)
existingModules[testModule.name] = ktTestModule
result.add(ktTestModule)
}
return result