diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt index b898e84423d..47c6ee66a30 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt @@ -458,6 +458,20 @@ class KotlinCoreEnvironment private constructor( return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs) } + @TestOnly + @JvmStatic + fun createForTests( + projectEnvironment: ProjectEnvironment, initialConfiguration: CompilerConfiguration, extensionConfigs: EnvironmentConfigFiles + ): KotlinCoreEnvironment { + return KotlinCoreEnvironment(projectEnvironment, initialConfiguration, extensionConfigs) + } + + @TestOnly + fun createProjectEnvironmentForTests(parentDisposable: Disposable, configuration: CompilerConfiguration): ProjectEnvironment { + val appEnv = createApplicationEnvironment(parentDisposable, configuration, unitTestMode = true) + return ProjectEnvironment(parentDisposable, appEnv) + } + // used in the daemon for jar cache cleanup val applicationEnvironment: KotlinCoreApplicationEnvironment? get() = ourApplicationEnvironment diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt.as42 b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt.as42 index b41f8d46e03..dc420f7f739 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt.as42 +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/compiler/KotlinCoreEnvironment.kt.as42 @@ -456,6 +456,20 @@ class KotlinCoreEnvironment private constructor( return KotlinCoreEnvironment(projectEnv, configuration, extensionConfigs) } + @TestOnly + @JvmStatic + fun createForTests( + projectEnvironment: ProjectEnvironment, initialConfiguration: CompilerConfiguration, extensionConfigs: EnvironmentConfigFiles + ): KotlinCoreEnvironment { + return KotlinCoreEnvironment(projectEnvironment, initialConfiguration, extensionConfigs) + } + + @TestOnly + fun createProjectEnvironmentForTests(parentDisposable: Disposable, configuration: CompilerConfiguration): ProjectEnvironment { + val appEnv = createApplicationEnvironment(parentDisposable, configuration, unitTestMode = true) + return ProjectEnvironment(parentDisposable, appEnv) + } + // used in the daemon for jar cache cleanup val applicationEnvironment: KotlinCoreApplicationEnvironment? get() = ourApplicationEnvironment diff --git a/compiler/config.jvm/src/org/jetbrains/kotlin/platform/jvm/JvmPlatform.kt b/compiler/config.jvm/src/org/jetbrains/kotlin/platform/jvm/JvmPlatform.kt index 68ff43b357b..09c029a3faa 100644 --- a/compiler/config.jvm/src/org/jetbrains/kotlin/platform/jvm/JvmPlatform.kt +++ b/compiler/config.jvm/src/org/jetbrains/kotlin/platform/jvm/JvmPlatform.kt @@ -18,7 +18,7 @@ abstract class JvmPlatform : SimplePlatform("JVM") { @Suppress("DEPRECATION_ERROR") object JvmPlatforms { - private val UNSPECIFIED_SIMPLE_JVM_PLATFORM = JdkPlatform(JvmTarget.JVM_1_6) + private val UNSPECIFIED_SIMPLE_JVM_PLATFORM = JdkPlatform(JvmTarget.DEFAULT) private val jvmTargetToJdkPlatform: Map = JvmTarget.values().map { it to JdkPlatform(it).toTargetPlatform() }.toMap() diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/analysis/FirAnalyzerFacade.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/analysis/FirAnalyzerFacade.kt index 0eb4608bb47..b6146c99b48 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/analysis/FirAnalyzerFacade.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/analysis/FirAnalyzerFacade.kt @@ -26,7 +26,7 @@ import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl import org.jetbrains.kotlin.psi.KtFile import org.jetbrains.kotlin.psi2ir.generators.GeneratorExtensions -class FirAnalyzerFacade(val session: FirSession, val languageVersionSettings: LanguageVersionSettings, val ktFiles: List) { +class FirAnalyzerFacade(val session: FirSession, val languageVersionSettings: LanguageVersionSettings, val ktFiles: Collection) { private var firFiles: List? = null private var scopeSession: ScopeSession? = null private var collectedDiagnostics: Map>>? = null diff --git a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirJvmModuleInfo.kt b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirJvmModuleInfo.kt index 82a56fde399..e342b7f50e3 100644 --- a/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirJvmModuleInfo.kt +++ b/compiler/fir/entrypoint/src/org/jetbrains/kotlin/fir/session/FirJvmModuleInfo.kt @@ -16,7 +16,10 @@ class FirJvmModuleInfo(override val name: Name, val dependencies: List") - fun createForLibraries(): FirJvmModuleInfo = FirJvmModuleInfo(LIBRARIES_MODULE_NAME, emptyList()) + fun createForLibraries(mainModuleName: String? = null): FirJvmModuleInfo { + val name = mainModuleName?.let { Name.special("") } ?: LIBRARIES_MODULE_NAME + return FirJvmModuleInfo(name, emptyList()) + } } constructor(moduleName: String, dependencies: List) : this(Name.identifier(moduleName), dependencies) diff --git a/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt b/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt index 8c50415868d..00f3ea98021 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/checkers/utils/CheckerTestUtil.kt @@ -149,7 +149,7 @@ object CheckerTestUtil { return diagnostics } - private fun getDebugInfoDiagnostics( + fun getDebugInfoDiagnostics( root: PsiElement, bindingContext: BindingContext, markDynamicCalls: Boolean, diff --git a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticFactoryWithPsiElement.java b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticFactoryWithPsiElement.java index 16a8223e4e5..62f2a6ba77d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticFactoryWithPsiElement.java +++ b/compiler/frontend/src/org/jetbrains/kotlin/diagnostics/DiagnosticFactoryWithPsiElement.java @@ -36,4 +36,8 @@ public abstract class DiagnosticFactoryWithPsiElement diagnostic) { return positioningStrategy.isValid(diagnostic.getPsiElement()); } + + public PositioningStrategy getPositioningStrategy() { + return positioningStrategy; + } } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt index 096f6a87a7d..28fa2b6499d 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmIrCodegenFactory.kt @@ -124,7 +124,7 @@ class JvmIrCodegenFactory(private val phaseConfig: PhaseConfig) : CodegenFactory ) } - internal fun doGenerateFilesInternal( + fun doGenerateFilesInternal( state: GenerationState, irModuleFragment: IrModuleFragment, symbolTable: SymbolTable, diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/script/scriptTestUtil.kt b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/script/scriptTestUtil.kt similarity index 100% rename from compiler/tests-common/tests/org/jetbrains/kotlin/script/scriptTestUtil.kt rename to compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/script/scriptTestUtil.kt diff --git a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/Condition.kt b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/Condition.kt new file mode 100644 index 00000000000..1e65a5b8b6f --- /dev/null +++ b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/Condition.kt @@ -0,0 +1,142 @@ +/* + * 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.test.util + +import java.util.* + +fun interface Condition { + operator fun invoke(value: T): Boolean +} + +object Conditions { + private val TRUE = Condition { true } + private val FALSE = Condition { false } + + fun alwaysTrue(): Condition { + return TRUE + } + + fun alwaysFalse(): Condition { + return FALSE + } + + fun notNull(): Condition { + return Condition { it != null } + } + + fun constant(value: Boolean): Condition { + return if (value) alwaysTrue() else alwaysFalse() + } + + fun instanceOf(clazz: Class<*>): Condition { + return Condition { t -> clazz.isInstance(t) } + } + + fun notInstanceOf(clazz: Class<*>): Condition { + return Condition { t -> !clazz.isInstance(t) } + } + + fun assignableTo(clazz: Class<*>): Condition> { + return Condition { t -> clazz.isAssignableFrom(t) } + } + + fun instanceOf(vararg clazz: Class<*>): Condition { + return Condition { t -> + for (aClass in clazz) { + if (aClass.isInstance(t)) return@Condition true + } + false + } + } + + fun `is`(option: T): Condition { + return equalTo(option) + } + + fun equalTo(option: Any?): Condition { + return Condition { t -> t == option } + } + + fun notEqualTo(option: Any?): Condition { + return Condition { t -> t != option } + } + + fun oneOf(vararg options: T): Condition { + return oneOf(options.toList()) + } + + fun oneOf(options: Collection): Condition { + return Condition { t -> options.contains(t) } + } + + fun not(c: Condition): Condition { + if (c === alwaysTrue()) return alwaysFalse() + if (c === alwaysFalse()) return alwaysTrue() + return if (c is Not<*>) { + (c as Not).c as Condition + } else Not(c) + } + + fun and(c1: Condition, c2: Condition): Condition { + if (c1 === alwaysTrue() || c2 === alwaysFalse()) return c2 + return if (c2 === alwaysTrue() || c1 === alwaysFalse()) c1 else And(c1, c2) + } + + fun or(c1: Condition, c2: Condition): Condition { + if (c1 === alwaysFalse() || c2 === alwaysTrue()) return c2 + return if (c2 === alwaysFalse() || c1 === alwaysTrue()) c1 else Or(c1, c2) + } + + fun compose(func: (A) -> B, condition: Condition): Condition { + return Condition { condition.invoke(func(it)) } + } + + fun cached(c: Condition): Condition { + return SoftRefCache(c) + } + + private class Not(val c: Condition) : Condition { + override fun invoke(value: T): Boolean { + return !c.invoke(value) + } + } + + private class And(val c1: Condition, val c2: Condition) : Condition { + override fun invoke(value: T): Boolean { + return c1.invoke(value) && c2.invoke(value) + } + } + + private class Or(val c1: Condition, val c2: Condition) : Condition { + override fun invoke(value: T): Boolean { + return c1.invoke(value) || c2.invoke(value) + } + } + + private class SoftRefCache(private val myCondition: Condition) : Condition { + private val myCache: WeakHashMap = WeakHashMap(); + + override fun invoke(value: T): Boolean { + return myCache.computeIfAbsent(value) { myCondition(value) } + } + } +} + +infix fun Condition.and(other: Condition): Condition { + return Conditions.and(this, other) +} + +infix fun Condition.or(other: Condition): Condition { + return Conditions.or(this, other) +} + +operator fun Condition.not(): Condition { + return Conditions.not(this) +} + +fun Condition.cached(): Condition { + return Conditions.cached(this) +} diff --git a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/StringUtils.kt b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/StringUtils.kt index db23ad397ab..9c56bab4713 100644 --- a/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/StringUtils.kt +++ b/compiler/test-infrastructure-utils/tests/org/jetbrains/kotlin/test/util/StringUtils.kt @@ -7,3 +7,15 @@ package org.jetbrains.kotlin.test.util fun Iterable<*>.joinToArrayString(): String = joinToString(separator = ", ", prefix = "[", postfix = "]") fun Array<*>.joinToArrayString(): String = joinToString(separator = ", ", prefix = "[", postfix = "]") + +private const val DEFAULT_LINE_SEPARATOR = "\n" + +fun String.trimTrailingWhitespacesAndAddNewlineAtEOF(): String = + this.trimTrailingWhitespaces().let { result -> if (result.endsWith("\n")) result else result + "\n" } + +fun String.trimTrailingWhitespaces(): String = + this.split('\n').joinToString(separator = "\n") { it.trimEnd() } + +fun String.convertLineSeparators(separator: String = DEFAULT_LINE_SEPARATOR): String { + return replace(Regex.fromLiteral("\r\n|\r|\n"), separator) +} diff --git a/compiler/tests-common-new/build.gradle.kts b/compiler/tests-common-new/build.gradle.kts new file mode 100644 index 00000000000..b037e357a9c --- /dev/null +++ b/compiler/tests-common-new/build.gradle.kts @@ -0,0 +1,66 @@ +import org.jetbrains.kotlin.ideaExt.idea + +plugins { + kotlin("jvm") + id("jps-compatible") +} + +dependencies { + testApi(project(":compiler:fir:entrypoint")) + testApi(project(":compiler:cli")) + testImplementation(project(":compiler:ir.tree.impl")) + testImplementation(intellijCoreDep()) { includeJars("intellij-core") } + + testCompileOnly(project(":kotlin-reflect-api")) + testRuntimeOnly(project(":kotlin-reflect")) + testRuntimeOnly(project(":core:descriptors.runtime")) + + testImplementation(projectTests(":generators:test-generator")) + + testImplementation(platform("org.junit:junit-bom:5.7.0")) + testImplementation("org.junit.jupiter:junit-jupiter") + testImplementation("org.junit.platform:junit-platform-commons:1.7.0") + testApi(projectTests(":compiler:test-infrastructure")) + testImplementation(projectTests(":compiler:test-infrastructure-utils")) + + testImplementation(intellijDep()) { + // This dependency is needed only for FileComparisonFailure + includeJars("idea_rt", rootProject = rootProject) + isTransitive = false + } + + // This is needed only for using FileComparisonFailure, which relies on JUnit 3 classes + testRuntimeOnly(commonDep("junit:junit")) + testRuntimeOnly(intellijDep()) { + includeJars("jna", rootProject = rootProject) + } + testRuntimeOnly(toolsJar()) +} + +val generationRoot = projectDir.resolve("tests-gen") + +sourceSets { + "main" { none() } + "test" { + projectDefault() + this.java.srcDir(generationRoot.name) + } +} + +if (kotlinBuildProperties.isInJpsBuildIdeaSync) { + apply(plugin = "idea") + idea { + this.module.generatedSourceDirs.add(generationRoot) + } +} + +projectTest(parallel = true) { + dependsOn(":dist") + workingDir = rootDir + jvmArgs!!.removeIf { it.contains("-Xmx") } + maxHeapSize = "3g" + + useJUnitPlatform() +} + +testsJar() diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/BlackBoxCodegenSuppressor.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/BlackBoxCodegenSuppressor.kt new file mode 100644 index 00000000000..47f9befff9a --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/BlackBoxCodegenSuppressor.kt @@ -0,0 +1,68 @@ +/* + * 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.test.backend + +import org.jetbrains.kotlin.platform.js.isJs +import org.jetbrains.kotlin.platform.jvm.isJvm +import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.test.directives.CodegenTestDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.model.AfterAnalysisChecker +import org.jetbrains.kotlin.test.model.BackendKinds +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.moduleStructure +import org.jetbrains.kotlin.test.util.joinToArrayString + +class BlackBoxCodegenSuppressor(testServices: TestServices) : AfterAnalysisChecker(testServices) { + override val directives: List + get() = listOf(CodegenTestDirectives) + + override fun suppressIfNeeded(failedAssertions: List): List { + val moduleStructure = testServices.moduleStructure + val ignoredBackends = moduleStructure.modules.flatMap { it.directives[CodegenTestDirectives.IGNORE_BACKEND] } + if (ignoredBackends.isEmpty()) return failedAssertions + val targetBackends = moduleStructure.modules.flatMap { it.targetBackends } + val matchedBackend = ignoredBackends.intersect(targetBackends) + if (ignoredBackends.contains(TargetBackend.ANY)) { + return processAssertions(failedAssertions) + } + if (matchedBackend.isNotEmpty()) { + return processAssertions(failedAssertions, "for ${matchedBackend.joinToArrayString()}") + } + return failedAssertions + + } + + private fun processAssertions(failedAssertions: List, additionalMessage: String = ""): List { + return if (failedAssertions.isNotEmpty()) emptyList() + else { + val message = buildString { + append("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive") + if (additionalMessage.isNotEmpty()) { + append(" ") + append(additionalMessage) + } + } + listOf(AssertionError(message)) + } + } + + private val TestModule.targetBackends: List + get() = when (backendKind) { + BackendKinds.ClassicBackend -> when { + targetPlatform.isJvm() -> listOf(TargetBackend.JVM, TargetBackend.JVM_OLD) + targetPlatform.isJs() -> listOf(TargetBackend.JS) + else -> emptyList() + } + BackendKinds.IrBackend -> when { + targetPlatform.isJvm() -> listOf(TargetBackend.JVM_IR) + targetPlatform.isJs() -> listOf(TargetBackend.JS_IR) + else -> emptyList() + } + else -> emptyList() + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/classic/ClassicBackendFacade.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/classic/ClassicBackendFacade.kt new file mode 100644 index 00000000000..4358bb39649 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/classic/ClassicBackendFacade.kt @@ -0,0 +1,14 @@ +/* + * 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.test.backend.classic + +import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.services.TestServices + +abstract class ClassicBackendFacade>( + testServices: TestServices, + binaryKind: BinaryKind +) : BackendFacade(testServices, BackendKinds.ClassicBackend, binaryKind) diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/classic/ClassicBackendInput.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/classic/ClassicBackendInput.kt new file mode 100644 index 00000000000..29358945820 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/classic/ClassicBackendInput.kt @@ -0,0 +1,26 @@ +/* + * 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.test.backend.classic + +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.test.model.BackendKinds +import org.jetbrains.kotlin.test.model.ResultingArtifact + +// Old backend (JVM and JS) +data class ClassicBackendInput( + val psiFiles: Collection, + val bindingContext: BindingContext, + val moduleDescriptor: ModuleDescriptor, + val project: Project, + val languageVersionSettings: LanguageVersionSettings +) : ResultingArtifact.BackendInput() { + override val kind: BackendKinds.ClassicBackend + get() = BackendKinds.ClassicBackend +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/classic/ClassicJvmBackendFacade.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/classic/ClassicJvmBackendFacade.kt new file mode 100644 index 00000000000..c368339c1e8 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/classic/ClassicJvmBackendFacade.kt @@ -0,0 +1,41 @@ +/* + * 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.test.backend.classic + +import org.jetbrains.kotlin.codegen.ClassBuilderFactories +import org.jetbrains.kotlin.codegen.DefaultCodegenFactory +import org.jetbrains.kotlin.codegen.KotlinCodegenFacade +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.test.model.ArtifactKinds +import org.jetbrains.kotlin.test.model.BinaryArtifacts +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.compilerConfigurationProvider + +class ClassicJvmBackendFacade( + testServices: TestServices +) : ClassicBackendFacade(testServices, ArtifactKinds.Jvm) { + override fun transform( + module: TestModule, + inputArtifact: ClassicBackendInput + ): BinaryArtifacts.Jvm { + val compilerConfiguration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module) + val (psiFiles, bindingContext, moduleDescriptor, project, languageVersionSettings) = inputArtifact + // TODO: add configuring classBuilderFactory + val generationState = GenerationState.Builder( + project, + ClassBuilderFactories.TEST, + moduleDescriptor, + bindingContext, + psiFiles.toList(), + compilerConfiguration + ).codegenFactory(DefaultCodegenFactory).build() + + KotlinCodegenFacade.compileCorrectFiles(generationState) + + return BinaryArtifacts.Jvm(generationState.factory) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/handlers/BinaryArtifactHandlers.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/handlers/BinaryArtifactHandlers.kt new file mode 100644 index 00000000000..930fd0967c7 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/handlers/BinaryArtifactHandlers.kt @@ -0,0 +1,23 @@ +/* + * 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.test.backend.handlers + +import org.jetbrains.kotlin.test.model.ArtifactKinds +import org.jetbrains.kotlin.test.model.BinaryArtifactHandler +import org.jetbrains.kotlin.test.model.BinaryArtifacts +import org.jetbrains.kotlin.test.services.TestServices + +abstract class JvmBinaryArtifactHandler( + testServices: TestServices +) : BinaryArtifactHandler(testServices, ArtifactKinds.Jvm) + +abstract class JsBinaryArtifactHandler( + testServices: TestServices +) : BinaryArtifactHandler(testServices, ArtifactKinds.Js) + +abstract class NativeBinaryArtifactHandler( + testServices: TestServices +) : BinaryArtifactHandler(testServices, ArtifactKinds.Native) diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/handlers/JvmBoxRunner.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/handlers/JvmBoxRunner.kt new file mode 100644 index 00000000000..ababfab1b2a --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/handlers/JvmBoxRunner.kt @@ -0,0 +1,109 @@ +/* + * 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.test.backend.handlers + +import junit.framework.TestCase +import org.jetbrains.kotlin.backend.common.CodegenUtil.getMemberDeclarationsToGenerate +import org.jetbrains.kotlin.cli.jvm.config.jvmClasspathRoots +import org.jetbrains.kotlin.codegen.ClassFileFactory +import org.jetbrains.kotlin.codegen.GeneratedClassLoader +import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil.getFileClassInfoNoResolve +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.model.BinaryArtifacts +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.compilerConfigurationProvider +import org.jetbrains.kotlin.utils.addToStdlib.runIf +import java.lang.reflect.Method +import java.net.URLClassLoader + +class JvmBoxRunner( + testServices: TestServices +) : JvmBinaryArtifactHandler(testServices) { + companion object { + private val BOX_IN_SEPARATE_PROCESS_PORT = System.getProperty("kotlin.test.box.in.separate.process.port") + } + + private var boxMethodFound = false + + override fun processAfterAllModules(someAssertionWasFailed: Boolean) { + if (!boxMethodFound) { + assertions.fail { "Can't find box methods" } + } + } + + override fun processModule(module: TestModule, info: BinaryArtifacts.Jvm) { + val ktFiles = info.classFileFactory.inputFiles + val classLoader = createClassLoader(module, info.classFileFactory) + for (ktFile in ktFiles) { + val className = ktFile.getFacadeFqName() ?: continue + val clazz = classLoader.getGeneratedClass(className) + val method = clazz.getBoxMethodOrNull() ?: continue + boxMethodFound = true + callBoxMethodAndCheckResult(classLoader, clazz, method, unexpectedBehaviour = false) + return + } + } + + private fun callBoxMethodAndCheckResult( + classLoader: URLClassLoader, + clazz: Class<*>?, + method: Method, + unexpectedBehaviour: Boolean + ) { + val result = if (BOX_IN_SEPARATE_PROCESS_PORT != null) { + TODO() +// result = invokeBoxInSeparateProcess(classLoader, aClass) + } else { + val savedClassLoader = Thread.currentThread().contextClassLoader + if (savedClassLoader !== classLoader) { + // otherwise the test infrastructure used in the test may conflict with the one from the context classloader + Thread.currentThread().contextClassLoader = classLoader + } + try { + method.invoke(null) as String + } finally { + if (savedClassLoader !== classLoader) { + Thread.currentThread().contextClassLoader = savedClassLoader + } + } + } + if (unexpectedBehaviour) { + TestCase.assertNotSame("OK", result) + } else { + assertions.assertEquals("OK", result) + } + } + + private fun createClassLoader(module: TestModule, classFileFactory: ClassFileFactory): GeneratedClassLoader { + val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module) + val urls = configuration.jvmClasspathRoots.map { it.toURI().toURL() } + val classLoader = URLClassLoader(urls.toTypedArray()) + return GeneratedClassLoader(classFileFactory, classLoader) + } + + private fun KtFile.getFacadeFqName(): String? { + return runIf(getMemberDeclarationsToGenerate(this).isNotEmpty()) { + getFileClassInfoNoResolve(this).facadeClassFqName.asString() + } + } + + private fun ClassLoader.getGeneratedClass(className: String): Class<*> { + try { + return loadClass(className) + } catch (e: ClassNotFoundException) { + assertions.fail { "No class file was generated for: $className" } + } + } + + private fun Class<*>.getBoxMethodOrNull(): Method? { + return try { + getMethod("box") + } catch (e: NoSuchMethodException) { + return null + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/ir/IrBackendFacade.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/ir/IrBackendFacade.kt new file mode 100644 index 00000000000..3a7e7ba4e38 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/ir/IrBackendFacade.kt @@ -0,0 +1,14 @@ +/* + * 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.test.backend.ir + +import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.services.TestServices + +abstract class IrBackendFacade>( + testServices: TestServices, + binaryKind: BinaryKind +) : BackendFacade(testServices, BackendKinds.IrBackend, binaryKind) diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/ir/IrBackendInput.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/ir/IrBackendInput.kt new file mode 100644 index 00000000000..16fb993d6af --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/ir/IrBackendInput.kt @@ -0,0 +1,32 @@ +/* + * 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.test.backend.ir + +import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig +import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensions +import org.jetbrains.kotlin.backend.jvm.MetadataSerializerFactory +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.ir.declarations.IrModuleFragment +import org.jetbrains.kotlin.ir.linkage.IrProvider +import org.jetbrains.kotlin.ir.util.SymbolTable +import org.jetbrains.kotlin.psi2ir.PsiSourceManager +import org.jetbrains.kotlin.test.model.BackendKinds +import org.jetbrains.kotlin.test.model.ResultingArtifact + +// IR backend (JVM, JS, Native) +data class IrBackendInput( + val state: GenerationState, + val irModuleFragment: IrModuleFragment, + val symbolTable: SymbolTable, + val sourceManager: PsiSourceManager, + val phaseConfig: PhaseConfig, + val irProviders: List, + val extensions: JvmGeneratorExtensions, + val serializerFactory: MetadataSerializerFactory +) : ResultingArtifact.BackendInput() { + override val kind: BackendKinds.IrBackend + get() = BackendKinds.IrBackend +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/ir/JvmIrBackendFacade.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/ir/JvmIrBackendFacade.kt new file mode 100644 index 00000000000..600646290a5 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/backend/ir/JvmIrBackendFacade.kt @@ -0,0 +1,36 @@ +/* + * 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.test.backend.ir + +import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory +import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.services.TestServices + +class JvmIrBackendFacade( + testServices: TestServices +) : IrBackendFacade(testServices, ArtifactKinds.Jvm) { + override fun transform( + module: TestModule, + inputArtifact: IrBackendInput + ): BinaryArtifacts.Jvm { + val (state, irModuleFragment, symbolTable, sourceManager, phaseConfig, irProviders, extensions, serializerFactory) = inputArtifact + + val codegenFactory = state.codegenFactory as JvmIrCodegenFactory + codegenFactory.doGenerateFilesInternal( + state, + irModuleFragment, + symbolTable, + sourceManager, + phaseConfig, + irProviders, + extensions, + serializerFactory + ) + state.factory.done() + + return BinaryArtifacts.Jvm(state.factory) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/DefaultsProviderBuilder.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/DefaultsProviderBuilder.kt new file mode 100644 index 00000000000..e46324d3713 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/DefaultsProviderBuilder.kt @@ -0,0 +1,51 @@ +/* + * 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.test.builders + +import org.jetbrains.kotlin.config.ApiVersion +import org.jetbrains.kotlin.config.LanguageVersion +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl +import org.jetbrains.kotlin.fir.PrivateForInline +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.test.services.DefaultsDsl +import org.jetbrains.kotlin.test.services.DefaultsProvider +import org.jetbrains.kotlin.test.model.BackendKind +import org.jetbrains.kotlin.test.model.DependencyKind +import org.jetbrains.kotlin.test.model.FrontendKind + +@DefaultsDsl +class DefaultsProviderBuilder { + lateinit var backend: BackendKind<*> + lateinit var frontend: FrontendKind<*> + lateinit var targetPlatform: TargetPlatform + lateinit var dependencyKind: DependencyKind + + @PrivateForInline + var languageVersionSettings: LanguageVersionSettings? = null + + @PrivateForInline + var languageVersionSettingsBuilder: LanguageVersionSettingsBuilder? = null + + @OptIn(PrivateForInline::class) + inline fun languageSettings(init: LanguageVersionSettingsBuilder.() -> Unit) { + languageVersionSettings = LanguageVersionSettingsBuilder().apply(init).also { + languageVersionSettingsBuilder = it + }.build() + } + + @OptIn(PrivateForInline::class) + fun build(): DefaultsProvider { + return DefaultsProvider( + backend, + frontend, + languageVersionSettings ?: LanguageVersionSettingsImpl(LanguageVersion.LATEST_STABLE, ApiVersion.LATEST_STABLE), + languageVersionSettingsBuilder ?: LanguageVersionSettingsBuilder(), + targetPlatform, + dependencyKind + ) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/RegisteredDirectivesBuilder.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/RegisteredDirectivesBuilder.kt new file mode 100644 index 00000000000..d40373422f9 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/RegisteredDirectivesBuilder.kt @@ -0,0 +1,45 @@ +/* + * 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.test.builders + +import org.jetbrains.kotlin.test.directives.model.* + +class RegisteredDirectivesBuilder { + private val simpleDirectives: MutableList = mutableListOf() + private val stringDirectives: MutableMap> = mutableMapOf() + private val valueDirectives: MutableMap, List> = mutableMapOf() + + operator fun SimpleDirective.unaryPlus() { + simpleDirectives += this + } + + infix fun StringDirective.with(value: String) { + with(listOf(value)) + } + + infix fun StringDirective.with(values: List) { + stringDirectives.putWithExistsCheck(this, values) + } + + infix fun ValueDirective.with(value: T) { + with(listOf(value)) + } + + infix fun ValueDirective.with(values: List) { + valueDirectives.putWithExistsCheck(this, values) + } + + private fun MutableMap.putWithExistsCheck(key: K, value: V) { + val alreadyRegistered = put(key, value) + if (alreadyRegistered != null) { + error("Default values for $key directive already registered") + } + } + + fun build(): RegisteredDirectives { + return RegisteredDirectivesImpl(simpleDirectives, stringDirectives, valueDirectives) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestConfigurationBuilder.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestConfigurationBuilder.kt new file mode 100644 index 00000000000..3f407a61efc --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestConfigurationBuilder.kt @@ -0,0 +1,150 @@ +/* + * 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.test.builders + +import org.jetbrains.kotlin.test.TestConfiguration +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.impl.TestConfigurationImpl +import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.services.* + +typealias Constructor = (TestServices) -> T + +@DefaultsDsl +class TestConfigurationBuilder { + val defaultsProviderBuilder: DefaultsProviderBuilder = DefaultsProviderBuilder() + lateinit var assertions: Assertions + + private val facades: MutableList>> = mutableListOf() + + private val handlers: MutableList>> = mutableListOf() + + private val sourcePreprocessors: MutableList> = mutableListOf() + private val additionalMetaInfoProcessors: MutableList> = mutableListOf() + private val environmentConfigurators: MutableList> = mutableListOf() + + private val additionalSourceProviders: MutableList> = mutableListOf() + + private val metaTestConfigurators: MutableList> = mutableListOf() + private val afterAnalysisCheckers: MutableList> = mutableListOf() + + private var metaInfoHandlerEnabled: Boolean = false + + private val directives: MutableList = mutableListOf() + val defaultRegisteredDirectivesBuilder: RegisteredDirectivesBuilder = RegisteredDirectivesBuilder() + + private val configurationsByTestDataCondition: MutableList Unit>> = mutableListOf() + + fun forTestsMatching(pattern: String, configuration: TestConfigurationBuilder.() -> Unit) { + val regex = pattern.toMatchingRegexString().toRegex() + forTestsMatching(regex, configuration) + } + + infix fun String.or(other: String): String { + return """$this|$other""" + } + + private fun String.toMatchingRegexString(): String = """^${replace("*", ".*")}$""" + + fun forTestsMatching(pattern: Regex, configuration: TestConfigurationBuilder.() -> Unit) { + configurationsByTestDataCondition += pattern to configuration + } + + inline fun globalDefaults(init: DefaultsProviderBuilder.() -> Unit) { + defaultsProviderBuilder.apply(init) + } + + fun unregisterAllFacades() { + facades.clear() + } + + fun useFrontendFacades(vararg constructor: Constructor>) { + facades += constructor + } + + fun useBackendFacades(vararg constructor: Constructor>) { + facades += constructor + } + + fun useFrontend2BackendConverters(vararg constructor: Constructor>) { + facades += constructor + } + + fun useFrontendHandlers(vararg constructor: Constructor>) { + handlers += constructor + } + + fun useBackendHandlers(vararg constructor: Constructor>) { + handlers += constructor + } + + fun useArtifactsHandlers(vararg constructor: Constructor>) { + handlers += constructor + } + + fun useSourcePreprocessor(vararg preprocessors: Constructor) { + sourcePreprocessors += preprocessors + } + + fun useDirectives(vararg directives: DirectivesContainer) { + this.directives += directives + } + + fun useConfigurators(vararg environmentConfigurators: Constructor) { + this.environmentConfigurators += environmentConfigurators + } + + fun useMetaInfoProcessors(vararg updaters: Constructor) { + additionalMetaInfoProcessors += updaters + } + + fun useAdditionalSourceProviders(vararg providers: Constructor) { + additionalSourceProviders += providers + } + + fun useMetaTestConfigurators(vararg configurators: Constructor) { + metaTestConfigurators += configurators + } + + fun useAfterAnalysisCheckers(vararg checkers: Constructor) { + afterAnalysisCheckers += checkers + } + + inline fun defaultDirectives(init: RegisteredDirectivesBuilder.() -> Unit) { + defaultRegisteredDirectivesBuilder.apply(init) + } + + fun enableMetaInfoHandler() { + metaInfoHandlerEnabled = true + } + + fun build(testDataPath: String): TestConfiguration { + for ((regex, configuration) in configurationsByTestDataCondition) { + if (regex.matches(testDataPath)) { + this.configuration() + } + } + return TestConfigurationImpl( + defaultsProviderBuilder.build(), + assertions, + facades, + handlers, + sourcePreprocessors, + additionalMetaInfoProcessors, + environmentConfigurators, + additionalSourceProviders, + metaTestConfigurators, + afterAnalysisCheckers, + metaInfoHandlerEnabled, + directives, + defaultRegisteredDirectivesBuilder.build() + ) + } +} + +inline fun testConfiguration(testDataPath: String, init: TestConfigurationBuilder.() -> Unit): TestConfiguration { + return TestConfigurationBuilder().apply(init).build(testDataPath) +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestConfigurationDslMarker.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestConfigurationDslMarker.kt new file mode 100644 index 00000000000..7e6d523755a --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestConfigurationDslMarker.kt @@ -0,0 +1,9 @@ +/* + * 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.test.builders + +@DslMarker +annotation class TestConfigurationDslMarker() diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestPathMatcher.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestPathMatcher.kt new file mode 100644 index 00000000000..c9d4830ed09 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestPathMatcher.kt @@ -0,0 +1,7 @@ +/* + * 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.test.builders + diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestRunnerBuilder.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestRunnerBuilder.kt new file mode 100644 index 00000000000..dc74149064e --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/builders/TestRunnerBuilder.kt @@ -0,0 +1,12 @@ +/* + * 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.test.builders + +import org.jetbrains.kotlin.test.TestRunner + +inline fun testRunner(testDataPath: String, crossinline init: TestConfigurationBuilder.() -> Unit): TestRunner { + return TestRunner(testConfiguration(testDataPath, init)) +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/AdditionalFilesDirectives.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/AdditionalFilesDirectives.kt new file mode 100644 index 00000000000..f28059996ef --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/AdditionalFilesDirectives.kt @@ -0,0 +1,40 @@ +/* + * 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.test.directives + +import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer + +object AdditionalFilesDirectives : SimpleDirectivesContainer() { + val CHECK_TYPE by directive( + description = """ + Adds util functions for type checking + See file ./compiler/testData/diagnostics/helpers/types/checkType.kt + """.trimIndent() + ) + + val WITH_COROUTINES by directive( + description = """ + Adds util functions for checking coroutines + See file ./compiler/testData/diagnostics/helpers/coroutines/CoroutineHelpers.kt + """.trimIndent() + ) + + val CHECK_STATE_MACHINE by directive( + description = """ + Adds util functions for checking state machines + May be enabled only with $WITH_COROUTINES directive + See file ./compiler/testData/diagnostics/helpers/coroutines/StateMachineChecker.kt + """.trimIndent() + ) + + val CHECK_TAIL_CALL_OPTIMIZATION by directive( + description = """ + Adds util functions for checking tail call optimizations + May be enabled only with $WITH_COROUTINES directive + See file ./compiler/testData/diagnostics/helpers/coroutines/TailCallOptimizationChecker.kt + """.trimIndent() + ) +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt new file mode 100644 index 00000000000..4748194f642 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/CodegenTestDirectives.kt @@ -0,0 +1,15 @@ +/* + * 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.test.directives + +import org.jetbrains.kotlin.test.TargetBackend +import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer + +object CodegenTestDirectives : SimpleDirectivesContainer() { + val IGNORE_BACKEND by enumDirective( + description = "Ignore failures of test on target backend" + ) +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/DiagnosticsDirectives.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/DiagnosticsDirectives.kt new file mode 100644 index 00000000000..983511bbde4 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/DiagnosticsDirectives.kt @@ -0,0 +1,49 @@ +/* + * 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.test.directives + +import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives.USE_JAVAC +import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer + +object DiagnosticsDirectives : SimpleDirectivesContainer() { + val WITH_NEW_INFERENCE by directive( + description = "Enables rendering different diagnostics for old and new inference" + ) + + val DIAGNOSTICS by stringDirective( + description = """ + Enables or disables rendering of specific diagnostics. + Syntax: + Must be '[+-]DIAGNOSTIC_FACTORY_NAME' + where '+' means 'include' + '-' means 'exclude' + '+' May be used in case if some diagnostic was disabled by default in test runner + and it should be enabled in specific test + """.trimIndent() + ) + + val SKIP_TXT by directive( + description = "Disables handler which dumps declarations to testName.txt" + ) + + val NI_EXPECTED_FILE by directive( + description = "Create separate .ni.txt file for declarations dump with new inference enabled" + ) + + val SKIP_JAVAC by directive( + description = "Skip this test if $USE_JAVAC enabled" + ) + + val JAVAC_EXPECTED_FILE by directive( + description = "Dump descriptors to .javac.txt file if $USE_JAVAC enabled" + ) + + val MARK_DYNAMIC_CALLS by directive( + description = """ + Render debug info about dynamic calls + """.trimIndent() + ) +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/FirDiagnosticsDirectives.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/FirDiagnosticsDirectives.kt new file mode 100644 index 00000000000..609cf5ba2a9 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/FirDiagnosticsDirectives.kt @@ -0,0 +1,27 @@ +/* + * 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.test.directives + +import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer + +object FirDiagnosticsDirectives : SimpleDirectivesContainer() { + val DUMP_CFG by directive( + description = """ + Dumps control flow graphs of all declarations to `testName.dot` file + This directive may be applied only to all modules + """.trimIndent() + ) + + val FIR_DUMP by directive( + description = """ + Dumps resulting fir to `testName.fir` file + """.trimIndent() + ) + + val FIR_IDENTICAL by directive( + description = "Contents of fir test data file and FE 1.0 are identical" + ) +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/JsEnvironmentConfigurationDirectives.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/JsEnvironmentConfigurationDirectives.kt new file mode 100644 index 00000000000..728505eac6d --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/JsEnvironmentConfigurationDirectives.kt @@ -0,0 +1,13 @@ +/* + * 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.test.directives + +import org.jetbrains.kotlin.serialization.js.ModuleKind +import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer + +object JsEnvironmentConfigurationDirectives : SimpleDirectivesContainer() { + val MODULE_KIND by enumDirective("Specifies kind of js module") +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/JvmEnvironmentConfigurationDirectives.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/JvmEnvironmentConfigurationDirectives.kt new file mode 100644 index 00000000000..a32f9dbb48a --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/directives/JvmEnvironmentConfigurationDirectives.kt @@ -0,0 +1,36 @@ +/* + * 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.test.directives + +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.test.TestJdkKind +import org.jetbrains.kotlin.test.directives.model.SimpleDirectivesContainer + +object JvmEnvironmentConfigurationDirectives : SimpleDirectivesContainer() { + val JVM_TARGET by enumDirective( + description = "Target bytecode version", + additionalParser = JvmTarget.Companion::fromString + ) + + val JDK_KIND by enumDirective("JDK used in tests") + val FULL_JDK by directive("Add full java standard library to classpath") + val STDLIB_JDK8 by directive("Add Java 8 stdlib to classpath") + + val WITH_RUNTIME by directive( + description = """ + Add Kotlin stdlib to classpath + This directive is deprecated, use WITH_STDLIB instead + """.trimIndent() + ) + val WITH_STDLIB by directive("Add Kotlin runtime to classpath") + val WITH_REFLECT by directive("Add Kotlin reflect to classpath") + val NO_RUNTIME by directive("Don't add any runtime libs to classpath") + + val ANDROID_ANNOTATIONS by directive("Add android annotations to classpath") + + val USE_PSI_CLASS_FILES_READING by directive("Use a slower (PSI-based) class files reading implementation") + val USE_JAVAC by directive("Enable javac integration") +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontend2ClassicBackendConverter.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontend2ClassicBackendConverter.kt new file mode 100644 index 00000000000..b7a3df856c1 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontend2ClassicBackendConverter.kt @@ -0,0 +1,32 @@ +/* + * 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.test.frontend.classic + +import org.jetbrains.kotlin.test.backend.classic.ClassicBackendInput +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.model.* + +class ClassicFrontend2ClassicBackendConverter( + testServices: TestServices +) : Frontend2BackendConverter( + testServices, + FrontendKinds.ClassicFrontend, + BackendKinds.ClassicBackend +) { + override fun transform( + module: TestModule, + inputArtifact: ClassicFrontendOutputArtifact + ): ClassicBackendInput { + val (psiFiles, analysisResults, project, languageVersionSettings) = inputArtifact + return ClassicBackendInput( + psiFiles.values, + analysisResults.bindingContext, + analysisResults.moduleDescriptor, + project, + languageVersionSettings + ) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontend2IrConverter.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontend2IrConverter.kt new file mode 100644 index 00000000000..420fffd775e --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontend2IrConverter.kt @@ -0,0 +1,158 @@ +/* + * 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.test.frontend.classic + +import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension +import org.jetbrains.kotlin.backend.common.extensions.IrPluginContextImpl +import org.jetbrains.kotlin.backend.common.ir.BuiltinSymbolsBase +import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig +import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensions +import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory +import org.jetbrains.kotlin.backend.jvm.JvmNameProvider +import org.jetbrains.kotlin.backend.jvm.codegen.DescriptorMetadataSerializer +import org.jetbrains.kotlin.backend.jvm.jvmPhases +import org.jetbrains.kotlin.backend.jvm.serialization.JvmIdSignatureDescriptor +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.codegen.ClassBuilderFactories +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.konan.DeserializedKlibModuleOrigin +import org.jetbrains.kotlin.descriptors.konan.KlibModuleOrigin +import org.jetbrains.kotlin.idea.MainFunctionDetector +import org.jetbrains.kotlin.ir.backend.jvm.serialization.EmptyLoggingContext +import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmIrLinker +import org.jetbrains.kotlin.ir.backend.jvm.serialization.JvmManglerDesc +import org.jetbrains.kotlin.ir.builders.TranslationPluginContext +import org.jetbrains.kotlin.ir.declarations.impl.IrFactoryImpl +import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns +import org.jetbrains.kotlin.ir.descriptors.IrFunctionFactory +import org.jetbrains.kotlin.ir.util.DeclarationStubGenerator +import org.jetbrains.kotlin.ir.util.ReferenceSymbolTable +import org.jetbrains.kotlin.ir.util.SymbolTable +import org.jetbrains.kotlin.ir.util.TypeTranslator +import org.jetbrains.kotlin.psi2ir.Psi2IrConfiguration +import org.jetbrains.kotlin.psi2ir.Psi2IrTranslator +import org.jetbrains.kotlin.resolve.BindingContext +import org.jetbrains.kotlin.test.backend.ir.IrBackendInput +import org.jetbrains.kotlin.test.model.BackendKinds +import org.jetbrains.kotlin.test.model.Frontend2BackendConverter +import org.jetbrains.kotlin.test.model.FrontendKinds +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.compilerConfigurationProvider + +class ClassicFrontend2IrConverter( + testServices: TestServices +) : Frontend2BackendConverter( + testServices, + FrontendKinds.ClassicFrontend, + BackendKinds.IrBackend +) { + override fun transform( + module: TestModule, + inputArtifact: ClassicFrontendOutputArtifact + ): IrBackendInput { + val (psiFiles, analysisResult, project, languageVersionSettings) = inputArtifact + + val configuration = testServices.compilerConfigurationProvider.getCompilerConfiguration(module) + + val files = psiFiles.values.toList() + val phaseConfig = configuration.get(CLIConfigurationKeys.PHASE_CONFIG) ?: PhaseConfig(jvmPhases) + val state = GenerationState.Builder( + project, ClassBuilderFactories.TEST, analysisResult.moduleDescriptor, analysisResult.bindingContext, + files, configuration + ).codegenFactory( + JvmIrCodegenFactory(phaseConfig) + ).isIrBackend(true).build() + + val extensions = JvmGeneratorExtensions() + val mangler = JvmManglerDesc(MainFunctionDetector(state.bindingContext, state.languageVersionSettings)) + val psi2ir = Psi2IrTranslator(state.languageVersionSettings, Psi2IrConfiguration()) + val symbolTable = SymbolTable(JvmIdSignatureDescriptor(mangler), IrFactoryImpl, JvmNameProvider) + val psi2irContext = psi2ir.createGeneratorContext(state.module, state.bindingContext, symbolTable, extensions) + val pluginExtensions = IrGenerationExtension.getInstances(state.project) + val functionFactory = IrFunctionFactory(psi2irContext.irBuiltIns, symbolTable) + psi2irContext.irBuiltIns.functionFactory = functionFactory + + val stubGenerator = DeclarationStubGenerator( + psi2irContext.moduleDescriptor, symbolTable, psi2irContext.irBuiltIns.languageVersionSettings, extensions + ) + val frontEndContext = object : TranslationPluginContext { + override val moduleDescriptor: ModuleDescriptor + get() = psi2irContext.moduleDescriptor + override val bindingContext: BindingContext + get() = psi2irContext.bindingContext + override val symbolTable: ReferenceSymbolTable + get() = symbolTable + override val typeTranslator: TypeTranslator + get() = psi2irContext.typeTranslator + override val irBuiltIns: IrBuiltIns + get() = psi2irContext.irBuiltIns + } + val irLinker = JvmIrLinker( + psi2irContext.moduleDescriptor, + EmptyLoggingContext, + psi2irContext.irBuiltIns, + symbolTable, + functionFactory, + frontEndContext, + stubGenerator, + mangler + ) + + val pluginContext by lazy { + psi2irContext.run { + val symbols = BuiltinSymbolsBase(irBuiltIns, moduleDescriptor.builtIns, symbolTable.lazyWrapper) + IrPluginContextImpl( + moduleDescriptor, bindingContext, languageVersionSettings, symbolTable, typeTranslator, irBuiltIns, irLinker, symbols + ) + } + } + + for (extension in pluginExtensions) { + psi2ir.addPostprocessingStep { moduleFragment -> + val old = stubGenerator.unboundSymbolGeneration + try { + stubGenerator.unboundSymbolGeneration = true + extension.generate(moduleFragment, pluginContext) + } finally { + stubGenerator.unboundSymbolGeneration = old + } + } + } + + val dependencies = psi2irContext.moduleDescriptor.allDependencyModules.map { + val kotlinLibrary = (it.getCapability(KlibModuleOrigin.CAPABILITY) as? DeserializedKlibModuleOrigin)?.library + irLinker.deserializeIrModuleHeader(it, kotlinLibrary) + } + val irProviders = listOf(irLinker) + + val irModuleFragment = psi2ir.generateModuleFragment( + psi2irContext, + files, + irProviders, + pluginExtensions, + expectDescriptorToSymbol = null + ) + irLinker.postProcess() + + stubGenerator.unboundSymbolGeneration = true + + // We need to compile all files we reference in Klibs + irModuleFragment.files.addAll(dependencies.flatMap { it.files }) + + return IrBackendInput( + state, + irModuleFragment, + symbolTable, + psi2irContext.sourceManager, + phaseConfig, + irProviders, + extensions, + ::DescriptorMetadataSerializer + ) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt new file mode 100644 index 00000000000..a15d042fdeb --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendFacade.kt @@ -0,0 +1,263 @@ +/* + * 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.test.frontend.classic + +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.analyzer.common.CommonResolverForModuleFactory +import org.jetbrains.kotlin.builtins.jvm.JvmBuiltIns +import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider +import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace +import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.JVMConfigurationKeys +import org.jetbrains.kotlin.config.JVMConfigurationKeys.JVM_TARGET +import org.jetbrains.kotlin.config.JvmTarget +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.container.get +import org.jetbrains.kotlin.context.ProjectContext +import org.jetbrains.kotlin.context.withModule +import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl +import org.jetbrains.kotlin.frontend.java.di.createContainerForLazyResolveWithJava +import org.jetbrains.kotlin.frontend.java.di.initJvmBuiltInsForTopDownAnalysis +import org.jetbrains.kotlin.incremental.components.ExpectActualTracker +import org.jetbrains.kotlin.incremental.components.LookupTracker +import org.jetbrains.kotlin.js.analyze.TopDownAnalyzerFacadeForJS +import org.jetbrains.kotlin.js.config.JsConfig +import org.jetbrains.kotlin.load.java.lazy.SingleModuleClassResolver +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.platform.isCommon +import org.jetbrains.kotlin.platform.js.isJs +import org.jetbrains.kotlin.platform.jvm.JvmPlatforms +import org.jetbrains.kotlin.platform.jvm.isJvm +import org.jetbrains.kotlin.platform.konan.isNative +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.CompilerEnvironment +import org.jetbrains.kotlin.resolve.LazyTopDownAnalyzer +import org.jetbrains.kotlin.resolve.TopDownAnalysisMode +import org.jetbrains.kotlin.resolve.jvm.JavaDescriptorResolver +import org.jetbrains.kotlin.resolve.lazy.KotlinCodeAnalyzer +import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory +import org.jetbrains.kotlin.serialization.deserialization.MetadataPartProvider +import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives +import org.jetbrains.kotlin.test.model.DependencyKind +import org.jetbrains.kotlin.test.model.FrontendFacade +import org.jetbrains.kotlin.test.model.FrontendKinds +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.* +import org.jetbrains.kotlin.test.util.KtTestUtil +import org.jetbrains.kotlin.utils.addToStdlib.runIf +import java.io.File + +class ClassicFrontendFacade( + testServices: TestServices +) : FrontendFacade(testServices, FrontendKinds.ClassicFrontend) { + override val additionalServices: List + get() = listOf(service(::ModuleDescriptorProvider)) + + override fun analyze(module: TestModule): ClassicFrontendOutputArtifact { + val dependencyProvider = testServices.dependencyProvider + val moduleDescriptorProvider = testServices.moduleDescriptorProvider + val compilerConfigurationProvider = testServices.compilerConfigurationProvider + val packagePartProviderFactory = compilerConfigurationProvider.getPackagePartProviderFactory(module) + val project = compilerConfigurationProvider.getProject(module) + val configuration = compilerConfigurationProvider.getCompilerConfiguration(module) + + val ktFilesMap = testServices.sourceFileProvider.getKtFilesForSourceFiles(module.files, project).toMutableMap() + val languageVersionSettings = module.languageVersionSettings + + val sourceDependencies = module.dependencies.filter { it.kind == DependencyKind.Source } + val dependentDescriptors = sourceDependencies.map { + val testModule = dependencyProvider.getTestModule(it.moduleName) + moduleDescriptorProvider.getModuleDescriptor(testModule) + } + + var hasCommonModules = false + sourceDependencies.forEach { + val dependencyModule = dependencyProvider.getTestModule(it.moduleName) + if (dependencyModule.targetPlatform.isCommon()) { + val artifact = dependencyProvider.getArtifact(dependencyModule, FrontendKinds.ClassicFrontend) + /* + * We need create KtFiles again with new project because otherwise we can access to some caches using + * old project as key which may leads to missing services in core environment + */ + ktFilesMap.putAll(testServices.sourceFileProvider.getKtFilesForSourceFiles(artifact.allKtFiles.keys, project)) + hasCommonModules = true + } + } + + val ktFiles = ktFilesMap.values.toList() + setupJavacIfNeeded(module, ktFiles, configuration) + val analysisResult = performResolve( + module, + project, + configuration, + packagePartProviderFactory, + ktFiles, + languageVersionSettings, + dependentDescriptors, + hasCommonModules + ) + moduleDescriptorProvider.replaceModuleDescriptorForModule(module, analysisResult.moduleDescriptor) + return ClassicFrontendOutputArtifact( + ktFilesMap, + analysisResult, + project, + languageVersionSettings + ) + } + + private fun setupJavacIfNeeded( + module: TestModule, + ktFiles: List, + configuration: CompilerConfiguration + ) { + if (JvmEnvironmentConfigurationDirectives.USE_JAVAC !in module.directives) return + val mockJdk = runIf(JvmEnvironmentConfigurationDirectives.FULL_JDK !in module.directives) { + File(KtTestUtil.getHomeDirectory(), "compiler/testData/mockJDK/jre/lib/rt.jar") + } + testServices.compilerConfigurationProvider.registerJavacForModule(module, ktFiles, mockJdk) + configuration.put(JVMConfigurationKeys.USE_JAVAC, true) + } + + private fun performResolve( + module: TestModule, + project: Project, + configuration: CompilerConfiguration, + packagePartProviderFactory: (GlobalSearchScope) -> JvmPackagePartProvider, + files: List, + languageVersionSettings: LanguageVersionSettings, + dependentDescriptors: List, + hasCommonModules: Boolean + ): AnalysisResult { + val targetPlatform = module.targetPlatform + return when { + targetPlatform.isJvm() -> performJvmModuleResolve( + module, + project, + configuration, + packagePartProviderFactory, + files, + dependentDescriptors, + hasCommonModules + ) + targetPlatform.isJs() -> performJsModuleResolve(project, configuration, files, dependentDescriptors) + targetPlatform.isNative() -> TODO() + targetPlatform.isCommon() -> performCommonModuleResolve(module, files, languageVersionSettings) + else -> error("Should not be here") + } + } + + @OptIn(ExperimentalStdlibApi::class) + private fun performJvmModuleResolve( + module: TestModule, + project: Project, + configuration: CompilerConfiguration, + packagePartProviderFactory: (GlobalSearchScope) -> JvmPackagePartProvider, + files: List, + dependentDescriptors: List, + hasCommonModules: Boolean + ): AnalysisResult { + val moduleTrace = NoScopeRecordCliBindingTrace() + if (!hasCommonModules) { + return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration( + project, + files, + moduleTrace, + configuration.copy(), + packagePartProviderFactory, + explicitModuleDependencyList = dependentDescriptors + ) + } + + val projectContext = ProjectContext(project, "test project context") + val storageManager = projectContext.storageManager + + val builtIns = JvmBuiltIns(storageManager, JvmBuiltIns.Kind.FROM_CLASS_LOADER) + val moduleDescriptor = ModuleDescriptorImpl(Name.special("<${module.name}>"), storageManager, builtIns, module.targetPlatform) + val dependencies = buildList { + add(moduleDescriptor) + add(moduleDescriptor.builtIns.builtInsModule) + addAll(dependentDescriptors) + } + moduleDescriptor.setDependencies(dependencies) + + val moduleContentScope = GlobalSearchScope.allScope(project) + val moduleClassResolver = SingleModuleClassResolver() + val moduleContext = projectContext.withModule(moduleDescriptor) + val jvmTarget = configuration[JVM_TARGET] ?: JvmTarget.DEFAULT + val container = createContainerForLazyResolveWithJava( + JvmPlatforms.jvmPlatformByTargetVersion(jvmTarget), // TODO(dsavvinov): do not pass JvmTarget around + moduleContext, + moduleTrace, + FileBasedDeclarationProviderFactory(moduleContext.storageManager, files), + moduleContentScope, + moduleClassResolver, + CompilerEnvironment, LookupTracker.DO_NOTHING, + ExpectActualTracker.DoNothing, + packagePartProviderFactory(moduleContentScope), + module.languageVersionSettings, + useBuiltInsProvider = true + ) + + container.initJvmBuiltInsForTopDownAnalysis() + moduleClassResolver.resolver = container.get() + + moduleDescriptor.initialize( + CompositePackageFragmentProvider( + listOf( + container.get().packageFragmentProvider, + container.get().packageFragmentProvider + ) + ) + ) + + container.get().analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, files) + + return AnalysisResult.success(moduleTrace.bindingContext, moduleDescriptor) + } + + @OptIn(ExperimentalStdlibApi::class) + private fun performJsModuleResolve( + project: Project, + configuration: CompilerConfiguration, + files: List, + dependentDescriptors: List + ): AnalysisResult { + val jsConfig = JsConfig(project, configuration) + val dependentDescriptorsIncludingLibraries = buildList { + addAll(dependentDescriptors) + addAll(jsConfig.moduleDescriptors) + } + return TopDownAnalyzerFacadeForJS.analyzeFiles( + files, + project, + configuration, + moduleDescriptors = dependentDescriptorsIncludingLibraries, + friendModuleDescriptors = emptyList() + ) + } + + private fun performCommonModuleResolve( + module: TestModule, + files: List, + languageVersionSettings: LanguageVersionSettings, + ): AnalysisResult { + return CommonResolverForModuleFactory.analyzeFiles( + files, + Name.special("<${module.name}>"), + dependOnBuiltIns = true, + languageVersionSettings, + module.targetPlatform, + // TODO: add dependency manager + ) { _ -> + // TODO + MetadataPartProvider.Empty + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendOutputArtifact.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendOutputArtifact.kt new file mode 100644 index 00000000000..e30b84b8923 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ClassicFrontendOutputArtifact.kt @@ -0,0 +1,26 @@ +/* + * 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.test.frontend.classic + +import com.intellij.openapi.project.Project +import org.jetbrains.kotlin.analyzer.AnalysisResult +import org.jetbrains.kotlin.config.LanguageVersionSettings +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.model.FrontendKinds +import org.jetbrains.kotlin.test.model.ResultingArtifact +import org.jetbrains.kotlin.test.model.TestFile + +data class ClassicFrontendOutputArtifact( + val allKtFiles: Map, + val analysisResult: AnalysisResult, + val project: Project, + val languageVersionSettings: LanguageVersionSettings +) : ResultingArtifact.FrontendOutput() { + override val kind: FrontendKinds.ClassicFrontend + get() = FrontendKinds.ClassicFrontend + + val ktFiles: Map = allKtFiles.filterKeys { !it.isAdditional } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ModuleDescriptorProvider.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ModuleDescriptorProvider.kt new file mode 100644 index 00000000000..57717a45165 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/ModuleDescriptorProvider.kt @@ -0,0 +1,32 @@ +/* + * 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.test.frontend.classic + +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl +import org.jetbrains.kotlin.test.services.TestService +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.assertions +import org.jetbrains.kotlin.test.model.TestModule + +class ModuleDescriptorProvider( + private val testServices: TestServices +) : TestService { + private val moduleDescriptorByModule = mutableMapOf() + + fun getModuleDescriptor(testModule: TestModule): ModuleDescriptorImpl { + return moduleDescriptorByModule[testModule] ?: testServices.assertions.fail { + "Module descriptor for module ${testModule.name} not found" + } + } + + fun replaceModuleDescriptorForModule(testModule: TestModule, moduleDescriptor: ModuleDescriptor) { + require(moduleDescriptor is ModuleDescriptorImpl) + moduleDescriptorByModule[testModule] = moduleDescriptor + } +} + +val TestServices.moduleDescriptorProvider: ModuleDescriptorProvider by TestServices.testServiceAccessor() diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/ClassicDiagnosticsHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/ClassicDiagnosticsHandler.kt new file mode 100644 index 00000000000..de29d974388 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/ClassicDiagnosticsHandler.kt @@ -0,0 +1,253 @@ +/* + * 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.test.frontend.classic.handlers + +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.asJava.getJvmSignatureDiagnostics +import org.jetbrains.kotlin.checkers.diagnostics.SyntaxErrorDiagnostic +import org.jetbrains.kotlin.checkers.utils.CheckerTestUtil +import org.jetbrains.kotlin.checkers.utils.DiagnosticsRenderingConfiguration +import org.jetbrains.kotlin.codeMetaInfo.model.CodeMetaInfo +import org.jetbrains.kotlin.codeMetaInfo.model.DiagnosticCodeMetaInfo +import org.jetbrains.kotlin.codeMetaInfo.model.ParsedCodeMetaInfo +import org.jetbrains.kotlin.config.JVMConfigurationKeys +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.diagnostics.Errors +import org.jetbrains.kotlin.platform.isCommon +import org.jetbrains.kotlin.platform.js.isJs +import org.jetbrains.kotlin.platform.jvm.isJvm +import org.jetbrains.kotlin.platform.konan.isNative +import org.jetbrains.kotlin.psi.KtDeclaration +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.AnalyzingUtils +import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowValueFactoryImpl +import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives +import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives +import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives.MARK_DYNAMIC_CALLS +import org.jetbrains.kotlin.test.directives.LanguageSettingsDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact +import org.jetbrains.kotlin.test.model.TestFile +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.* +import java.util.* + +class ClassicDiagnosticsHandler(testServices: TestServices) : ClassicFrontendAnalysisHandler(testServices) { + override val directivesContainers: List = + listOf(DiagnosticsDirectives) + + override val additionalServices: List = + listOf(service(::DiagnosticsService)) + + private val globalMetadataInfoHandler: GlobalMetadataInfoHandler + get() = testServices.globalMetadataInfoHandler + + private val diagnosticsService: DiagnosticsService + get() = testServices.diagnosticsService + + @OptIn(ExperimentalStdlibApi::class) + override fun processModule(module: TestModule, info: ClassicFrontendOutputArtifact) { + var allDiagnostics = info.analysisResult.bindingContext.diagnostics + computeJvmSignatureDiagnostics(info) + if (AdditionalFilesDirectives.CHECK_TYPE in module.directives) { + allDiagnostics = allDiagnostics.filter { it.factory.name != Errors.UNDERSCORE_USAGE_WITHOUT_BACKTICKS.name } + } + if (LanguageSettingsDirectives.API_VERSION in module.directives) { + allDiagnostics = allDiagnostics.filter { it.factory.name != Errors.NEWER_VERSION_IN_SINCE_KOTLIN.name } + } + + val diagnosticsPerFile = allDiagnostics.groupBy { it.psiFile } + + val withNewInferenceModeEnabled = testServices.withNewInferenceModeEnabled() + + val configuration = DiagnosticsRenderingConfiguration( + platform = null, + withNewInference = info.languageVersionSettings.supportsFeature(LanguageFeature.NewInference), + languageVersionSettings = info.languageVersionSettings, + skipDebugInfoDiagnostics = testServices.compilerConfigurationProvider.getCompilerConfiguration(module) + .getBoolean(JVMConfigurationKeys.IR) + ) + + for ((file, ktFile) in info.ktFiles) { + val diagnostics = diagnosticsPerFile[ktFile] ?: emptyList() + for (diagnostic in diagnostics) { + if (!diagnostic.isValid) continue + if (!diagnosticsService.shouldRenderDiagnostic(module, diagnostic.factory.name)) continue + globalMetadataInfoHandler.addMetadataInfosForFile( + file, + diagnostic.toMetaInfo( + module, + file, + configuration.withNewInference, + withNewInferenceModeEnabled + ) + ) + } + for (errorElement in AnalyzingUtils.getSyntaxErrorRanges(ktFile)) { + globalMetadataInfoHandler.addMetadataInfosForFile( + file, + SyntaxErrorDiagnostic(errorElement).toMetaInfo( + module, + file, + configuration.withNewInference, + withNewInferenceModeEnabled + ) + ) + } + processDebugInfoDiagnostics(configuration, module, file, ktFile, info, withNewInferenceModeEnabled) + } + } + + private fun computeJvmSignatureDiagnostics(info: ClassicFrontendOutputArtifact): Set { + if (testServices.moduleStructure.modules.any { !it.targetPlatform.isJvm() }) return emptySet() + val bindingContext = info.analysisResult.bindingContext + val project = info.project + val jvmSignatureDiagnostics = HashSet() + for (ktFile in info.ktFiles.values) { + val declarations = PsiTreeUtil.findChildrenOfType(ktFile, KtDeclaration::class.java) + for (declaration in declarations) { + val diagnostics = getJvmSignatureDiagnostics( + declaration, + bindingContext.diagnostics, + GlobalSearchScope.allScope(project) + ) ?: continue + jvmSignatureDiagnostics.addAll(diagnostics.forElement(declaration)) + } + } + return jvmSignatureDiagnostics + } + + private fun Diagnostic.toMetaInfo( + module: TestModule, + file: TestFile, + newInferenceEnabled: Boolean, + withNewInferenceModeEnabled: Boolean + ): List = textRanges.map { range -> + val metaInfo = DiagnosticCodeMetaInfo(range, ClassicMetaInfoUtils.renderDiagnosticNoArgs, this) + if (withNewInferenceModeEnabled) { + metaInfo.attributes += if (newInferenceEnabled) OldNewInferenceMetaInfoProcessor.NI else OldNewInferenceMetaInfoProcessor.OI + } + if (file !in module.files) { + val targetPlatform = module.targetPlatform + metaInfo.attributes += when { + targetPlatform.isJvm() -> "JVM" + targetPlatform.isJs() -> "JS" + targetPlatform.isNative() -> "NATIVE" + targetPlatform.isCommon() -> "COMMON" + else -> error("Should not be here") + } + } + val existing = globalMetadataInfoHandler.getExistingMetaInfosForActualMetadata(file, metaInfo) + if (existing.any { it.description != null }) { + metaInfo.replaceRenderConfiguration(ClassicMetaInfoUtils.renderDiagnosticWithArgs) + } + metaInfo + } + + private fun processDebugInfoDiagnostics( + configuration: DiagnosticsRenderingConfiguration, + module: TestModule, + file: TestFile, + ktFile: KtFile, + info: ClassicFrontendOutputArtifact, + withNewInferenceModeEnabled: Boolean + ) { + val diagnosedRanges = globalMetadataInfoHandler.getExistingMetaInfosForFile(file) + .groupBy( + keySelector = { it.start..it.end }, + valueTransform = { it.tag } + ) + .mapValues { (_, it) -> it.toMutableSet() } + val debugAnnotations = CheckerTestUtil.getDebugInfoDiagnostics( + ktFile, + info.analysisResult.bindingContext, + markDynamicCalls = MARK_DYNAMIC_CALLS in module.directives, + dynamicCallDescriptors = mutableListOf(), + configuration, + dataFlowValueFactory = DataFlowValueFactoryImpl(info.languageVersionSettings), + info.analysisResult.moduleDescriptor as ModuleDescriptorImpl, + diagnosedRanges = diagnosedRanges + ) + debugAnnotations.mapNotNull { debugAnnotation -> + if (!diagnosticsService.shouldRenderDiagnostic(module, debugAnnotation.diagnostic.factory.name)) return@mapNotNull null + globalMetadataInfoHandler.addMetadataInfosForFile( + file, + debugAnnotation.diagnostic.toMetaInfo(module, file, configuration.withNewInference, withNewInferenceModeEnabled) + ) + } + } + + override fun processAfterAllModules(someAssertionWasFailed: Boolean) {} +} + +class OldNewInferenceMetaInfoProcessor(testServices: TestServices) : AdditionalMetaInfoProcessor(testServices) { + companion object { + const val OI = "OI" + const val NI = "NI" + } + + override fun processMetaInfos(module: TestModule, file: TestFile) { + /* + * Rules for OI/NI attribute: + * ┌──────────┬──────┬──────┬──────────┐ + * │ │ OI │ NI │ nothing │ <- reported + * ├──────────┼──────┼──────┼──────────┤ + * │ nothing │ both │ both │ nothing │ + * │ OI │ OI │ both │ OI │ + * │ NI │ both │ NI │ NI │ + * │ both │ both │ both │ opposite │ <- OI if NI enabled in test and vice versa + * └──────────┴──────┴──────┴──────────┘ + * ^ existed + */ + if (!testServices.withNewInferenceModeEnabled()) return + val newInferenceEnabled = module.languageVersionSettings.supportsFeature(LanguageFeature.NewInference) + val (currentFlag, otherFlag) = when (newInferenceEnabled) { + true -> NI to OI + false -> OI to NI + } + val matchedExistedInfos = mutableSetOf() + val matchedReportedInfos = mutableSetOf() + val allReportedInfos = globalMetadataInfoHandler.getReportedMetaInfosForFile(file) + for ((_, reportedInfos) in allReportedInfos.groupBy { Triple(it.start, it.end, it.tag) }) { + val existedInfos = globalMetadataInfoHandler.getExistingMetaInfosForActualMetadata(file, reportedInfos.first()) + for ((reportedInfo, existedInfo) in reportedInfos.zip(existedInfos)) { + matchedExistedInfos += existedInfo + matchedReportedInfos += reportedInfo + if (currentFlag !in reportedInfo.attributes) continue + if (currentFlag in existedInfo.attributes) continue + reportedInfo.attributes.remove(currentFlag) + } + } + + if (allReportedInfos.size != matchedReportedInfos.size) { + for (info in allReportedInfos) { + if (info !in matchedReportedInfos) { + info.attributes.remove(currentFlag) + } + } + } + + val allExistedInfos = globalMetadataInfoHandler.getExistingMetaInfosForFile(file) + if (allExistedInfos.size == matchedExistedInfos.size) return + + val newInfos = allExistedInfos.mapNotNull { + if (it in matchedExistedInfos) return@mapNotNull null + if (currentFlag in it.attributes) return@mapNotNull null + it.copy().apply { + if (otherFlag !in attributes) { + attributes += otherFlag + } + } + } + globalMetadataInfoHandler.addMetadataInfosForFile(file, newInfos) + } +} + +private fun TestServices.withNewInferenceModeEnabled(): Boolean { + return DiagnosticsDirectives.WITH_NEW_INFERENCE in moduleStructure.allDirectives +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/ClassicFrontendAnalysisHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/ClassicFrontendAnalysisHandler.kt new file mode 100644 index 00000000000..3d2be8420f2 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/ClassicFrontendAnalysisHandler.kt @@ -0,0 +1,17 @@ +/* + * 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.test.frontend.classic.handlers + +import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact +import org.jetbrains.kotlin.test.model.FrontendKinds +import org.jetbrains.kotlin.test.model.FrontendOutputHandler +import org.jetbrains.kotlin.test.services.TestServices + +abstract class ClassicFrontendAnalysisHandler( + testServices: TestServices +) : FrontendOutputHandler(testServices, FrontendKinds.ClassicFrontend) + + diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/ClassicMetaInfoUtils.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/ClassicMetaInfoUtils.kt new file mode 100644 index 00000000000..0f4425dc76e --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/ClassicMetaInfoUtils.kt @@ -0,0 +1,13 @@ +/* + * 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.test.frontend.classic.handlers + +import org.jetbrains.kotlin.codeMetaInfo.renderConfigurations.DiagnosticCodeMetaInfoRenderConfiguration + +object ClassicMetaInfoUtils { + val renderDiagnosticNoArgs = DiagnosticCodeMetaInfoRenderConfiguration().apply { renderParams = false } + val renderDiagnosticWithArgs = DiagnosticCodeMetaInfoRenderConfiguration().apply { renderParams = true } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/DeclarationsDumpHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/DeclarationsDumpHandler.kt new file mode 100644 index 00000000000..5593fe9c115 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/DeclarationsDumpHandler.kt @@ -0,0 +1,136 @@ +/* + * 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.test.frontend.classic.handlers + +import org.jetbrains.kotlin.config.LanguageFeature +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor +import org.jetbrains.kotlin.descriptors.ModuleDescriptor +import org.jetbrains.kotlin.descriptors.PackageViewDescriptor +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.name.SpecialNames +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives +import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives +import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact +import org.jetbrains.kotlin.test.model.TestFile +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.isJavaFile +import org.jetbrains.kotlin.test.services.moduleStructure +import org.jetbrains.kotlin.test.util.DescriptorValidator +import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator +import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.RECURSIVE +import org.jetbrains.kotlin.test.utils.MultiModuleInfoDumper +import org.jetbrains.kotlin.test.utils.MultiModuleInfoDumperImpl +import org.jetbrains.kotlin.utils.keysToMap +import java.util.function.Predicate +import java.util.regex.Pattern + +class DeclarationsDumpHandler( + testServices: TestServices +) : ClassicFrontendAnalysisHandler(testServices) { + companion object { + private val NAMES_OF_CHECK_TYPE_HELPER = listOf("checkSubtype", "CheckTypeInv", "_", "checkType").map { Name.identifier(it) } + + private val JAVA_PACKAGE_PATTERN = Pattern.compile("^\\s*package [.\\w\\d]*", Pattern.MULTILINE) + } + + override val directivesContainers: List + get() = listOf(DiagnosticsDirectives) + + private val dumper: MultiModuleInfoDumper = MultiModuleInfoDumperImpl(moduleHeaderTemplate = "// -- Module: <%s> --") + + override fun processAfterAllModules(someAssertionWasFailed: Boolean) { + if (dumper.isEmpty()) return + val resultDump = dumper.generateResultingDump() + val testDataFile = testServices.moduleStructure.originalTestDataFiles.first() + val allDirectives = testServices.moduleStructure.allDirectives + val prefix = when { + DiagnosticsDirectives.NI_EXPECTED_FILE in allDirectives && + testServices.moduleStructure.modules.any { it.languageVersionSettings.supportsFeature(LanguageFeature.NewInference) } -> ".ni" + + JvmEnvironmentConfigurationDirectives.USE_JAVAC in allDirectives + && DiagnosticsDirectives.JAVAC_EXPECTED_FILE in allDirectives -> ".javac" + + else -> "" + } + val expectedFileName = "${testDataFile.nameWithoutExtension}$prefix.txt" + val expectedFile = testDataFile.parentFile.resolve(expectedFileName) + assertions.assertEqualsToFile(expectedFile, resultDump) + } + + override fun processModule(module: TestModule, info: ClassicFrontendOutputArtifact) { + if (DiagnosticsDirectives.SKIP_TXT in module.directives) return + val moduleDescriptor = info.analysisResult.moduleDescriptor + val checkTypeEnabled = AdditionalFilesDirectives.CHECK_TYPE in module.directives + val comparator = RecursiveDescriptorComparator( + createdAffectedPackagesConfiguration(module.files, info.ktFiles, moduleDescriptor, checkTypeEnabled) + ) + val packages = listOf(FqName.ROOT) + val textByPackage = packages.keysToMap { StringBuilder() } + + for ((packageName, packageText) in textByPackage.entries) { + val aPackage = moduleDescriptor.getPackage(packageName) + assertions.assertFalse(aPackage.isEmpty()) + + val actualSerialized = comparator.serializeRecursively(aPackage) + packageText.append(actualSerialized) + } + val allPackagesText = textByPackage.values.joinToString("\n") + dumper.builderForModule(module).appendLine(allPackagesText) + } + + private fun createdAffectedPackagesConfiguration( + testFiles: List, + ktFiles: Map, + moduleDescriptor: ModuleDescriptor, + checkTypeEnabled: Boolean + ): RecursiveDescriptorComparator.Configuration { + val packagesNames = testFiles.mapNotNullTo(mutableSetOf()) { + val ktFile = ktFiles[it] + when { + ktFile != null -> ktFile.packageFqName.pathSegments().firstOrNull() ?: SpecialNames.ROOT_PACKAGE + it.isJavaFile -> getJavaFilePackage(it) + else -> null + } + } + val stepIntoFilter = Predicate { descriptor -> + val module = DescriptorUtils.getContainingModuleOrNull(descriptor) + if (module != moduleDescriptor) return@Predicate false + + if (descriptor is PackageViewDescriptor) { + val fqName = descriptor.fqName + return@Predicate fqName.isRoot || fqName.pathSegments().first() in packagesNames + } + + if (checkTypeEnabled && descriptor.name in NAMES_OF_CHECK_TYPE_HELPER) return@Predicate false + + true + } + return RECURSIVE.filterRecursion(stepIntoFilter) + .withValidationStrategy(DescriptorValidator.ValidationVisitor.errorTypesAllowed()) + .checkFunctionContracts(true) + } + + private fun getJavaFilePackage(testFile: TestFile): Name { + val matcher = JAVA_PACKAGE_PATTERN.matcher(testFile.originalContent) + + if (matcher.find()) { + return testFile.originalContent + .substring(matcher.start(), matcher.end()) + .split(" ") + .last() + .filter { !it.isWhitespace() } + .let { Name.identifier(it.split(".").first()) } + } + + return SpecialNames.ROOT_PACKAGE + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/DiagnosticTestWithJavacSkipConfigurator.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/DiagnosticTestWithJavacSkipConfigurator.kt new file mode 100644 index 00000000000..20b1569feb5 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/DiagnosticTestWithJavacSkipConfigurator.kt @@ -0,0 +1,21 @@ +/* + * 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.test.frontend.classic.handlers + +import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.services.MetaTestConfigurator +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.moduleStructure + +class DiagnosticTestWithJavacSkipConfigurator(testServices: TestServices) : MetaTestConfigurator(testServices) { + override val directives: List + get() = listOf(DiagnosticsDirectives) + + override fun shouldSkipTest(): Boolean { + return DiagnosticsDirectives.SKIP_JAVAC in testServices.moduleStructure.allDirectives + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/FirTestDataConsistencyHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/FirTestDataConsistencyHandler.kt new file mode 100644 index 00000000000..f98051b3ab3 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/classic/handlers/FirTestDataConsistencyHandler.kt @@ -0,0 +1,46 @@ +/* + * 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.test.frontend.classic.handlers + +import org.jetbrains.kotlin.codeMetaInfo.clearTextFromDiagnosticMarkup +import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.model.AfterAnalysisChecker +import org.jetbrains.kotlin.test.runners.AbstractFirDiagnosticTest +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.assertions +import org.jetbrains.kotlin.test.services.moduleStructure +import org.jetbrains.kotlin.test.utils.firTestDataFile +import java.io.File + +class FirTestDataConsistencyHandler(testServices: TestServices) : AfterAnalysisChecker(testServices) { + override val directives: List + get() = listOf(FirDiagnosticsDirectives) + + override fun check(failedAssertions: List) { + val moduleStructure = testServices.moduleStructure + val testData = moduleStructure.originalTestDataFiles.first() + if (testData.extension == "kts") return + if (FirDiagnosticsDirectives.FIR_IDENTICAL in moduleStructure.allDirectives) return + val firTestData = testData.firTestDataFile + if (!firTestData.exists()) { + runFirTestAndGeneratedTestData(testData, firTestData) + return + } + val originalFileContent = clearTextFromDiagnosticMarkup(testData.readText()) + val firFileContent = clearTextFromDiagnosticMarkup(firTestData.readText()) + testServices.assertions.assertEquals(originalFileContent, firFileContent) { + "Original and fir test data aren't identical. " + + "Please, add changes from ${testData.name} to ${firTestData.name}" + } + } + + private fun runFirTestAndGeneratedTestData(testData: File, firTestData: File) { + firTestData.writeText(clearTextFromDiagnosticMarkup(testData.readText())) + val test = object : AbstractFirDiagnosticTest() {} + test.runTest(firTestData.absolutePath) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/Fir2IrResultsConverter.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/Fir2IrResultsConverter.kt new file mode 100644 index 00000000000..d54cda890ba --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/Fir2IrResultsConverter.kt @@ -0,0 +1,96 @@ +/* + * 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.test.frontend.fir + +import org.jetbrains.kotlin.backend.common.phaser.PhaseConfig +import org.jetbrains.kotlin.backend.jvm.JvmGeneratorExtensions +import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory +import org.jetbrains.kotlin.backend.jvm.jvmPhases +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.jvm.compiler.NoScopeRecordCliBindingTrace +import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM +import org.jetbrains.kotlin.codegen.ClassBuilderFactories +import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.container.get +import org.jetbrains.kotlin.fir.backend.jvm.FirJvmBackendClassResolver +import org.jetbrains.kotlin.fir.backend.jvm.FirMetadataSerializer +import org.jetbrains.kotlin.fir.psi +import org.jetbrains.kotlin.ir.descriptors.IrFunctionFactory +import org.jetbrains.kotlin.ir.util.generateTypicalIrProviderList +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.resolve.CompilerEnvironment +import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory +import org.jetbrains.kotlin.test.backend.ir.IrBackendInput +import org.jetbrains.kotlin.test.model.BackendKinds +import org.jetbrains.kotlin.test.model.Frontend2BackendConverter +import org.jetbrains.kotlin.test.model.FrontendKinds +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.compilerConfigurationProvider + +class Fir2IrResultsConverter( + testServices: TestServices +) : Frontend2BackendConverter( + testServices, + FrontendKinds.FIR, + BackendKinds.IrBackend +) { + override fun transform( + module: TestModule, + inputArtifact: FirOutputArtifact + ): IrBackendInput { + val extensions = JvmGeneratorExtensions() + val (irModuleFragment, symbolTable, sourceManager, components) = inputArtifact.firAnalyzerFacade.convertToIr(extensions) + val dummyBindingContext = NoScopeRecordCliBindingTrace().bindingContext + + val compilerConfigurationProvider = testServices.compilerConfigurationProvider + val configuration = compilerConfigurationProvider.getCompilerConfiguration(module) + + val phaseConfig = configuration.get(CLIConfigurationKeys.PHASE_CONFIG) ?: PhaseConfig(jvmPhases) + val codegenFactory = JvmIrCodegenFactory(phaseConfig) + + // TODO: handle fir from light tree + val ktFiles = inputArtifact.firFiles.values.mapNotNull { it.psi as KtFile? } + + // Create and initialize the module and its dependencies + val project = compilerConfigurationProvider.getProject(module) + val container = TopDownAnalyzerFacadeForJVM.createContainer( + project, ktFiles, NoScopeRecordCliBindingTrace(), configuration, + compilerConfigurationProvider.getPackagePartProviderFactory(module), + ::FileBasedDeclarationProviderFactory, CompilerEnvironment, + TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, ktFiles), emptyList() + ) + + val generationState = GenerationState.Builder( + project, ClassBuilderFactories.BINARIES, + container.get(), dummyBindingContext, ktFiles, + configuration + ).codegenFactory( + codegenFactory + ).isIrBackend( + true + ).jvmBackendClassResolver( + FirJvmBackendClassResolver(components) + ).build() + + irModuleFragment.irBuiltins.functionFactory = IrFunctionFactory(irModuleFragment.irBuiltins, symbolTable) + val irProviders = generateTypicalIrProviderList( + irModuleFragment.descriptor, irModuleFragment.irBuiltins, symbolTable, extensions = extensions + ) + + return IrBackendInput( + generationState, + irModuleFragment, + symbolTable, + sourceManager, + phaseConfig, + irProviders, + extensions, + ) { context, irClass, _, serializationBindings, parent -> + FirMetadataSerializer(inputArtifact.session, context, irClass, serializationBindings, components, parent) + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirFailingTestSuppressor.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirFailingTestSuppressor.kt new file mode 100644 index 00000000000..dc8b4b787f0 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirFailingTestSuppressor.kt @@ -0,0 +1,26 @@ +/* + * 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.test.frontend.fir + +import org.jetbrains.kotlin.test.ExceptionFromTestError +import org.jetbrains.kotlin.test.model.AfterAnalysisChecker +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.moduleStructure + +class FirFailingTestSuppressor(testServices: TestServices) : AfterAnalysisChecker(testServices) { + override fun suppressIfNeeded(failedAssertions: List): List { + val testFile = testServices.moduleStructure.originalTestDataFiles.first() + val failFile = testFile.parentFile.resolve("${testFile.nameWithoutExtension}.fail") + val exceptionFromFir = failedAssertions.firstOrNull { it is ExceptionFromTestError } + return when { + failFile.exists() && exceptionFromFir != null -> emptyList() + failFile.exists() && exceptionFromFir == null -> { + failedAssertions + AssertionError("Fail file exists but no exception was throw. Please remove ${failFile.name}") + } + else -> failedAssertions + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirFrontendFacade.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirFrontendFacade.kt new file mode 100644 index 00000000000..643bf466f67 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirFrontendFacade.kt @@ -0,0 +1,99 @@ +/* + * 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.test.frontend.fir + +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiElementFinder +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.ProjectScope +import org.jetbrains.kotlin.asJava.finder.JavaElementFinder +import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider +import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM +import org.jetbrains.kotlin.fir.analysis.FirAnalyzerFacade +import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider +import org.jetbrains.kotlin.fir.session.FirJvmModuleInfo +import org.jetbrains.kotlin.fir.session.FirSessionFactory +import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.services.* + +class FirFrontendFacade( + testServices: TestServices +) : FrontendFacade(testServices, FrontendKinds.FIR) { + override val additionalServices: List + get() = listOf(service(::FirModuleInfoProvider)) + + override fun analyze(module: TestModule): FirOutputArtifact { + val moduleInfoProvider = testServices.firModuleInfoProvider + val compilerConfigurationProvider = testServices.compilerConfigurationProvider + // TODO: add configurable parser + + val project = compilerConfigurationProvider.getProject(module) + + PsiElementFinder.EP.getPoint(project).unregisterExtension(JavaElementFinder::class.java) + + val ktFiles = testServices.sourceFileProvider.getKtFilesForSourceFiles(module.files, project).values + + val sessionProvider = moduleInfoProvider.firSessionProvider + + val languageVersionSettings = module.languageVersionSettings + val builtinsModuleInfo = moduleInfoProvider.builtinsModuleInfoForModule(module) + val packagePartProviderFactory = compilerConfigurationProvider.getPackagePartProviderFactory(module) + + createSessionForBuiltins(builtinsModuleInfo, sessionProvider, project, packagePartProviderFactory) + createSessionForBinaryDependencies(module, sessionProvider, project, packagePartProviderFactory) + + val sourcesScope = TopDownAnalyzerFacadeForJVM.newModuleSearchScope(project, ktFiles) + val sourcesModuleInfo = moduleInfoProvider.convertToFirModuleInfo(module) + val session = FirSessionFactory.createJavaModuleBasedSession( + sourcesModuleInfo, + sessionProvider, + sourcesScope, + project, + languageVersionSettings = languageVersionSettings + ) + + val firAnalyzerFacade = FirAnalyzerFacade(session, languageVersionSettings, ktFiles) + val firFiles = firAnalyzerFacade.runResolution() + val filesMap = firFiles.mapNotNull { firFile -> + val testFile = module.files.firstOrNull { it.name == firFile.name } ?: return@mapNotNull null + testFile to firFile + }.toMap() + + return FirOutputArtifact(session, filesMap, firAnalyzerFacade) + } + + private fun createSessionForBuiltins( + builtinsModuleInfo: FirJvmModuleInfo, + sessionProvider: FirProjectSessionProvider, + project: Project, + packagePartProviderFactory: (GlobalSearchScope) -> JvmPackagePartProvider, + ) { + //For BuiltIns, registered in sessionProvider automatically + val allProjectScope = GlobalSearchScope.allScope(project) + + FirSessionFactory.createLibrarySession( + builtinsModuleInfo, sessionProvider, allProjectScope, project, + packagePartProviderFactory(allProjectScope) + ) + } + + private fun createSessionForBinaryDependencies( + module: TestModule, + sessionProvider: FirProjectSessionProvider, + project: Project, + packagePartProviderFactory: (GlobalSearchScope) -> JvmPackagePartProvider, + ) { + val librariesScope = ProjectScope.getLibrariesScope(project) + val librariesModuleInfo = FirJvmModuleInfo.createForLibraries(module.name) + FirSessionFactory.createLibrarySession( + librariesModuleInfo, + sessionProvider, + librariesScope, + project, + packagePartProviderFactory(librariesScope) + ) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirModuleInfoProvider.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirModuleInfoProvider.kt new file mode 100644 index 00000000000..2b2fd88df6f --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirModuleInfoProvider.kt @@ -0,0 +1,44 @@ +/* + * 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.test.frontend.fir + +import org.jetbrains.kotlin.fir.java.FirProjectSessionProvider +import org.jetbrains.kotlin.fir.session.FirJvmModuleInfo +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.test.services.TestService +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.dependencyProvider +import org.jetbrains.kotlin.test.model.TestModule + +class FirModuleInfoProvider( + private val testServices: TestServices +) : TestService { + val firSessionProvider = FirProjectSessionProvider() + + private val builtinsByModule: MutableMap = mutableMapOf() + private val firModuleInfoByModule: MutableMap = mutableMapOf() + + fun convertToFirModuleInfo(module: TestModule): FirJvmModuleInfo { + return firModuleInfoByModule.getOrPut(module) { + val dependencies = mutableListOf(builtinsModuleInfoForModule(module)) + module.dependencies.mapTo(dependencies) { + convertToFirModuleInfo(testServices.dependencyProvider.getTestModule(it.moduleName)) + } + FirJvmModuleInfo( + module.name, + dependencies + ) + } + } + + fun builtinsModuleInfoForModule(module: TestModule): FirJvmModuleInfo { + return builtinsByModule.getOrPut(module) { + FirJvmModuleInfo(Name.special(""), emptyList()) + } + } +} + +val TestServices.firModuleInfoProvider: FirModuleInfoProvider by TestServices.testServiceAccessor() diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirOutputArtifact.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirOutputArtifact.kt new file mode 100644 index 00000000000..012e0ef2568 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/FirOutputArtifact.kt @@ -0,0 +1,24 @@ +/* + * 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.test.frontend.fir + +import org.jetbrains.kotlin.fir.FirSession +import org.jetbrains.kotlin.fir.analysis.FirAnalyzerFacade +import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.test.model.FrontendKinds +import org.jetbrains.kotlin.test.model.ResultingArtifact +import org.jetbrains.kotlin.test.model.TestFile + +data class FirOutputArtifact( + val session: FirSession, + val allFirFiles: Map, + val firAnalyzerFacade: FirAnalyzerFacade +) : ResultingArtifact.FrontendOutput() { + override val kind: FrontendKinds.FIR + get() = FrontendKinds.FIR + + val firFiles: Map = allFirFiles.filterKeys { !it.isAdditional } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirAnalysisHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirAnalysisHandler.kt new file mode 100644 index 00000000000..a8305f22940 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirAnalysisHandler.kt @@ -0,0 +1,19 @@ +/* + * 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.test.frontend.fir.handlers + +import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact +import org.jetbrains.kotlin.test.model.FrontendKinds +import org.jetbrains.kotlin.test.model.FrontendOutputHandler +import org.jetbrains.kotlin.test.services.TestServices +import java.io.File + +abstract class FirAnalysisHandler( + testServices: TestServices +) : FrontendOutputHandler(testServices, FrontendKinds.FIR) { + protected val File.nameWithoutFirExtension: String + get() = nameWithoutExtension.removeSuffix(".fir") +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirCfgConsistencyHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirCfgConsistencyHandler.kt new file mode 100644 index 00000000000..df1afe15cd5 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirCfgConsistencyHandler.kt @@ -0,0 +1,19 @@ +/* + * 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.test.frontend.fir.handlers + +import org.jetbrains.kotlin.fir.AbstractFirDiagnosticsTest +import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices + +class FirCfgConsistencyHandler(testServices: TestServices) : FirAnalysisHandler(testServices) { + override fun processModule(module: TestModule, info: FirOutputArtifact) { + info.firFiles.values.forEach { it.accept(AbstractFirDiagnosticsTest.CfgConsistencyChecker) } + } + + override fun processAfterAllModules(someAssertionWasFailed: Boolean) {} +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirCfgDumpHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirCfgDumpHandler.kt new file mode 100644 index 00000000000..49afd6fb3e3 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirCfgDumpHandler.kt @@ -0,0 +1,36 @@ +/* + * 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.test.frontend.fir.handlers + +import org.jetbrains.kotlin.fir.resolve.dfa.cfg.FirControlFlowGraphRenderVisitor +import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.moduleStructure + +// TODO: adapt to multifile and multimodule tests +class FirCfgDumpHandler(testServices: TestServices) : FirAnalysisHandler(testServices) { + override val directivesContainers: List + get() = listOf(FirDiagnosticsDirectives) + + private val builder = StringBuilder() + private var alreadyDumped: Boolean = false + + override fun processModule(module: TestModule, info: FirOutputArtifact) { + if (alreadyDumped || FirDiagnosticsDirectives.DUMP_CFG !in module.directives) return + val file = info.firFiles.values.first() + file.accept(FirControlFlowGraphRenderVisitor(builder)) + } + + override fun processAfterAllModules(someAssertionWasFailed: Boolean) { + if (!alreadyDumped) return + val testDataFile = testServices.moduleStructure.originalTestDataFiles.first() + val expectedFile = testDataFile.parentFile.resolve("${testDataFile.nameWithoutFirExtension}.dot") + assertions.assertEqualsToFile(expectedFile, builder.toString()) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDiagnosticCodeMetaInfo.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDiagnosticCodeMetaInfo.kt new file mode 100644 index 00000000000..fbb6af8262d --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDiagnosticCodeMetaInfo.kt @@ -0,0 +1,90 @@ +/* + * 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.test.frontend.fir.handlers + +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.codeMetaInfo.model.CodeMetaInfo +import org.jetbrains.kotlin.codeMetaInfo.renderConfigurations.AbstractCodeMetaInfoRenderConfiguration +import org.jetbrains.kotlin.diagnostics.PositioningStrategy +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDefaultErrorMessages +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnostic +import org.jetbrains.kotlin.fir.analysis.diagnostics.FirDiagnosticRenderer +import org.jetbrains.kotlin.fir.psi + +object FirMetaInfoUtils { + val renderDiagnosticNoArgs = FirDiagnosticCodeMetaRenderConfiguration().apply { renderParams = false } + val renderDiagnosticWithArgs = FirDiagnosticCodeMetaRenderConfiguration().apply { renderParams = true } +} + +class FirDiagnosticCodeMetaInfo( + val diagnostic: FirDiagnostic<*>, + renderConfiguration: FirDiagnosticCodeMetaRenderConfiguration +) : CodeMetaInfo { + // TODO: this implementation is hacky and doesn't support proper ranges for light tree diagnostics + private val textRangeFromClassicDiagnostic: TextRange? = run { + val psi = diagnostic.element.psi ?: return@run null + + @Suppress("UNCHECKED_CAST") + val positioningStrategy = diagnostic.factory.positioningStrategy.psiStrategy as PositioningStrategy + positioningStrategy.mark(psi).first() + } + + override var renderConfiguration: FirDiagnosticCodeMetaRenderConfiguration = renderConfiguration + private set + + override val start: Int + get() = textRangeFromClassicDiagnostic?.startOffset ?: diagnostic.element.startOffset + + override val end: Int + get() = textRangeFromClassicDiagnostic?.endOffset ?: diagnostic.element.endOffset + + override val tag: String + get() = renderConfiguration.getTag(this) + + override val attributes: MutableList = mutableListOf() + + override fun asString(): String = renderConfiguration.asString(this) + + fun replaceRenderConfiguration(renderConfiguration: FirDiagnosticCodeMetaRenderConfiguration) { + this.renderConfiguration = renderConfiguration + } +} + +class FirDiagnosticCodeMetaRenderConfiguration( + val renderSeverity: Boolean = false, +) : AbstractCodeMetaInfoRenderConfiguration(renderParams = false) { + private val crossPlatformLineBreak = """\r?\n""".toRegex() + + override fun asString(codeMetaInfo: CodeMetaInfo): String { + if (codeMetaInfo !is FirDiagnosticCodeMetaInfo) return "" + return (getTag(codeMetaInfo) + + getPlatformsString(codeMetaInfo) + + getParamsString(codeMetaInfo)) + .replace(crossPlatformLineBreak, "") + } + + private fun getParamsString(codeMetaInfo: FirDiagnosticCodeMetaInfo): String { + if (!renderParams) return "" + val params = mutableListOf() + + val diagnostic = codeMetaInfo.diagnostic + + val renderer = FirDefaultErrorMessages.getRendererForDiagnostic(diagnostic) as FirDiagnosticRenderer> + params.add(renderer.render(diagnostic)) + + if (renderSeverity) + params.add("severity='${diagnostic.severity}'") + + params.add(getAdditionalParams(codeMetaInfo)) + + return "(\"${params.filter { it.isNotEmpty() }.joinToString("; ")}\")" + } + + fun getTag(codeMetaInfo: FirDiagnosticCodeMetaInfo): String { + return codeMetaInfo.diagnostic.factory.name + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDiagnosticsHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDiagnosticsHandler.kt new file mode 100644 index 00000000000..5a9b08289b3 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDiagnosticsHandler.kt @@ -0,0 +1,232 @@ +/* + * 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.test.frontend.fir.handlers + +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.checkers.diagnostics.factories.DebugInfoDiagnosticFactory1 +import org.jetbrains.kotlin.checkers.utils.TypeOfCall +import org.jetbrains.kotlin.diagnostics.rendering.Renderers +import org.jetbrains.kotlin.fir.* +import org.jetbrains.kotlin.fir.analysis.diagnostics.* +import org.jetbrains.kotlin.fir.declarations.FirCallableMemberDeclaration +import org.jetbrains.kotlin.fir.declarations.FirFile +import org.jetbrains.kotlin.fir.declarations.FirFunction +import org.jetbrains.kotlin.fir.declarations.FirProperty +import org.jetbrains.kotlin.fir.expressions.FirExpression +import org.jetbrains.kotlin.fir.expressions.FirExpressionWithSmartcast +import org.jetbrains.kotlin.fir.expressions.FirFunctionCall +import org.jetbrains.kotlin.fir.references.FirNamedReference +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference +import org.jetbrains.kotlin.fir.symbols.AbstractFirBasedSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirCallableSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirClassLikeSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirFunctionSymbol +import org.jetbrains.kotlin.fir.types.ConeKotlinType +import org.jetbrains.kotlin.fir.types.FirTypeRef +import org.jetbrains.kotlin.fir.types.coneTypeSafe +import org.jetbrains.kotlin.fir.visitors.FirDefaultVisitorVoid +import org.jetbrains.kotlin.name.FqNameUnsafe +import org.jetbrains.kotlin.resolve.AnalyzingUtils +import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact +import org.jetbrains.kotlin.test.model.TestFile +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.* +import org.jetbrains.kotlin.util.OperatorNameConventions +import org.jetbrains.kotlin.utils.addIfNotNull + +class FirDiagnosticsHandler(testServices: TestServices) : FirAnalysisHandler(testServices) { + companion object { + private val allowedKindsForDebugInfo = setOf( + FirRealSourceElementKind, + FirFakeSourceElementKind.DesugaredCompoundAssignment, + ) + } + + private val globalMetadataInfoHandler: GlobalMetadataInfoHandler + get() = testServices.globalMetadataInfoHandler + + private val diagnosticsService: DiagnosticsService + get() = testServices.diagnosticsService + + override val directivesContainers: List = + listOf(DiagnosticsDirectives) + + override val additionalServices: List = + listOf(service(::DiagnosticsService)) + + override fun processModule(module: TestModule, info: FirOutputArtifact) { + val diagnosticsPerFile = info.firAnalyzerFacade.runCheckers() + + for (file in module.files) { + val firFile = info.firFiles[file] ?: continue + val diagnostics = diagnosticsPerFile[firFile] ?: continue + val diagnosticsMetadataInfos = diagnostics.mapNotNull { diagnostic -> + if (!diagnosticsService.shouldRenderDiagnostic(module, diagnostic.factory.name)) return@mapNotNull null + // SYNTAX errors will be reported later + if (diagnostic.factory == FirErrors.SYNTAX) return@mapNotNull null + if (!diagnostic.isValid) return@mapNotNull null + diagnostic.toMetaInfo(file) + } + globalMetadataInfoHandler.addMetadataInfosForFile(file, diagnosticsMetadataInfos) + collectSyntaxDiagnostics(file, firFile) + collectDebugInfoDiagnostics(file, firFile) + } + } + + private fun FirDiagnostic<*>.toMetaInfo(file: TestFile, forceRenderArguments: Boolean = false): FirDiagnosticCodeMetaInfo { + val metaInfo = FirDiagnosticCodeMetaInfo(this, FirMetaInfoUtils.renderDiagnosticNoArgs) + val shouldRenderArguments = forceRenderArguments || globalMetadataInfoHandler.getExistingMetaInfosForActualMetadata(file, metaInfo) + .any { it.description != null } + if (shouldRenderArguments) { + metaInfo.replaceRenderConfiguration(FirMetaInfoUtils.renderDiagnosticWithArgs) + } + return metaInfo + } + + private fun collectSyntaxDiagnostics(testFile: TestFile, firFile: FirFile) { + // TODO: support in light tree + val psiFile = firFile.psi ?: return + val metaInfos = AnalyzingUtils.getSyntaxErrorRanges(psiFile).map { + FirErrors.SYNTAX.on(FirRealPsiSourceElement(it)).toMetaInfo(testFile) + } + globalMetadataInfoHandler.addMetadataInfosForFile(testFile, metaInfos) + } + + private fun collectDebugInfoDiagnostics( + testFile: TestFile, + firFile: FirFile, + ) { + val result = mutableListOf>() + val diagnosedRangesToDiagnosticNames = globalMetadataInfoHandler.getExistingMetaInfosForFile(testFile).groupBy( + keySelector = { it.start..it.end }, + valueTransform = { it.tag } + ).mapValues { (_, it) -> it.toSet() } + object : FirDefaultVisitorVoid() { + override fun visitElement(element: FirElement) { + if (element is FirExpression) { + result.addIfNotNull( + createExpressionTypeDiagnosticIfExpected( + element, diagnosedRangesToDiagnosticNames + ) + ) + } + + element.acceptChildren(this) + } + + override fun visitFunctionCall(functionCall: FirFunctionCall) { + result.addIfNotNull( + createCallDiagnosticIfExpected(functionCall, functionCall.calleeReference, diagnosedRangesToDiagnosticNames) + ) + + super.visitFunctionCall(functionCall) + } + }.let(firFile::accept) + globalMetadataInfoHandler.addMetadataInfosForFile(testFile, result.map { it.toMetaInfo(testFile, forceRenderArguments = true) }) + } + + fun createExpressionTypeDiagnosticIfExpected( + element: FirExpression, + diagnosedRangesToDiagnosticNames: Map> + ): FirDiagnosticWithParameters1? = + DebugInfoDiagnosticFactory1.EXPRESSION_TYPE.createDebugInfoDiagnostic(element, diagnosedRangesToDiagnosticNames) { + element.typeRef.renderAsString((element as? FirExpressionWithSmartcast)?.originalType) + } + + private fun FirTypeRef.renderAsString(originalTypeRef: FirTypeRef?): String { + val type = coneTypeSafe() ?: return "Type is unknown" + val rendered = type.renderForDebugInfo() + val originalTypeRendered = originalTypeRef?.coneTypeSafe()?.renderForDebugInfo() ?: return rendered + + return "$rendered & $originalTypeRendered" + } + + private fun createCallDiagnosticIfExpected( + element: FirElement, + reference: FirNamedReference, + diagnosedRangesToDiagnosticNames: Map> + ): FirDiagnosticWithParameters1? = + DebugInfoDiagnosticFactory1.CALL.createDebugInfoDiagnostic(element, diagnosedRangesToDiagnosticNames) { + val resolvedSymbol = (reference as? FirResolvedNamedReference)?.resolvedSymbol + val fqName = resolvedSymbol?.fqNameUnsafe() + Renderers.renderCallInfo(fqName, getTypeOfCall(reference, resolvedSymbol)) + } + + private inline fun DebugInfoDiagnosticFactory1.createDebugInfoDiagnostic( + element: FirElement, + diagnosedRangesToDiagnosticNames: Map>, + argument: () -> String, + ): FirDiagnosticWithParameters1? { + val sourceElement = element.source ?: return null + if (sourceElement.kind !in allowedKindsForDebugInfo) return null + // Lambda argument is always (?) duplicated by function literal + // Block expression is always (?) duplicated by single block expression + if (sourceElement.elementType == KtNodeTypes.LAMBDA_ARGUMENT || sourceElement.elementType == KtNodeTypes.BLOCK) return null + if (diagnosedRangesToDiagnosticNames[sourceElement.startOffset..sourceElement.endOffset]?.contains(this.name) != true) return null + + val argumentText = argument() + return when (sourceElement) { + is FirPsiSourceElement<*> -> FirPsiDiagnosticWithParameters1( + sourceElement, + argumentText, + severity, + FirDiagnosticFactory1( + name, + severity, + ) + ) + is FirLightSourceElement -> FirLightDiagnosticWithParameters1( + sourceElement, + argumentText, + severity, + FirDiagnosticFactory1( + name, + severity + ) + ) + } + } + + private fun getTypeOfCall( + reference: FirNamedReference, + resolvedSymbol: AbstractFirBasedSymbol<*>? + ): String { + if (resolvedSymbol == null) return TypeOfCall.UNRESOLVED.nameToRender + + if ((resolvedSymbol as? FirFunctionSymbol)?.callableId?.callableName == OperatorNameConventions.INVOKE + && reference.name != OperatorNameConventions.INVOKE + ) { + return TypeOfCall.VARIABLE_THROUGH_INVOKE.nameToRender + } + + return when (val fir = resolvedSymbol.fir) { + is FirProperty -> { + TypeOfCall.PROPERTY_GETTER.nameToRender + } + is FirFunction<*> -> buildString { + if (fir is FirCallableMemberDeclaration<*>) { + if (fir.status.isInline) append("inline ") + if (fir.status.isInfix) append("infix ") + if (fir.status.isOperator) append("operator ") + if (fir.receiverTypeRef != null) append("extension ") + } + append(TypeOfCall.FUNCTION.nameToRender) + } + else -> TypeOfCall.OTHER.nameToRender + } + } + + private fun AbstractFirBasedSymbol<*>.fqNameUnsafe(): FqNameUnsafe? = when (this) { + is FirClassLikeSymbol<*> -> classId.asSingleFqName().toUnsafe() + is FirCallableSymbol<*> -> callableId.asFqNameForDebugInfo().toUnsafe() + else -> null + } + + override fun processAfterAllModules(someAssertionWasFailed: Boolean) {} +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDumpHandler.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDumpHandler.kt new file mode 100644 index 00000000000..ad3cb818713 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirDumpHandler.kt @@ -0,0 +1,41 @@ +/* + * 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.test.frontend.fir.handlers + +import org.jetbrains.kotlin.fir.render +import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.moduleStructure +import org.jetbrains.kotlin.test.utils.MultiModuleInfoDumper +import org.jetbrains.kotlin.test.utils.MultiModuleInfoDumperImpl + +class FirDumpHandler( + testServices: TestServices +) : FirAnalysisHandler(testServices) { + private val dumper: MultiModuleInfoDumper = MultiModuleInfoDumperImpl() + + override val directivesContainers: List + get() = listOf(FirDiagnosticsDirectives) + + override fun processModule(module: TestModule, info: FirOutputArtifact) { + if (FirDiagnosticsDirectives.FIR_DUMP !in module.directives) return + val builderForModule = dumper.builderForModule(module) + val firFiles = info.firFiles + firFiles.values.forEach { builderForModule.append(it.render()) } + } + + override fun processAfterAllModules(someAssertionWasFailed: Boolean) { + if (dumper.isEmpty()) return + // TODO: change according to multiple testdata files + val testDataFile = testServices.moduleStructure.originalTestDataFiles.first() + val expectedFile = testDataFile.parentFile.resolve("${testDataFile.nameWithoutFirExtension}.fir.txt") + val actualText = dumper.generateResultingDump() + assertions.assertEqualsToFile(expectedFile, actualText, message = { "Content is not equal" }) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirIdenticalChecker.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirIdenticalChecker.kt new file mode 100644 index 00000000000..1416eaedd9f --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/frontend/fir/handlers/FirIdenticalChecker.kt @@ -0,0 +1,53 @@ +/* + * 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.test.frontend.fir.handlers + +import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives +import org.jetbrains.kotlin.test.model.AfterAnalysisChecker +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.assertions +import org.jetbrains.kotlin.test.services.moduleStructure +import org.jetbrains.kotlin.test.utils.firTestDataFile +import org.jetbrains.kotlin.test.utils.isFirTestData +import org.jetbrains.kotlin.test.utils.originalTestDataFile +import java.io.File + +class FirIdenticalChecker(testServices: TestServices) : AfterAnalysisChecker(testServices) { + override fun check(failedAssertions: List) { + if (failedAssertions.isNotEmpty()) return + val file = testServices.moduleStructure.originalTestDataFiles.first() + if (file.isFirTestData) { + addDirectiveToClassicFile(file) + } else { + removeFirFileIfExist(file) + } + } + + private fun addDirectiveToClassicFile(firFile: File) { + val classicFile = firFile.originalTestDataFile + val classicFileContent = classicFile.readText() + val firFileContent = firFile.readText() + if (classicFileContent == firFileContent) { + classicFile.writer().use { + it.appendLine("// ${FirDiagnosticsDirectives.FIR_IDENTICAL.name}") + it.append(classicFileContent) + } + firFile.delete() + testServices.assertions.fail { + """ + Dumps via FIR & via old FE are the same. + Deleted .fir.txt dump, added // FIR_IDENTICAL to test source + Please re-run the test now + """.trimIndent() + } + } + } + + private fun removeFirFileIfExist(classicFile: File) { + val firFile = classicFile.firTestDataFile + firFile.delete() + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/impl/TestConfigurationImpl.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/impl/TestConfigurationImpl.kt new file mode 100644 index 00000000000..ea840bbe034 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/impl/TestConfigurationImpl.kt @@ -0,0 +1,151 @@ +/* + * 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.test.impl + +import com.intellij.openapi.Disposable +import org.jetbrains.kotlin.test.Constructor +import org.jetbrains.kotlin.test.TestConfiguration +import org.jetbrains.kotlin.test.directives.model.ComposedDirectivesContainer +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives +import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.services.* +import org.jetbrains.kotlin.test.services.impl.ModuleStructureExtractorImpl +import org.jetbrains.kotlin.test.utils.TestDisposable + +class TestConfigurationImpl( + defaultsProvider: DefaultsProvider, + assertions: Assertions, + + facades: List>>, + + analysisHandlers: List>>, + + sourcePreprocessors: List>, + additionalMetaInfoProcessors: List>, + environmentConfigurators: List>, + + additionalSourceProviders: List>, + metaTestConfigurators: List>, + afterAnalysisCheckers: List>, + + override val metaInfoHandlerEnabled: Boolean, + + directives: List, + override val defaultRegisteredDirectives: RegisteredDirectives +) : TestConfiguration() { + override val rootDisposable: Disposable = TestDisposable() + override val testServices: TestServices = TestServices() + + private val allDirectives = directives.toMutableSet() + override val directives: DirectivesContainer by lazy { + when (allDirectives.size) { + 0 -> DirectivesContainer.Empty + 1 -> allDirectives.single() + else -> ComposedDirectivesContainer(allDirectives) + } + } + + override val moduleStructureExtractor: ModuleStructureExtractor = ModuleStructureExtractorImpl( + testServices, + additionalSourceProviders.map { it.invoke(testServices) }.also { + it.flatMapTo(allDirectives) { provider -> provider.directives } + } + ) + + override val metaTestConfigurators: List = metaTestConfigurators.map { + it.invoke(testServices).also { configurator -> + allDirectives += configurator.directives + } + } + + override val afterAnalysisCheckers: List = afterAnalysisCheckers.map { + it.invoke(testServices).also { checker -> + allDirectives += checker.directives + } + } + + init { + testServices.apply { + @OptIn(ExperimentalStdlibApi::class) + val sourceFilePreprocessors = sourcePreprocessors.map { it.invoke(this@apply) } + val sourceFileProvider = SourceFileProviderImpl(sourceFilePreprocessors) + register(SourceFileProvider::class, sourceFileProvider) + + val configurators = environmentConfigurators.map { it.invoke(this) } + configurators.flatMapTo(allDirectives) { it.directivesContainers } + for (configurator in configurators) { + configurator.additionalServices.forEach { register(it) } + } + val environmentProvider = CompilerConfigurationProviderImpl( + rootDisposable, + configurators + ) + register(CompilerConfigurationProvider::class, environmentProvider) + + register(Assertions::class, assertions) + register(DefaultsProvider::class, defaultsProvider) + + register(DefaultRegisteredDirectivesProvider::class, DefaultRegisteredDirectivesProvider(defaultRegisteredDirectives)) + + val metaInfoProcessors = additionalMetaInfoProcessors.map { it.invoke(this) } + register(GlobalMetadataInfoHandler::class, GlobalMetadataInfoHandler(this, metaInfoProcessors)) + } + } + + private val facades: Map, Map, AbstractTestFacade<*, *>>> = + facades + .map { it.invoke(testServices) } + .groupBy { it.inputKind } + .mapValues { (frontendKind, converters) -> + converters.groupBy { it.outputKind }.mapValues { + it.value.singleOrNull() ?: manyFacadesError("converters", "$frontendKind -> ${it.key}") + } + } + + + private val analysisHandlers: Map, List>> = + analysisHandlers.map { it.invoke(testServices).also(this::registerDirectivesAndServices) } + .groupBy { it.artifactKind } + .withDefault { emptyList() } + + private fun manyFacadesError(name: String, kinds: String): Nothing { + error("Too many $name passed for $kinds configuration") + } + + private fun registerDirectivesAndServices(handler: AnalysisHandler<*>) { + allDirectives += handler.directivesContainers + testServices.register(handler.additionalServices) + } + + init { + testServices.apply { + this@TestConfigurationImpl.facades.values.forEach { it.values.forEach { facade -> register(facade.additionalServices) } } + } + } + + override fun , O : ResultingArtifact> getFacade( + inputKind: TestArtifactKind, + outputKind: TestArtifactKind + ): AbstractTestFacade { + @Suppress("UNCHECKED_CAST") + return facades[inputKind]?.get(outputKind) as AbstractTestFacade? + ?: facadeNotFoundError(inputKind, outputKind) + } + + private fun facadeNotFoundError(from: Any, to: Any): Nothing { + error("Facade for converting '$from' to '$to' not found") + } + + override fun > getHandlers(artifactKind: TestArtifactKind): List> { + @Suppress("UNCHECKED_CAST") + return analysisHandlers.getValue(artifactKind) as List> + } + + override fun getAllHandlers(): List> { + return analysisHandlers.values.flatten() + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt new file mode 100644 index 00000000000..276ff31fe7d --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/ResultingArtifacts.kt @@ -0,0 +1,30 @@ +/* + * 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.test.model + +import org.jetbrains.kotlin.codegen.ClassFileFactory + +object BinaryArtifacts { + class Jvm(val classFileFactory: ClassFileFactory) : ResultingArtifact.Binary() { + override val kind: BinaryKind + get() = ArtifactKinds.Jvm + } + + class Js : ResultingArtifact.Binary() { + override val kind: BinaryKind + get() = ArtifactKinds.Js + } + + class Native : ResultingArtifact.Binary() { + override val kind: BinaryKind + get() = ArtifactKinds.Native + } + + class KLib : ResultingArtifact.Binary() { + override val kind: BinaryKind + get() = ArtifactKinds.KLib + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/TestArtifactKinds.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/TestArtifactKinds.kt new file mode 100644 index 00000000000..72c0974413e --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/model/TestArtifactKinds.kt @@ -0,0 +1,54 @@ +/* + * 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.test.model + +import org.jetbrains.kotlin.test.backend.classic.ClassicBackendInput +import org.jetbrains.kotlin.test.backend.ir.IrBackendInput +import org.jetbrains.kotlin.test.frontend.classic.ClassicFrontendOutputArtifact +import org.jetbrains.kotlin.test.frontend.fir.FirOutputArtifact + +object FrontendKinds { + object ClassicFrontend : FrontendKind("ClassicFrontend") + object FIR : FrontendKind("FIR") + + fun fromString(string: String): FrontendKind<*>? { + return when (string) { + "ClassicFrontend" -> ClassicFrontend + "FIR" -> FIR + else -> null + } + } +} + +object BackendKinds { + object ClassicBackend : BackendKind("ClassicBackend") + object IrBackend : BackendKind("IrBackend") + + fun fromString(string: String): BackendKind<*>? { + return when (string) { + "ClassicBackend" -> ClassicBackend + "IrBackend" -> IrBackend + else -> null + } + } +} + +object ArtifactKinds { + object Jvm : BinaryKind("JVM") + object Js : BinaryKind("JS") + object Native : BinaryKind("Native") + object KLib : BinaryKind("KLib") + + fun fromString(string: String): BinaryKind<*>? { + return when (string) { + "Jvm" -> Jvm + "Js" -> Js + "Native" -> Native + "KLib" -> KLib + else -> null + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/preprocessors/MetaInfosCleanupPreprocessor.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/preprocessors/MetaInfosCleanupPreprocessor.kt new file mode 100644 index 00000000000..55ffda76bb3 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/preprocessors/MetaInfosCleanupPreprocessor.kt @@ -0,0 +1,17 @@ +/* + * 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.test.preprocessors + +import org.jetbrains.kotlin.codeMetaInfo.clearTextFromDiagnosticMarkup +import org.jetbrains.kotlin.test.model.TestFile +import org.jetbrains.kotlin.test.services.SourceFilePreprocessor +import org.jetbrains.kotlin.test.services.TestServices + +class MetaInfosCleanupPreprocessor(testServices: TestServices) : SourceFilePreprocessor(testServices) { + override fun process(file: TestFile, content: String): String { + return clearTextFromDiagnosticMarkup(content) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/AdditionalDiagnosticsSourceFilesProvider.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/AdditionalDiagnosticsSourceFilesProvider.kt new file mode 100644 index 00000000000..cf7953201e4 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/AdditionalDiagnosticsSourceFilesProvider.kt @@ -0,0 +1,32 @@ +/* + * 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.test.services + +import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives +import org.jetbrains.kotlin.test.model.TestFile +import org.jetbrains.kotlin.test.model.TestModule +import java.io.File + +class AdditionalDiagnosticsSourceFilesProvider(testServices: TestServices) : AdditionalSourceProvider(testServices) { + companion object { + private const val HELPERS_PATH = "./compiler/testData/diagnostics/helpers" + private const val CHECK_TYPE_PATH = "$HELPERS_PATH/types/checkType.kt" + } + + override val directives: List = + listOf(AdditionalFilesDirectives) + + @OptIn(ExperimentalStdlibApi::class) + override fun produceAdditionalFiles(globalDirectives: RegisteredDirectives, module: TestModule): List { + return buildList { + if (containsDirective(globalDirectives, module, AdditionalFilesDirectives.CHECK_TYPE)) { + add(File(CHECK_TYPE_PATH).toTestFile()) + } + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/CompilerConfigurationProvider.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/CompilerConfigurationProvider.kt new file mode 100644 index 00000000000..360a5d48b32 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/CompilerConfigurationProvider.kt @@ -0,0 +1,100 @@ +/* + * 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.test.services + +import com.intellij.mock.MockProject +import com.intellij.openapi.Disposable +import com.intellij.openapi.project.Project +import com.intellij.psi.search.GlobalSearchScope +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSourceLocation +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles +import org.jetbrains.kotlin.cli.jvm.compiler.JvmPackagePartProvider +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment +import org.jetbrains.kotlin.config.CommonConfigurationKeys +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.CompilerConfigurationKey +import org.jetbrains.kotlin.config.languageVersionSettings +import org.jetbrains.kotlin.psi.KtFile +import org.jetbrains.kotlin.test.model.TestFile +import org.jetbrains.kotlin.test.model.TestModule +import java.io.File + +abstract class CompilerConfigurationProvider : TestService { + abstract val testRootDisposable: Disposable + + protected abstract fun getKotlinCoreEnvironment(module: TestModule): KotlinCoreEnvironment + + fun getProject(module: TestModule): Project { + return getKotlinCoreEnvironment(module).project + } + + fun getPackagePartProviderFactory(module: TestModule): (GlobalSearchScope) -> JvmPackagePartProvider { + return getKotlinCoreEnvironment(module)::createPackagePartProvider + } + + fun getCompilerConfiguration(module: TestModule): CompilerConfiguration { + return getKotlinCoreEnvironment(module).configuration + } + + fun registerJavacForModule(module: TestModule, ktFiles: List, mockJdk: File?) { + val environment = getKotlinCoreEnvironment(module) + val bootClasspath = mockJdk?.let { listOf(it) } + environment.registerJavac(kotlinFiles = ktFiles, bootClasspath = bootClasspath) + } +} + +val TestServices.compilerConfigurationProvider: CompilerConfigurationProvider by TestServices.testServiceAccessor() + +class CompilerConfigurationProviderImpl( + override val testRootDisposable: Disposable, + val configurators: List +) : CompilerConfigurationProvider() { + private val cache: MutableMap = mutableMapOf() + + override fun getKotlinCoreEnvironment(module: TestModule): KotlinCoreEnvironment { + return cache.getOrPut(module) { + val projectEnv = KotlinCoreEnvironment.createProjectEnvironmentForTests(testRootDisposable, CompilerConfiguration()) + KotlinCoreEnvironment.createForTests( + projectEnv, + createCompilerConfiguration(module, projectEnv.project), + EnvironmentConfigFiles.JVM_CONFIG_FILES + ) + } + } + + private fun createCompilerConfiguration(module: TestModule, project: MockProject): CompilerConfiguration { + val configuration = CompilerConfiguration() + configuration[CommonConfigurationKeys.MODULE_NAME] = module.name + + configuration[CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY] = object : MessageCollector { + override fun clear() {} + + override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageSourceLocation?) { + if (severity == CompilerMessageSeverity.ERROR) { + val prefix = if (location == null) "" else "(" + location.path + ":" + location.line + ":" + location.column + ") " + throw AssertionError(prefix + message) + } + } + + override fun hasErrors(): Boolean = false + } + configuration.languageVersionSettings = module.languageVersionSettings + + configurators.forEach { it.configureCompilerConfiguration(configuration, module, project) } + + return configuration + } + + private operator fun CompilerConfiguration.set(key: CompilerConfigurationKey, value: T) { + put(key, value) + } +} + +val TestModule.javaFiles: List + get() = files.filter { it.isJavaFile } diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/CoroutineHelpersSourceFilesProvider.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/CoroutineHelpersSourceFilesProvider.kt new file mode 100644 index 00000000000..a02da496757 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/CoroutineHelpersSourceFilesProvider.kt @@ -0,0 +1,42 @@ +/* + * 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.test.services + +import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives +import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives.CHECK_STATE_MACHINE +import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives.CHECK_TAIL_CALL_OPTIMIZATION +import org.jetbrains.kotlin.test.directives.AdditionalFilesDirectives.WITH_COROUTINES +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives +import org.jetbrains.kotlin.test.model.TestFile +import org.jetbrains.kotlin.test.model.TestModule +import java.io.File + +class CoroutineHelpersSourceFilesProvider(testServices: TestServices) : AdditionalSourceProvider(testServices) { + companion object { + private const val HELPERS_PATH = "./compiler/testData/diagnostics/helpers/coroutines" + private const val COROUTINE_HELPERS_PATH = "$HELPERS_PATH/CoroutineHelpers.kt" + private const val STATE_MACHINE_CHECKER_PATH = "$HELPERS_PATH/StateMachineChecker.kt" + private const val TAIL_CALL_OPTIMIZATION_CHECKER_PATH = "$HELPERS_PATH/TailCallOptimizationChecker.kt" + } + + override val directives: List = + listOf(AdditionalFilesDirectives) + + @OptIn(ExperimentalStdlibApi::class) + override fun produceAdditionalFiles(globalDirectives: RegisteredDirectives, module: TestModule): List { + if (WITH_COROUTINES !in module.directives) return emptyList() + return buildList { + add(File(COROUTINE_HELPERS_PATH).toTestFile()) + if (CHECK_STATE_MACHINE in module.directives) { + add(File(STATE_MACHINE_CHECKER_PATH).toTestFile()) + } + if (CHECK_TAIL_CALL_OPTIMIZATION in module.directives) { + add(File(TAIL_CALL_OPTIMIZATION_CHECKER_PATH).toTestFile()) + } + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/DiagnosticsService.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/DiagnosticsService.kt new file mode 100644 index 00000000000..981653b7ac5 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/DiagnosticsService.kt @@ -0,0 +1,49 @@ +/* + * 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.test.services + +import org.jetbrains.kotlin.test.directives.DiagnosticsDirectives +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.util.* + +class DiagnosticsService(val testServices: TestServices) : TestService { + private val conditionsPerModule: MutableMap> = mutableMapOf() + private val globalDefinedDiagnostics by lazy { + testServices.moduleStructure.allDirectives[DiagnosticsDirectives.DIAGNOSTICS] + } + + fun shouldRenderDiagnostic(module: TestModule, name: String): Boolean { + val condition = conditionsPerModule.getOrPut(module) { + computeDiagnosticConditionForModule(module) + } + return condition(name) + } + + private fun computeDiagnosticConditionForModule(module: TestModule): Condition { + val diagnosticsInDirective = module.directives[DiagnosticsDirectives.DIAGNOSTICS] + globalDefinedDiagnostics + val enabledNames = mutableSetOf() + val disabledNames = mutableSetOf() + for (diagnosticInDirective in diagnosticsInDirective) { + val enabled = when { + diagnosticInDirective.startsWith("+") -> true + diagnosticInDirective.startsWith("-") -> false + else -> error("Incorrect diagnostics directive syntax. See reference:\n${DiagnosticsDirectives.DIAGNOSTICS.description}") + } + val name = diagnosticInDirective.substring(1) + val collection = if (enabled) enabledNames else disabledNames + collection += name + } + if (disabledNames.isEmpty()) return Conditions.alwaysTrue() + var condition = !Conditions.oneOf(disabledNames) + if (enabledNames.isNotEmpty()) { + condition = condition or Conditions.oneOf(enabledNames) + } + return condition.cached() + } + +} + +val TestServices.diagnosticsService: DiagnosticsService by TestServices.testServiceAccessor() diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/JUnit5Assertions.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/JUnit5Assertions.kt new file mode 100644 index 00000000000..670e65e2815 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/JUnit5Assertions.kt @@ -0,0 +1,62 @@ +/* + * 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.test.services + +import com.intellij.rt.execution.junit.FileComparisonFailure +import org.jetbrains.kotlin.test.util.convertLineSeparators +import org.jetbrains.kotlin.test.util.trimTrailingWhitespacesAndAddNewlineAtEOF +import org.jetbrains.kotlin.utils.rethrow +import org.junit.jupiter.api.function.Executable +import java.io.File +import java.io.IOException +import org.junit.jupiter.api.Assertions as JUnit5PlatformAssertions + +object JUnit5Assertions : Assertions() { + override fun assertEqualsToFile(expectedFile: File, actual: String, sanitizer: (String) -> String, message: () -> String) { + try { + val actualText = actual.trim { it <= ' ' }.convertLineSeparators().trimTrailingWhitespacesAndAddNewlineAtEOF() + if (!expectedFile.exists()) { + expectedFile.writeText(actualText) + org.junit.jupiter.api.fail("Expected data file did not exist. Generating: $expectedFile") + } + val expected = expectedFile.readText().convertLineSeparators() + val expectedText = expected.trim { it <= ' ' }.trimTrailingWhitespacesAndAddNewlineAtEOF() + if (sanitizer.invoke(expectedText) != sanitizer.invoke(actualText)) { + throw FileComparisonFailure( + "${message()}: ${expectedFile.name}", + expected, actual, expectedFile.absolutePath + ) + } + } catch (e: IOException) { + throw rethrow(e) + } + } + + override fun assertEquals(expected: Any?, actual: Any?, message: (() -> String)?) { + JUnit5PlatformAssertions.assertEquals(expected, actual, message?.invoke()) + } + + override fun assertNotEquals(expected: Any?, actual: Any?, message: (() -> String)?) { + JUnit5PlatformAssertions.assertNotEquals(expected, actual, message?.invoke()) + } + + override fun assertTrue(value: Boolean, message: (() -> String)?) { + JUnit5PlatformAssertions.assertTrue(value, message?.invoke()) + } + + override fun assertFalse(value: Boolean, message: (() -> String)?) { + JUnit5PlatformAssertions.assertFalse(value, message?.invoke()) + } + + override fun assertAll(exceptions: List) { + exceptions.singleOrNull()?.let { throw it } + JUnit5PlatformAssertions.assertAll(exceptions.map { Executable { throw it } }) + } + + override fun fail(message: () -> String): Nothing { + org.junit.jupiter.api.fail(message) + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt new file mode 100644 index 00000000000..b7f0929e65f --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JsEnvironmentConfigurator.kt @@ -0,0 +1,36 @@ +/* + * 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.test.services.configuration + +import com.intellij.mock.MockProject +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.js.config.JSConfigurationKeys +import org.jetbrains.kotlin.js.config.JsConfig +import org.jetbrains.kotlin.serialization.js.ModuleKind +import org.jetbrains.kotlin.test.directives.JsEnvironmentConfigurationDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.EnvironmentConfigurator +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.util.joinToArrayString + +class JsEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) { + override val directivesContainers: List + get() = listOf(JsEnvironmentConfigurationDirectives) + + override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule, project: MockProject) { + val moduleKinds = module.directives[JsEnvironmentConfigurationDirectives.MODULE_KIND] + val moduleKind = when (moduleKinds.size) { + 0 -> ModuleKind.PLAIN + 1 -> moduleKinds.single() + else -> error("Too many module kinds passed ${moduleKinds.joinToArrayString()}") + } + configuration.put(JSConfigurationKeys.MODULE_KIND, moduleKind) + + configuration.put(JSConfigurationKeys.LIBRARIES, JsConfig.JS_STDLIB) + } +} + diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JvmEnvironmentConfigurator.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JvmEnvironmentConfigurator.kt new file mode 100644 index 00000000000..8fa8ed5af84 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/JvmEnvironmentConfigurator.kt @@ -0,0 +1,162 @@ +/* + * 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.test.services.configuration + +import com.intellij.mock.MockProject +import com.intellij.openapi.util.SystemInfo +import org.jetbrains.kotlin.cli.jvm.config.addJavaSourceRoot +import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoot +import org.jetbrains.kotlin.cli.jvm.config.addJvmClasspathRoots +import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.config.JVMConfigurationKeys +import org.jetbrains.kotlin.platform.jvm.JvmPlatforms +import org.jetbrains.kotlin.test.ConfigurationKind +import org.jetbrains.kotlin.test.TestJdkKind +import org.jetbrains.kotlin.test.directives.JvmEnvironmentConfigurationDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives +import org.jetbrains.kotlin.test.model.BackendKinds +import org.jetbrains.kotlin.test.model.DependencyKind +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.* +import org.jetbrains.kotlin.test.services.jvm.CompiledJarManager +import org.jetbrains.kotlin.test.services.jvm.compiledJarManager +import org.jetbrains.kotlin.test.util.KtTestUtil +import org.jetbrains.kotlin.test.util.joinToArrayString +import java.io.File + +class JvmEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) { + override val directivesContainers: List + get() = listOf(JvmEnvironmentConfigurationDirectives) + + override val additionalServices: List + get() = listOf(service(::CompiledJarManager)) + + override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule, project: MockProject) { + if (module.targetPlatform !in JvmPlatforms.allJvmPlatforms) return + val registeredDirectives = module.allRegisteredDirectives + val targets = registeredDirectives[JvmEnvironmentConfigurationDirectives.JVM_TARGET] + when (targets.size) { + 0 -> {} + 1 -> configuration.put(JVMConfigurationKeys.JVM_TARGET, targets.single()) + else -> error("Too many jvm targets passed: ${targets.joinToArrayString()}") + } + + when (extractJdkKind(registeredDirectives)) { + TestJdkKind.MOCK_JDK -> { + configuration.addJvmClasspathRoot(KtTestUtil.findMockJdkRtJar()) + configuration.put(JVMConfigurationKeys.NO_JDK, true) + } + TestJdkKind.MODIFIED_MOCK_JDK -> { + configuration.addJvmClasspathRoot(KtTestUtil.findMockJdkRtModified()) + configuration.put(JVMConfigurationKeys.NO_JDK, true) + } + TestJdkKind.FULL_JDK_6 -> { + val jdk6 = System.getenv("JDK_16") ?: error("Environment variable JDK_16 is not set") + configuration.put(JVMConfigurationKeys.JDK_HOME, File(jdk6)) + } + TestJdkKind.FULL_JDK_9 -> { + configuration.put(JVMConfigurationKeys.JDK_HOME, KtTestUtil.getJdk9Home()) + } + TestJdkKind.FULL_JDK -> { + if (SystemInfo.IS_AT_LEAST_JAVA9) { + configuration.put(JVMConfigurationKeys.JDK_HOME, File(System.getProperty("java.home"))) + } + } + TestJdkKind.ANDROID_API -> { + configuration.addJvmClasspathRoot(KtTestUtil.findAndroidApiJar()) + configuration.put(JVMConfigurationKeys.NO_JDK, true) + } + } + + val configurationKind = extractConfigurationKind(registeredDirectives) + + if (configurationKind.withRuntime) { + configuration.addJvmClasspathRoot(ForTestCompileRuntime.runtimeJarForTests()) + configuration.addJvmClasspathRoot(ForTestCompileRuntime.scriptRuntimeJarForTests()) + configuration.addJvmClasspathRoot(ForTestCompileRuntime.kotlinTestJarForTests()) + } else if (configurationKind.withMockRuntime) { + configuration.addJvmClasspathRoot(ForTestCompileRuntime.minimalRuntimeJarForTests()) + configuration.addJvmClasspathRoot(ForTestCompileRuntime.scriptRuntimeJarForTests()) + } + if (configurationKind.withReflection) { + configuration.addJvmClasspathRoot(ForTestCompileRuntime.reflectJarForTests()) + } + configuration.addJvmClasspathRoot(KtTestUtil.getAnnotationsJar()) + + if (JvmEnvironmentConfigurationDirectives.STDLIB_JDK8 in module.directives) { + configuration.addJvmClasspathRoot(ForTestCompileRuntime.runtimeJarForTestsWithJdk8()) + } + + if (JvmEnvironmentConfigurationDirectives.ANDROID_ANNOTATIONS in module.directives) { + configuration.addJvmClasspathRoot(ForTestCompileRuntime.androidAnnotationsForTests()) + } + + val isIr = module.backendKind == BackendKinds.IrBackend + configuration.put(JVMConfigurationKeys.IR, isIr) + + module.javaFiles.takeIf { it.isNotEmpty() }?.let { javaFiles -> + javaFiles.forEach { testServices.sourceFileProvider.getRealFileForSourceFile(it) } + configuration.addJavaSourceRoot(testServices.sourceFileProvider.javaSourceDirectory) + } + + configuration.registerModuleDependencies(module) + + if (JvmEnvironmentConfigurationDirectives.USE_PSI_CLASS_FILES_READING in module.directives) { + configuration.put(JVMConfigurationKeys.USE_PSI_CLASS_FILES_READING, true) + } + } + + private fun extractJdkKind(registeredDirectives: RegisteredDirectives): TestJdkKind { + val fullJdkEnabled = JvmEnvironmentConfigurationDirectives.FULL_JDK in registeredDirectives + val jdkKinds = registeredDirectives[JvmEnvironmentConfigurationDirectives.JDK_KIND] + + if (fullJdkEnabled) { + if (jdkKinds.isNotEmpty()) { + error("FULL_JDK and JDK_KIND can not be used together") + } + return TestJdkKind.FULL_JDK + } + + return when (jdkKinds.size) { + 0 -> TestJdkKind.MOCK_JDK + 1 -> jdkKinds.single() + else -> error("Too many jdk kinds passed: ${jdkKinds.joinToArrayString()}") + } + } + + private fun extractConfigurationKind(registeredDirectives: RegisteredDirectives): ConfigurationKind { + val withRuntime = JvmEnvironmentConfigurationDirectives.WITH_RUNTIME in registeredDirectives || JvmEnvironmentConfigurationDirectives.WITH_STDLIB in registeredDirectives + val withReflect = JvmEnvironmentConfigurationDirectives.WITH_REFLECT in registeredDirectives + val noRuntime = JvmEnvironmentConfigurationDirectives.NO_RUNTIME in registeredDirectives + if (noRuntime && withRuntime) { + error("NO_RUNTIME and WITH_RUNTIME can not be used together") + } + if (withReflect && !withRuntime) { + error("WITH_REFLECT may be used only with WITH_RUNTIME") + } + return when { + withRuntime && withReflect -> ConfigurationKind.ALL + withRuntime -> ConfigurationKind.NO_KOTLIN_REFLECT + noRuntime -> ConfigurationKind.JDK_NO_RUNTIME + else -> ConfigurationKind.JDK_ONLY + } + } + + private fun CompilerConfiguration.registerModuleDependencies(module: TestModule) { + val dependencyProvider = testServices.dependencyProvider + val modulesFromDependencies = module.dependencies + .filter { it.kind == DependencyKind.Binary } + .map { dependencyProvider.getTestModule(it.moduleName) } + .takeIf { it.isNotEmpty() } + ?: return + val jarManager = testServices.compiledJarManager + val dependenciesClassPath = modulesFromDependencies.map { jarManager.getCompiledJarForModule(it) } + addJvmClasspathRoots(dependenciesClassPath) + } +} + diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/ScriptingEnvironmentConfigurator.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/ScriptingEnvironmentConfigurator.kt new file mode 100644 index 00000000000..89aa9fee42f --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/configuration/ScriptingEnvironmentConfigurator.kt @@ -0,0 +1,22 @@ +/* + * 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.test.services.configuration + +import com.intellij.mock.MockProject +import org.jetbrains.kotlin.config.CompilerConfiguration +import org.jetbrains.kotlin.script.loadScriptingPlugin +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.EnvironmentConfigurator +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.isKtsFile + +class ScriptingEnvironmentConfigurator(testServices: TestServices) : EnvironmentConfigurator(testServices) { + override fun configureCompilerConfiguration(configuration: CompilerConfiguration, module: TestModule, project: MockProject) { + if (module.files.any { it.isKtsFile }) { + loadScriptingPlugin(configuration) + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/fir/FirOldFrontendMetaConfigurator.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/fir/FirOldFrontendMetaConfigurator.kt new file mode 100644 index 00000000000..ff6d7bc49ec --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/fir/FirOldFrontendMetaConfigurator.kt @@ -0,0 +1,28 @@ +/* + * 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.test.services.fir + +import org.jetbrains.kotlin.test.directives.FirDiagnosticsDirectives +import org.jetbrains.kotlin.test.services.MetaTestConfigurator +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.utils.firTestDataFile +import java.io.File + +class FirOldFrontendMetaConfigurator(testServices: TestServices) : MetaTestConfigurator(testServices) { + override fun transformTestDataPath(testDataFileName: String): String { + val originalFile = File(testDataFileName) + val isFirIdentical = originalFile.useLines { it.first() == "// ${FirDiagnosticsDirectives.FIR_IDENTICAL.name}" } + return if (isFirIdentical) { + testDataFileName + } else { + val firFile = originalFile.firTestDataFile + if (!firFile.exists()) { + originalFile.copyTo(firFile) + } + firFile.absolutePath + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/impl/ModuleStructureExtractorImpl.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/impl/ModuleStructureExtractorImpl.kt new file mode 100644 index 00000000000..eadecfdb5d4 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/impl/ModuleStructureExtractorImpl.kt @@ -0,0 +1,326 @@ +/* + * 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.test.services.impl + +import org.jetbrains.kotlin.platform.CommonPlatforms +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.platform.js.JsPlatforms +import org.jetbrains.kotlin.platform.jvm.JvmPlatforms +import org.jetbrains.kotlin.platform.konan.NativePlatforms +import org.jetbrains.kotlin.test.builders.LanguageVersionSettingsBuilder +import org.jetbrains.kotlin.test.directives.ModuleStructureDirectives +import org.jetbrains.kotlin.test.directives.model.ComposedRegisteredDirectives +import org.jetbrains.kotlin.test.directives.model.DirectivesContainer +import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives +import org.jetbrains.kotlin.test.model.* +import org.jetbrains.kotlin.test.services.* +import org.jetbrains.kotlin.test.util.joinToArrayString +import org.jetbrains.kotlin.utils.DFS +import java.io.File + +class ModuleStructureExtractorImpl( + testServices: TestServices, + additionalSourceProviders: List +) : ModuleStructureExtractor(testServices, additionalSourceProviders) { + companion object { + private val allowedExtensionsForFiles = listOf(".kt", ".kts", ".java") + private val moduleDirectiveRegex = """([\w-]+)(\((.*)\))?""".toRegex() + } + + override fun splitTestDataByModules( + testDataFileName: String, + directivesContainer: DirectivesContainer, + ): TestModuleStructure { + val testDataFile = File(testDataFileName) + val extractor = ModuleStructureExtractorWorker(listOf(testDataFile), directivesContainer) + return extractor.splitTestDataByModules() + } + + private inner class ModuleStructureExtractorWorker constructor( + private val testDataFiles: List, + private val directivesContainer: DirectivesContainer, + ) { + private val assertions: Assertions + get() = testServices.assertions + + private val defaultsProvider: DefaultsProvider + get() = testServices.defaultsProvider + + private lateinit var currentTestDataFile: File + + private val defaultFileName: String + get() = currentTestDataFile.name + + private val defaultModuleName: String + get() = "main" + + private var currentModuleName: String? = null + private var currentModuleTargetPlatform: TargetPlatform? = null + private var currentModuleFrontendKind: FrontendKind<*>? = null + private var currentModuleBackendKind: BackendKind<*>? = null + private var currentModuleLanguageVersionSettingsBuilder: LanguageVersionSettingsBuilder = initLanguageSettingsBuilder() + private var dependenciesOfCurrentModule = mutableListOf() + private var filesOfCurrentModule = mutableListOf() + + private var currentFileName: String? = null + private var firstFileInModule: Boolean = true + private var linesOfCurrentFile = mutableListOf() + private var startLineNumberOfCurrentFile = 0 + + private var directivesBuilder = RegisteredDirectivesParser(directivesContainer, assertions) + + private var globalDirectives: RegisteredDirectives? = null + + private val modules = mutableListOf() + + private val moduleDirectiveBuilder = RegisteredDirectivesParser(ModuleStructureDirectives, assertions) + + fun splitTestDataByModules(): TestModuleStructure { + for (testDataFile in testDataFiles) { + currentTestDataFile = testDataFile + val lines = testDataFile.readLines() + lines.forEachIndexed { lineNumber, line -> + val rawDirective = RegisteredDirectivesParser.parseDirective(line) + if (tryParseStructureDirective(rawDirective, lineNumber + 1)) { + linesOfCurrentFile.add(line) + return@forEachIndexed + } + tryParseRegularDirective(rawDirective) + linesOfCurrentFile.add(line) + } + } + finishModule() + val sortedModules = sortModules(modules) + checkCycles(modules) + return TestModuleStructureImpl(sortedModules, testDataFiles) + } + + private fun sortModules(modules: List): List { + val moduleByName = modules.groupBy { it.name }.mapValues { (name, modules) -> + modules.singleOrNull() ?: error("Duplicated modules with name $name") + } + return DFS.topologicalOrder(modules) { module -> + module.dependencies.map { + val moduleName = it.moduleName + moduleByName[moduleName] ?: error("Module \"$moduleName\" not found while observing dependencies of \"${module.name}\"") + } + }.asReversed() + } + + private fun checkCycles(modules: List) { + val visited = mutableSetOf() + for (module in modules) { + val moduleName = module.name + visited.add(moduleName) + for (dependency in module.dependencies) { + val dependencyName = dependency.moduleName + if (dependencyName == moduleName) { + error("Module $moduleName has dependency to itself") + } + if (dependencyName !in visited) { + error("There is cycle in modules dependencies. See modules: $dependencyName, $moduleName") + } + } + } + } + + /* + * returns [true] means that passed directive was module directive and line is processed + */ + private fun tryParseStructureDirective(rawDirective: RegisteredDirectivesParser.RawDirective?, lineNumber: Int): Boolean { + if (rawDirective == null) return false + val (directive, values) = moduleDirectiveBuilder.convertToRegisteredDirective(rawDirective) ?: return false + when (directive) { + ModuleStructureDirectives.MODULE -> { + /* + * There was previous module, so we should save it + */ + if (currentModuleName != null) { + finishModule() + } else { + finishGlobalDirectives() + } + val (moduleName, dependencies) = splitRawModuleStringToNameAndDependencies(values.joinToString(separator = " ")) + currentModuleName = moduleName + dependencies.mapTo(dependenciesOfCurrentModule) { name -> + val kind = defaultsProvider.defaultDependencyKind + DependencyDescription(name, kind, DependencyRelation.Dependency) + } + } + ModuleStructureDirectives.DEPENDENCY, + ModuleStructureDirectives.DEPENDS_ON -> { + val name = values.first() as String + val kind = values.getOrNull(1)?.let { valueOfOrNull(it as String) } ?: defaultsProvider.defaultDependencyKind + val relation = when (directive) { + ModuleStructureDirectives.DEPENDENCY -> DependencyRelation.Dependency + ModuleStructureDirectives.DEPENDS_ON -> DependencyRelation.DependsOn + else -> error("Should not be here") + } + dependenciesOfCurrentModule.add(DependencyDescription(name, kind, relation)) + } + ModuleStructureDirectives.TARGET_FRONTEND -> { + val name = values.singleOrNull() as? String? ?: assertions.fail { + "Target frontend specified incorrectly\nUsage: ${directive.description}" + } + currentModuleFrontendKind = FrontendKinds.fromString(name) ?: assertions.fail { + "Unknown frontend: $name" + } + } + ModuleStructureDirectives.TARGET_BACKEND_KIND -> { + val name = values.singleOrNull() as? String ?: assertions.fail { + "Target backend specified incorrectly\nUsage: ${directive.description}" + } + currentModuleBackendKind = BackendKinds.fromString(name) ?: assertions.fail { + "Unknown backend: $name" + } + } + ModuleStructureDirectives.FILE -> { + if (currentFileName != null) { + finishFile() + } else { + resetFileCaches() + } + currentFileName = (values.first() as String).also(::validateFileName) + startLineNumberOfCurrentFile = lineNumber + } + else -> return false + } + + return true + } + + private fun splitRawModuleStringToNameAndDependencies(moduleDirectiveString: String): Pair> { + val matchResult = moduleDirectiveRegex.matchEntire(moduleDirectiveString) + ?: error("\"$moduleDirectiveString\" doesn't matches with pattern \"moduleName(dep1, dep2)\"") + val (name, _, dependencies) = matchResult.destructured + if (dependencies.isBlank()) return name to emptyList() + return name to dependencies.split(" ") + } + + private fun finishGlobalDirectives() { + globalDirectives = directivesBuilder.build() + resetModuleCaches() + resetFileCaches() + } + + private fun finishModule() { + finishFile() + val moduleDirectives = directivesBuilder.build() + testServices.defaultDirectives + globalDirectives + currentModuleLanguageVersionSettingsBuilder.configureUsingDirectives(moduleDirectives) + val moduleName = currentModuleName ?: defaultModuleName + val testModule = TestModule( + name = moduleName, + targetPlatform = currentModuleTargetPlatform ?: parseModulePlatformByName(moduleName) ?: defaultsProvider.defaultPlatform, + frontendKind = currentModuleFrontendKind ?: defaultsProvider.defaultFrontend, + backendKind = currentModuleBackendKind ?: defaultsProvider.defaultBackend, + files = filesOfCurrentModule, + dependencies = dependenciesOfCurrentModule, + directives = moduleDirectives, + languageVersionSettings = currentModuleLanguageVersionSettingsBuilder.build() + ) + modules += testModule + additionalSourceProviders.flatMapTo(filesOfCurrentModule) { additionalSourceProvider -> + additionalSourceProvider.produceAdditionalFiles( + globalDirectives ?: RegisteredDirectives.Empty, + testModule + ).also { additionalFiles -> + require(additionalFiles.all { it.isAdditional }) { + "Files produced by ${additionalSourceProvider::class.qualifiedName} should have flag `isAdditional = true`" + } + } + } + firstFileInModule = true + resetModuleCaches() + } + + private fun parseModulePlatformByName(moduleName: String): TargetPlatform? { + val nameSuffix = moduleName.substringAfterLast("-", "").toUpperCase() + return when { + nameSuffix == "COMMON" -> CommonPlatforms.defaultCommonPlatform + nameSuffix == "JVM" -> JvmPlatforms.unspecifiedJvmPlatform // TODO(dsavvinov): determine JvmTarget precisely + nameSuffix == "JS" -> JsPlatforms.defaultJsPlatform + nameSuffix == "NATIVE" -> NativePlatforms.unspecifiedNativePlatform + nameSuffix.isEmpty() -> null // TODO(dsavvinov): this leads to 'null'-platform in ModuleDescriptor + else -> throw IllegalStateException("Can't determine platform by name $nameSuffix") + } + } + + private fun finishFile() { + val filename = currentFileName ?: defaultFileName + if (filesOfCurrentModule.any { it.name == filename }) { + error("File with name \"$filename\" already defined in module ${currentModuleName ?: defaultModuleName}") + } + filesOfCurrentModule.add( + TestFile( + relativePath = filename, + originalContent = linesOfCurrentFile.joinToString(separator = System.lineSeparator(), postfix = System.lineSeparator()), + originalFile = currentTestDataFile, + startLineNumberInOriginalFile = startLineNumberOfCurrentFile, + isAdditional = false + ) + ) + firstFileInModule = false + resetFileCaches() + } + + private fun resetModuleCaches() { + firstFileInModule = true + currentModuleName = null + currentModuleTargetPlatform = null + currentModuleFrontendKind = null + currentModuleBackendKind = null + currentModuleLanguageVersionSettingsBuilder = initLanguageSettingsBuilder() + filesOfCurrentModule = mutableListOf() + dependenciesOfCurrentModule = mutableListOf() + directivesBuilder = RegisteredDirectivesParser(directivesContainer, assertions) + } + + private fun resetFileCaches() { + if (!firstFileInModule) { + linesOfCurrentFile = mutableListOf() + } + currentFileName = null + startLineNumberOfCurrentFile = 0 + } + + private fun tryParseRegularDirective(rawDirective: RegisteredDirectivesParser.RawDirective?) { + if (rawDirective == null) return + val parsedDirective = directivesBuilder.convertToRegisteredDirective(rawDirective) ?: return + directivesBuilder.addParsedDirective(parsedDirective) + } + + private fun validateFileName(fileName: String) { + if (!allowedExtensionsForFiles.any { fileName.endsWith(it) }) { + assertions.fail { + "Filename $fileName is not valid. Allowed extensions: ${allowedExtensionsForFiles.joinToArrayString()}" + } + } + } + + private fun initLanguageSettingsBuilder(): LanguageVersionSettingsBuilder { + return defaultsProvider.newLanguageSettingsBuilder() + } + } +} + +private operator fun RegisteredDirectives.plus(other: RegisteredDirectives?): RegisteredDirectives { + return when { + other == null -> this + other.isEmpty() -> this + this.isEmpty() -> other + else -> ComposedRegisteredDirectives(this, other) + } +} + +inline fun > valueOfOrNull(value: String): T? { + for (enumValue in enumValues()) { + if (enumValue.name == value) { + return enumValue + } + } + return null +} + diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/impl/TestModuleStructureImpl.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/impl/TestModuleStructureImpl.kt new file mode 100644 index 00000000000..8b8e0a16151 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/impl/TestModuleStructureImpl.kt @@ -0,0 +1,62 @@ +/* + * 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.test.services.impl + +import org.jetbrains.kotlin.platform.TargetPlatform +import org.jetbrains.kotlin.platform.js.JsPlatforms +import org.jetbrains.kotlin.platform.jvm.JvmPlatforms +import org.jetbrains.kotlin.platform.konan.NativePlatforms +import org.jetbrains.kotlin.test.directives.model.ComposedRegisteredDirectives +import org.jetbrains.kotlin.test.directives.model.RegisteredDirectives +import org.jetbrains.kotlin.test.model.BinaryKind +import org.jetbrains.kotlin.test.model.ArtifactKinds +import org.jetbrains.kotlin.test.model.DependencyKind +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestModuleStructure +import java.io.File + +class TestModuleStructureImpl( + override val modules: List, + override val originalTestDataFiles: List +) : TestModuleStructure() { + override val allDirectives: RegisteredDirectives = ComposedRegisteredDirectives(modules.map { it.directives }) + + @OptIn(ExperimentalStdlibApi::class) + private val targetArtifactsByModule: Map>> = buildMap { + for (module in modules) { + val result = mutableListOf>() + for (dependency in module.dependencies) { + if (dependency.kind == DependencyKind.KLib) { + result += ArtifactKinds.KLib + } + } + module.targetPlatform.toArtifactKind()?.let { result += it } + put(module.name, result) + } + } + + override fun getTargetArtifactKinds(module: TestModule): List> { + return targetArtifactsByModule[module.name] ?: emptyList() + } + + override fun toString(): String { + return buildString { + modules.forEach { + appendLine(it) + appendLine() + } + } + } + + companion object { + private fun TargetPlatform.toArtifactKind(): BinaryKind<*>? = when (this) { + in JvmPlatforms.allJvmPlatforms -> ArtifactKinds.Jvm + in JsPlatforms.allJsPlatforms -> ArtifactKinds.Js + in NativePlatforms.allNativePlatforms -> ArtifactKinds.Native + else -> null + } + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/jvm/CompiledJarManager.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/jvm/CompiledJarManager.kt new file mode 100644 index 00000000000..05a7f3da099 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/services/jvm/CompiledJarManager.kt @@ -0,0 +1,37 @@ +/* + * 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.test.services.jvm + +import org.jetbrains.kotlin.backend.common.output.SimpleOutputFileCollection +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.output.writeAll +import org.jetbrains.kotlin.test.model.ArtifactKinds +import org.jetbrains.kotlin.test.model.TestModule +import org.jetbrains.kotlin.test.services.TestService +import org.jetbrains.kotlin.test.services.TestServices +import org.jetbrains.kotlin.test.services.compilerConfigurationProvider +import org.jetbrains.kotlin.test.services.dependencyProvider +import java.io.File +import java.nio.file.Files + +class CompiledJarManager(val testServices: TestServices) : TestService { + private val jarCache = mutableMapOf() + + fun getCompiledJarForModule(module: TestModule): File { + return jarCache.getOrPut(module) { + val outputDir = Files.createTempDirectory("module_${module.name}").toFile() + val classFileFactory = testServices.dependencyProvider.getArtifact(module, ArtifactKinds.Jvm).classFileFactory + val outputFileCollection = SimpleOutputFileCollection(classFileFactory.currentOutput) + val messageCollector = testServices.compilerConfigurationProvider.getCompilerConfiguration(module) + .getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + outputFileCollection.writeAll(outputDir, messageCollector, reportOutputFiles = false) + classFileFactory.releaseGeneratedOutput() + outputDir + } + } +} + +val TestServices.compiledJarManager: CompiledJarManager by TestServices.testServiceAccessor() diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/utils/FileUtils.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/utils/FileUtils.kt new file mode 100644 index 00000000000..89e1a6442b2 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/utils/FileUtils.kt @@ -0,0 +1,28 @@ +/* + * 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.test.utils + +import java.io.File + +private const val FIR_KT = ".fir.kt" +private const val KT = ".kt" + +val File.isFirTestData: Boolean + get() = name.endsWith(FIR_KT) + +val File.originalTestDataFile: File + get() = if (isFirTestData) { + parentFile.resolve("${name.removeSuffix(FIR_KT)}$KT") + } else { + this + } + +val File.firTestDataFile: File + get() = if (isFirTestData) { + this + } else { + parentFile.resolve("${name.removeSuffix(KT)}$FIR_KT") + } diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/utils/MultiModuleInfoDumper.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/utils/MultiModuleInfoDumper.kt new file mode 100644 index 00000000000..58e705fc21d --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/utils/MultiModuleInfoDumper.kt @@ -0,0 +1,37 @@ +/* + * 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.test.utils + +import org.jetbrains.kotlin.test.model.TestModule + +abstract class MultiModuleInfoDumper { + abstract fun builderForModule(module: TestModule): StringBuilder + abstract fun generateResultingDump(): String + abstract fun isEmpty(): Boolean +} + +// TODO: consider about tests with multiple testdata files +class MultiModuleInfoDumperImpl(private val moduleHeaderTemplate: String = "Module: %s") : MultiModuleInfoDumper() { + private val builderByModule = LinkedHashMap() + + override fun builderForModule(module: TestModule): StringBuilder { + return builderByModule.getOrPut(module, ::StringBuilder) + } + + override fun generateResultingDump(): String { + builderByModule.values.singleOrNull()?.let { return it.toString() } + return buildString { + for ((module, builder) in builderByModule) { + appendLine(String.format(moduleHeaderTemplate, module.name)) + append(builder) + } + } + } + + override fun isEmpty(): Boolean { + return builderByModule.isEmpty() + } +} diff --git a/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/utils/TestDisposable.kt b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/utils/TestDisposable.kt new file mode 100644 index 00000000000..9baa7ebe252 --- /dev/null +++ b/compiler/tests-common-new/tests/org/jetbrains/kotlin/test/utils/TestDisposable.kt @@ -0,0 +1,18 @@ +/* + * 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.test.utils + +import com.intellij.openapi.Disposable + +class TestDisposable : Disposable { + @Volatile + var isDisposed = false + private set + + override fun dispose() { + isDisposed = true + } +} diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticTest.kt index e3d63ec7f65..d984edb56d9 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticTest.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractFirDiagnosticTest.kt @@ -295,7 +295,7 @@ abstract class AbstractFirDiagnosticsTest : AbstractFirBaseDiagnosticsTest() { firFiles.forEach { it.accept(CfgConsistencyChecker) } } - private object CfgConsistencyChecker : FirVisitorVoid() { + object CfgConsistencyChecker : FirVisitorVoid() { override fun visitElement(element: FirElement) { element.acceptChildren(this) } diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/KotlinTestUtils.java b/compiler/tests-common/tests/org/jetbrains/kotlin/test/KotlinTestUtils.java index a5390e30c57..fd1d1ba3761 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/KotlinTestUtils.java +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/KotlinTestUtils.java @@ -52,8 +52,8 @@ import org.jetbrains.kotlin.psi.KtFile; import org.jetbrains.kotlin.psi.KtPsiFactoryKt; import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil; import org.jetbrains.kotlin.storage.LockBasedStorageManager; -import org.jetbrains.kotlin.test.util.JetTestUtilsKt; import org.jetbrains.kotlin.test.util.KtTestUtil; +import org.jetbrains.kotlin.test.util.StringUtilsKt; import org.jetbrains.kotlin.utils.ExceptionUtilsKt; import org.junit.Assert; @@ -291,7 +291,7 @@ public class KotlinTestUtils { public static void assertEqualsToFile(@NotNull String message, @NotNull File expectedFile, @NotNull String actual, @NotNull Function1 sanitizer) { try { - String actualText = JetTestUtilsKt.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(actual.trim())); + String actualText = StringUtilsKt.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(actual.trim())); if (!expectedFile.exists()) { FileUtil.writeToFile(expectedFile, actualText); @@ -299,7 +299,7 @@ public class KotlinTestUtils { } String expected = FileUtil.loadFile(expectedFile, CharsetToolkit.UTF8, true); - String expectedText = JetTestUtilsKt.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(expected.trim())); + String expectedText = StringUtilsKt.trimTrailingWhitespacesAndAddNewlineAtEOF(StringUtil.convertLineSeparators(expected.trim())); if (!Comparing.equal(sanitizer.invoke(expectedText), sanitizer.invoke(actualText))) { throw new FileComparisonFailure(message + ": " + expectedFile.getName(), diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/test/util/jetTestUtils.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/test/util/jetTestUtils.kt index 43c44475b69..c58e214df4e 100644 --- a/compiler/tests-common/tests/org/jetbrains/kotlin/test/util/jetTestUtils.kt +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/test/util/jetTestUtils.kt @@ -24,14 +24,6 @@ import org.jetbrains.kotlin.psi.KtDeclaration import org.jetbrains.kotlin.psi.KtPackageDirective import org.jetbrains.kotlin.psi.KtTreeVisitorVoid -fun String.trimTrailingWhitespaces(): String = - this.split('\n').joinToString(separator = "\n") { it.trimEnd() } - -fun String.trimTrailingWhitespacesAndAddNewlineAtEOF(): String = - this.trimTrailingWhitespaces().let { - result -> if (result.endsWith("\n")) result else result + "\n" - } - fun PsiFile.findElementByCommentPrefix(commentText: String): PsiElement? = findElementsByCommentPrefix(commentText).keys.singleOrNull() diff --git a/js/js.config/src/org/jetbrains/kotlin/platform/js/JsPlatform.kt b/js/js.config/src/org/jetbrains/kotlin/platform/js/JsPlatform.kt index a43f7ceefb3..f0c1c963375 100644 --- a/js/js.config/src/org/jetbrains/kotlin/platform/js/JsPlatform.kt +++ b/js/js.config/src/org/jetbrains/kotlin/platform/js/JsPlatform.kt @@ -15,7 +15,7 @@ abstract class JsPlatform : SimplePlatform("JS") { @Suppress("DEPRECATION_ERROR") object JsPlatforms { - private object DefaultSimpleJsPlatform : JsPlatform() + object DefaultSimpleJsPlatform : JsPlatform() @Deprecated( message = "Should be accessed only by compatibility layer, other clients should use 'defaultJsPlatform'", diff --git a/settings.gradle b/settings.gradle index 09e7e05ae78..4c14693281a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -327,7 +327,8 @@ include ":compiler:fir:cones", ":compiler:fir:analysis-tests" include ":compiler:test-infrastructure", - ":compiler:test-infrastructure-utils" + ":compiler:test-infrastructure-utils", + ":compiler:tests-common-new" include ":idea:idea-frontend-fir:idea-fir-low-level-api" include ":idea:idea-fir-performance-tests"