From 87898021a1f85dcddc96f1f4d735ae97c2151a81 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Mon, 31 Aug 2020 18:02:27 +0300 Subject: [PATCH] FIR IDE: introduce multi module lazy resolve tests --- .../kotlin/generators/tests/GenerateTests.kt | 7 ++ .../whenWithHeiarchy/dependency/dependency.kt | 8 ++ .../whenWithHeiarchy/expected.txt | 26 ++++++ .../whenWithHeiarchy/main/main.kt | 8 ++ .../whenWithHeiarchy/structure.json | 8 ++ .../AbstractFirMultiModuleLazyResolveTest.kt | 57 +++++++++++++ ...irMultiModuleLazyResolveTestGenerated.java | 35 ++++++++ .../fir/low/level/api/TestProjectStructure.kt | 82 +++++++++++++++++++ 8 files changed, 231 insertions(+) create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/dependency/dependency.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/expected.txt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/main/main.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/structure.json create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/AbstractFirMultiModuleLazyResolveTest.kt create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirMultiModuleLazyResolveTestGenerated.java create mode 100644 idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/TestProjectStructure.kt diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index b9e36d6efa8..97ba4a898ad 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -88,6 +88,7 @@ import org.jetbrains.kotlin.idea.filters.AbstractKotlinExceptionFilterTest import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirLazyResolveTest import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleResolveTest import org.jetbrains.kotlin.idea.fir.AbstractKtDeclarationAndFirDeclarationEqualityChecker +import org.jetbrains.kotlin.idea.fir.low.level.api.AbstractFirMultiModuleLazyResolveTest import org.jetbrains.kotlin.idea.folding.AbstractKotlinFoldingTest import org.jetbrains.kotlin.idea.frontend.api.fir.AbstractResolveCallTest import org.jetbrains.kotlin.idea.frontend.api.scopes.AbstractMemberScopeByFqNameTest @@ -1015,6 +1016,12 @@ fun main(args: Array) { } } + testGroup("idea/idea-frontend-fir/idea-fir-low-level-api/tests", "idea/idea-frontend-fir/idea-fir-low-level-api/testdata") { + testClass { + model("multiModuleLazyResolve", recursive = false, extension = null) + } + } + testGroup("idea/idea-fir/tests", "idea") { testClass { model("testData/highlighter") diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/dependency/dependency.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/dependency/dependency.kt new file mode 100644 index 00000000000..57d319fb23d --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/dependency/dependency.kt @@ -0,0 +1,8 @@ +interface A + +interface X : A +interface Y : A + +interface F : Y, X +interface G : Y, X + diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/expected.txt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/expected.txt new file mode 100644 index 00000000000..6326571ce7a --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/expected.txt @@ -0,0 +1,26 @@ +FILE: main.kt + public abstract interface A : R|kotlin/Any| { + } + public abstract interface X : R|A| { + } + public abstract interface Y : R|A| { + } + public abstract interface F : R|Y|, R|X| { + } + public abstract interface G : R|Y|, R|X| { + } + public final fun R|Y|.test(): R|kotlin/String?| { + lval a: R|kotlin/Int| = when (this@R|/test|) { + ($subj$ is R|F|) -> { + Int(1) + } + ($subj$ is R|G|) -> { + Int(2) + } + else -> { + ^test Null(null) + } + } + + ^test Null(null) + } diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/main/main.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/main/main.kt new file mode 100644 index 00000000000..fd4a0ed6a53 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/main/main.kt @@ -0,0 +1,8 @@ +fun Y.test(): String? { + val a = when (this) { + is F -> 1 + is G -> 2 + else -> return null + } + return null +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/structure.json b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/structure.json new file mode 100644 index 00000000000..3eb62dde3ec --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/structure.json @@ -0,0 +1,8 @@ +{ + "modules" : [ + { "name": "main", "dependsOn": ["dependency"] }, + { "name": "dependency" } + ], + "fileToResolve": { "module": "main", "file": "main.kt" }, + "fails": true +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/AbstractFirMultiModuleLazyResolveTest.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/AbstractFirMultiModuleLazyResolveTest.kt new file mode 100644 index 00000000000..89b19e63a88 --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/AbstractFirMultiModuleLazyResolveTest.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2010-2020 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.idea.fir.low.level.api + +import com.intellij.openapi.util.io.FileUtil +import com.intellij.openapi.vfs.VirtualFileManager +import com.intellij.psi.PsiManager +import org.jetbrains.kotlin.fir.declarations.FirResolvePhase +import org.jetbrains.kotlin.fir.render +import org.jetbrains.kotlin.idea.stubs.AbstractMultiModuleTest +import org.jetbrains.kotlin.idea.util.sourceRoots +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import org.jetbrains.kotlin.test.KotlinTestUtils +import java.io.File +import java.nio.file.Paths + +abstract class AbstractFirMultiModuleLazyResolveTest : AbstractMultiModuleTest() { + override fun getTestDataPath(): String = + "${KotlinTestUtils.getHomeDirectory()}/idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/" + + fun doTest(path: String) { + val testStructure = TestProjectStructureReader.read(Paths.get(path)) + val modulesByNames = testStructure.modules.associate { moduleData -> + moduleData.name to module(moduleData.name) + } + testStructure.modules.forEach { moduleData -> + val module = modulesByNames.getValue(moduleData.name) + moduleData.dependsOnModules.forEach { dependencyName -> + module.addDependency(modulesByNames.getValue(dependencyName)) + } + } + val moduleToResolve = modulesByNames.getValue(testStructure.fileToResolve.moduleName) + val fileToAnalysePath = moduleToResolve.sourceRoots.first().url + "/" + testStructure.fileToResolve.relativeFilePath + + val virtualFileToAnalyse = VirtualFileManager.getInstance().findFileByUrl(fileToAnalysePath) + ?: error("File ${testStructure.fileToResolve.filePath} not found") + val ktFileToAnalyse = PsiManager.getInstance(project).findFile(virtualFileToAnalyse) as KtFile + val resolveState = LowLevelFirApiFacade.getResolveStateFor(ktFileToAnalyse) + + val fails = testStructure.fails + + try { + val fir = LowLevelFirApiFacade.getOrBuildFirFor(ktFileToAnalyse, resolveState, FirResolvePhase.BODY_RESOLVE) + KotlinTestUtils.assertEqualsToFile(File("$path/expected.txt"), fir.render()) + } catch (e: Exception) { + if (!fails) throw e + return + } + if (fails) { + throw AssertionError("Looks like test is passing, please remove // FAILS form the file") + } + } +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirMultiModuleLazyResolveTestGenerated.java b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirMultiModuleLazyResolveTestGenerated.java new file mode 100644 index 00000000000..b523f4fd71b --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/FirMultiModuleLazyResolveTestGenerated.java @@ -0,0 +1,35 @@ +/* + * Copyright 2010-2020 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.idea.fir.low.level.api; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class FirMultiModuleLazyResolveTestGenerated extends AbstractFirMultiModuleLazyResolveTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInMultiModuleLazyResolve() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve"), Pattern.compile("^([^\\.]+)$"), null, false); + } + + @TestMetadata("whenWithHeiarchy") + public void testWhenWithHeiarchy() throws Exception { + runTest("idea/idea-frontend-fir/idea-fir-low-level-api/testdata/multiModuleLazyResolve/whenWithHeiarchy/"); + } +} diff --git a/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/TestProjectStructure.kt b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/TestProjectStructure.kt new file mode 100644 index 00000000000..a40c1bb153b --- /dev/null +++ b/idea/idea-frontend-fir/idea-fir-low-level-api/tests/org/jetbrains/kotlin/idea/fir/low/level/api/TestProjectStructure.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2010-2020 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.idea.fir.low.level.api + +import com.google.gson.JsonElement +import com.google.gson.JsonObject +import com.google.gson.JsonParser +import com.google.gson.JsonPrimitive +import com.intellij.openapi.util.io.FileUtil +import org.jetbrains.kotlin.idea.jsonUtils.getString +import java.nio.file.Path + +sealed class TestProjectStructure { + abstract val modules: List + abstract val fileToResolve: FileToResolve + abstract val fails: Boolean + + companion object { + fun parse(json: JsonElement): TestProjectStructure { + require(json is JsonObject) + val fails = if (json.has(FAILS_FIELD)) json.get(FAILS_FIELD).asBoolean else false + + return MultiModuleTestProjectStructure( + json.getAsJsonArray("modules").map { TestProjectModule.parse(it) }, + FileToResolve.parse(json.getAsJsonObject("fileToResolve")), + fails + ) + } + + private const val FAILS_FIELD = "fails" + } +} + +data class FileToResolve(val moduleName: String, val relativeFilePath: String) { + val filePath get() = "$moduleName/$relativeFilePath" + + companion object { + fun parse(json: JsonElement): FileToResolve { + require(json is JsonObject) + return FileToResolve( + moduleName = json.getString("module"), + relativeFilePath = json.getString("file") + ) + } + } +} + +data class MultiModuleTestProjectStructure( + override val modules: List, + override val fileToResolve: FileToResolve, + override val fails: Boolean +) : TestProjectStructure() + +data class TestProjectModule(val name: String, val dependsOnModules: List) { + companion object { + fun parse(json: JsonElement): TestProjectModule { + require(json is JsonObject) + val dependencies = if (json.has(DEPENDS_ON_FIELD)) { + json.getAsJsonArray(DEPENDS_ON_FIELD).map { (it as JsonPrimitive).asString } + } else emptyList() + return TestProjectModule( + json.getString("name"), + dependencies + ) + } + + private const val DEPENDS_ON_FIELD = "dependsOn" + } +} + +object TestProjectStructureReader { + fun read(testDirectory: Path, jsonFileName: String = "structure.json"): TestProjectStructure { + val jsonFile = testDirectory.resolve(jsonFileName) + + @Suppress("DEPRECATION") // AS 4.0 + val json = JsonParser().parse(FileUtil.loadFile(jsonFile.toFile(), /*convertLineSeparators=*/true)) + return TestProjectStructure.parse(json) + } +} \ No newline at end of file