Move compiler/tests-common/{src -> tests}, adjust dependencies

This commit is contained in:
Alexander Udalov
2017-10-17 14:36:23 +02:00
parent c2276e2ccb
commit 33549362cb
115 changed files with 76 additions and 57 deletions
@@ -0,0 +1,29 @@
/*
* Copyright 2000-2010 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.testFramework;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Marks the annotated parameter as referencing a file in the testdata directory.
*/
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.PARAMETER})
public @interface TestDataFile {
}
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.testFramework;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Specifies the path to testdata for the current test case class.
* May use the variable $CONTENT_ROOT to specify the module content root or
* $PROJECT_ROOT to use the project base directory.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface TestDataPath {
String value();
}
@@ -0,0 +1,68 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.asJava
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiClass
import com.intellij.psi.impl.compiled.ClsElementImpl
import junit.framework.TestCase
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
import java.util.regex.Pattern
object LightClassTestCommon {
private val SUBJECT_FQ_NAME_PATTERN = Pattern.compile("^//\\s*(.*)$", Pattern.MULTILINE)
@JvmOverloads
fun testLightClass(
expectedFile: File,
testDataFile: File,
findLightClass: (String) -> PsiClass?,
normalizeText: (String) -> String
) {
val text = FileUtil.loadFile(testDataFile, true)
val matcher = SUBJECT_FQ_NAME_PATTERN.matcher(text)
TestCase.assertTrue("No FqName specified. First line of the form '// f.q.Name' expected", matcher.find())
val fqName = matcher.group(1)
val lightClass = findLightClass(fqName)
val actual = actualText(fqName, lightClass, normalizeText)
KotlinTestUtils.assertEqualsToFile(expectedFile, actual)
}
private fun actualText(fqName: String?, lightClass: PsiClass?, normalizeText: (String) -> String): String {
if (lightClass == null) {
return "<not generated>"
}
TestCase.assertTrue("Not a light class: $lightClass ($fqName)", lightClass is KtLightClass)
val delegate = (lightClass as KtLightClass).clsDelegate
TestCase.assertTrue("Not a CLS element: $delegate", delegate is ClsElementImpl)
val buffer = StringBuilder()
(delegate as ClsElementImpl).appendMirrorText(0, buffer)
return normalizeText(buffer.toString())
}
// Actual text for light class is generated with ClsElementImpl.appendMirrorText() that can find empty DefaultImpl inner class in stubs
// for all interfaces. This inner class can't be used in Java as it generally is not seen from light classes built from Kotlin sources.
// It is also omitted during classes generation in backend so it also absent in light classes built from compiled code.
fun removeEmptyDefaultImpls(text: String) : String = text.replace("\n static final class DefaultImpls {\n }\n", "")
}
@@ -0,0 +1,580 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.checkers
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.analyzer.common.CommonAnalyzerFacade
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.context.ModuleContext
import org.jetbrains.kotlin.context.SimpleGlobalContext
import org.jetbrains.kotlin.context.withModule
import org.jetbrains.kotlin.context.withProject
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.descriptors.PackagePartProvider
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.descriptors.impl.CompositePackageFragmentProvider
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl
import org.jetbrains.kotlin.diagnostics.Diagnostic
import org.jetbrains.kotlin.diagnostics.DiagnosticFactory
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import org.jetbrains.kotlin.diagnostics.Errors.*
import org.jetbrains.kotlin.frontend.java.di.createContainerForTopDownAnalyzerForJvm
import org.jetbrains.kotlin.frontend.java.di.initJvmBuiltInsForTopDownAnalysis
import org.jetbrains.kotlin.incremental.components.LookupTracker
import org.jetbrains.kotlin.load.java.lazy.SingleModuleClassResolver
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.platform.JvmBuiltIns
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.calls.USE_NEW_INFERENCE
import org.jetbrains.kotlin.resolve.calls.model.MutableResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
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.storage.ExceptionTracker
import org.jetbrains.kotlin.storage.LockBasedStorageManager
import org.jetbrains.kotlin.storage.StorageManager
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
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.util.RecursiveDescriptorComparator.RECURSIVE_ALL
import org.jetbrains.kotlin.utils.keysToMap
import org.junit.Assert
import java.io.File
import java.util.*
import java.util.function.Predicate
import java.util.regex.Pattern
abstract class AbstractDiagnosticsTest : BaseDiagnosticsTest() {
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
val groupedByModule = files.groupBy(TestFile::module)
var lazyOperationsLog: LazyOperationsLog? = null
val tracker = ExceptionTracker()
val storageManager: StorageManager
if (files.any(TestFile::checkLazyLog)) {
lazyOperationsLog = LazyOperationsLog(HASH_SANITIZER)
storageManager = LoggingStorageManager(
LockBasedStorageManager.createWithExceptionHandling(tracker),
lazyOperationsLog.addRecordFunction
)
}
else {
storageManager = LockBasedStorageManager.createWithExceptionHandling(tracker)
}
val context = SimpleGlobalContext(storageManager, tracker)
val modules = createModules(groupedByModule, context.storageManager)
val moduleBindings = HashMap<TestModule?, BindingContext>()
for ((testModule, testFilesInModule) in groupedByModule) {
val ktFiles = getKtFiles(testFilesInModule, true)
val oldModule = modules[testModule]!!
val languageVersionSettings = loadLanguageVersionSettings(testFilesInModule)
val moduleContext = context.withProject(project).withModule(oldModule)
val separateModules = groupedByModule.size == 1 && groupedByModule.keys.single() == null
val result = analyzeModuleContents(
moduleContext, ktFiles, CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(),
languageVersionSettings, separateModules
)
if (oldModule != result.moduleDescriptor) {
// For common modules, we use DefaultAnalyzerFacade who creates ModuleDescriptor instances by itself
// (its API does not support working with a module created beforehand).
// So, we should replace the old (effectively discarded) module with the new one everywhere in dependencies.
// TODO: dirty hack, refactor this test so that it doesn't create ModuleDescriptor instances
modules[testModule] = result.moduleDescriptor as ModuleDescriptorImpl
for (module in modules.values) {
@Suppress("DEPRECATION")
val it = (module.testOnly_AllDependentModules as MutableList).listIterator()
while (it.hasNext()) {
if (it.next() == oldModule) {
it.set(result.moduleDescriptor as ModuleDescriptorImpl)
}
}
}
}
moduleBindings[testModule] = result.bindingContext
checkAllResolvedCallsAreCompleted(ktFiles, result.bindingContext)
}
// We want to always create a test data file (txt) if it was missing,
// but don't want to skip the following checks in case this one fails
var exceptionFromLazyResolveLogValidation: Throwable? = null
if (lazyOperationsLog != null) {
exceptionFromLazyResolveLogValidation = checkLazyResolveLog(lazyOperationsLog, testDataFile)
}
else {
val lazyLogFile = getLazyLogFile(testDataFile)
assertFalse("No lazy log expected, but found: ${lazyLogFile.absolutePath}", lazyLogFile.exists())
}
var exceptionFromDescriptorValidation: Throwable? = null
try {
val expectedFile = if (InTextDirectivesUtils.isDirectiveDefined(testDataFile.readText(), "// JAVAC_EXPECTED_FILE")
&& environment.configuration.getBoolean(JVMConfigurationKeys.USE_JAVAC)) {
File(FileUtil.getNameWithoutExtension(testDataFile.absolutePath) + ".javac.txt")
} else {
File(FileUtil.getNameWithoutExtension(testDataFile.absolutePath) + ".txt")
}
validateAndCompareDescriptorWithFile(expectedFile, files, modules)
}
catch (e: Throwable) {
exceptionFromDescriptorValidation = e
}
// main checks
var ok = true
val actualText = StringBuilder()
for (testFile in files) {
val module = testFile.module
val isCommonModule = modules[module]!!.getMultiTargetPlatform() == MultiTargetPlatform.Common
val implementingModules =
if (!isCommonModule) emptyList()
else modules.entries.filter { (testModule) -> module in testModule?.getDependencies().orEmpty() }
val implementingModulesBindings = implementingModules.mapNotNull {
(testModule, moduleDescriptor) ->
val platform = moduleDescriptor.getCapability(MultiTargetPlatform.CAPABILITY)
if (platform is MultiTargetPlatform.Specific) platform to moduleBindings[testModule]!!
else null
}
ok = ok and testFile.getActualText(
moduleBindings[module]!!, implementingModulesBindings, actualText,
shouldSkipJvmSignatureDiagnostics(groupedByModule) || isCommonModule
)
}
var exceptionFromDynamicCallDescriptorsValidation: Throwable? = null
try {
val expectedFile = File(FileUtil.getNameWithoutExtension(testDataFile.absolutePath) + ".dynamic.txt")
checkDynamicCallDescriptors(expectedFile, files)
}
catch (e: Throwable) {
exceptionFromDynamicCallDescriptorsValidation = e
}
KotlinTestUtils.assertEqualsToFile(testDataFile, actualText.toString())
assertTrue("Diagnostics mismatch. See the output above", ok)
// now we throw a previously found error, if any
exceptionFromDescriptorValidation?.let { throw it }
exceptionFromLazyResolveLogValidation?.let { throw it }
exceptionFromDynamicCallDescriptorsValidation?.let { throw it }
performAdditionalChecksAfterDiagnostics(testDataFile, files, groupedByModule, modules, moduleBindings)
}
protected open fun performAdditionalChecksAfterDiagnostics(
testDataFile: File,
testFiles: List<TestFile>,
moduleFiles: Map<TestModule?, List<TestFile>>,
moduleDescriptors: Map<TestModule?, ModuleDescriptorImpl>,
moduleBindings: Map<TestModule?, BindingContext>
) {
// To be overridden by diagnostic-like tests.
}
protected open fun loadLanguageVersionSettings(module: List<TestFile>): LanguageVersionSettings {
var result: LanguageVersionSettings? = null
for (file in module) {
val current = file.customLanguageVersionSettings
if (current != null) {
if (result != null && result != current) {
Assert.fail(
"More than one file in the module has $LANGUAGE_DIRECTIVE or $API_VERSION_DIRECTIVE directive specified. " +
"This is not supported. Please move all directives into one file"
)
}
result = current
}
}
return result ?: CompilerTestLanguageVersionSettings(
BaseDiagnosticsTest.DEFAULT_DIAGNOSTIC_TESTS_FEATURES,
LanguageVersionSettingsImpl.DEFAULT.apiVersion,
LanguageVersionSettingsImpl.DEFAULT.languageVersion
)
}
private fun checkDynamicCallDescriptors(expectedFile: File, testFiles: List<TestFile>) {
val serializer = RecursiveDescriptorComparator(RECURSIVE_ALL)
val actualText = StringBuilder()
for (testFile in testFiles) {
for (descriptor in testFile.dynamicCallDescriptors) {
val actualSerialized = serializer.serializeRecursively(descriptor)
actualText.append(actualSerialized)
}
}
if (actualText.isNotEmpty() || expectedFile.exists()) {
KotlinTestUtils.assertEqualsToFile(expectedFile, actualText.toString())
}
}
protected open fun shouldSkipJvmSignatureDiagnostics(groupedByModule: Map<TestModule?, List<TestFile>>): Boolean =
groupedByModule.size > 1
private fun checkLazyResolveLog(lazyOperationsLog: LazyOperationsLog, testDataFile: File): Throwable? =
try {
val expectedFile = getLazyLogFile(testDataFile)
KotlinTestUtils.assertEqualsToFile(expectedFile, lazyOperationsLog.getText(), HASH_SANITIZER)
null
}
catch (e: Throwable) {
e
}
private fun getLazyLogFile(testDataFile: File): File =
File(FileUtil.getNameWithoutExtension(testDataFile.absolutePath) + ".lazy.log")
protected open fun analyzeModuleContents(
moduleContext: ModuleContext,
files: List<KtFile>,
moduleTrace: BindingTrace,
languageVersionSettings: LanguageVersionSettings,
separateModules: Boolean
): AnalysisResult {
@Suppress("NAME_SHADOWING")
var files = files
// New JavaDescriptorResolver is created for each module, which is good because it emulates different Java libraries for each module,
// albeit with same class names
// See TopDownAnalyzerFacadeForJVM#analyzeFilesWithJavaIntegration
// Temporary solution: only use separate module mode in single-module tests because analyzeFilesWithJavaIntegration
// only supports creating two modules, whereas there can be more than two in multi-module diagnostic tests
// TODO: always use separate module mode, once analyzeFilesWithJavaIntegration can create multiple modules
if (separateModules) {
return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
moduleContext.project,
files,
moduleTrace,
environment.configuration.copy().apply { this.languageVersionSettings = languageVersionSettings },
environment::createPackagePartProvider
)
}
val moduleDescriptor = moduleContext.module as ModuleDescriptorImpl
val platform = moduleDescriptor.getMultiTargetPlatform()
if (platform == MultiTargetPlatform.Common) {
return CommonAnalyzerFacade.analyzeFiles(
files, moduleDescriptor.name, true, languageVersionSettings,
mapOf(
MultiTargetPlatform.CAPABILITY to MultiTargetPlatform.Common,
MODULE_FILES to files
)
) { _, _ ->
// TODO
PackagePartProvider.Empty
}
}
else if (platform != null) {
// TODO: analyze with the correct platform, not always JVM
files += getCommonCodeFilesForPlatformSpecificModule(moduleDescriptor)
}
val moduleContentScope = GlobalSearchScope.allScope(moduleContext.project)
val moduleClassResolver = SingleModuleClassResolver()
val container = createContainerForTopDownAnalyzerForJvm(
moduleContext,
moduleTrace,
FileBasedDeclarationProviderFactory(moduleContext.storageManager, files),
moduleContentScope,
LookupTracker.DO_NOTHING,
environment.createPackagePartProvider(moduleContentScope),
moduleClassResolver,
JvmTarget.JVM_1_6,
languageVersionSettings
)
container.initJvmBuiltInsForTopDownAnalysis()
moduleClassResolver.resolver = container.get<JavaDescriptorResolver>()
moduleDescriptor.initialize(CompositePackageFragmentProvider(listOf(
container.get<KotlinCodeAnalyzer>().packageFragmentProvider,
container.get<JavaDescriptorResolver>().packageFragmentProvider
)))
container.get<LazyTopDownAnalyzer>().analyzeDeclarations(TopDownAnalysisMode.TopLevelDeclarations, files)
return AnalysisResult.success(moduleTrace.bindingContext, moduleDescriptor)
}
private fun getCommonCodeFilesForPlatformSpecificModule(moduleDescriptor: ModuleDescriptorImpl): List<KtFile> {
// We assume that a platform-specific module _implements_ all declarations from common modules which are immediate dependencies.
// So we collect all sources from such modules to analyze in the platform-specific module as well
@Suppress("DEPRECATION")
val dependencies = moduleDescriptor.testOnly_AllDependentModules
// TODO: diagnostics on common code reported during the platform module analysis should be distinguished somehow
// E.g. "<!JVM:ACTUAL_WITHOUT_EXPECT!>...<!>
val result = ArrayList<KtFile>(0)
for (dependency in dependencies) {
if (dependency.getCapability(MultiTargetPlatform.CAPABILITY) == MultiTargetPlatform.Common) {
val files = dependency.getCapability(MODULE_FILES)
?: error("MODULE_FILES should have been set for the common module: $dependency")
result.addAll(files)
}
}
return result
}
private fun validateAndCompareDescriptorWithFile(
expectedFile: File,
testFiles: List<TestFile>,
modules: Map<TestModule?, ModuleDescriptorImpl>
) {
if (skipDescriptorsValidation()) return
if (testFiles.any { file -> InTextDirectivesUtils.isDirectiveDefined(file.expectedText, "// SKIP_TXT") }) {
assertFalse(".txt file should not exist if SKIP_TXT directive is used: $expectedFile", expectedFile.exists())
return
}
val comparator = RecursiveDescriptorComparator(createdAffectedPackagesConfiguration(testFiles, modules.values))
val isMultiModuleTest = modules.size != 1
val packages =
(testFiles.flatMap {
InTextDirectivesUtils.findListWithPrefixes(it.expectedText, "// RENDER_PACKAGE:").map {
FqName(it.trim())
}
} + FqName.ROOT).toSet()
val textByPackage = packages.keysToMap { StringBuilder() }
val sortedModules = modules.keys.sortedWith(Comparator { x, y ->
when {
x == null && y == null -> 0
x == null && y != null -> -1
x != null && y == null -> 1
x != null && y != null -> x.compareTo(y)
else -> error("Unreachable")
}
})
for ((packageName, packageText) in textByPackage.entries) {
val module = sortedModules.iterator()
while (module.hasNext()) {
val moduleDescriptor = modules[module.next()]!!
val aPackage = moduleDescriptor.getPackage(packageName)
assertFalse(aPackage.isEmpty())
if (isMultiModuleTest) {
packageText.append(String.format("// -- Module: %s --\n", moduleDescriptor.name))
}
val actualSerialized = comparator.serializeRecursively(aPackage)
packageText.append(actualSerialized)
if (isMultiModuleTest && module.hasNext()) {
packageText.append("\n\n")
}
}
}
val allPackagesText = textByPackage.values.joinToString("\n")
val lineCount = StringUtil.getLineBreakCount(allPackagesText)
assert(lineCount < 1000) {
"Rendered descriptors of this test take up $lineCount lines. " +
"Please ensure you don't render JRE contents to the .txt file. " +
"Such tests are hard to maintain, take long time to execute and are subject to sudden unreviewed changes anyway."
}
KotlinTestUtils.assertEqualsToFile(expectedFile, allPackagesText)
}
protected open fun skipDescriptorsValidation(): Boolean = false
private fun getJavaFilePackage(testFile: TestFile): Name {
val pattern = Pattern.compile("^\\s*package [.\\w\\d]*", Pattern.MULTILINE)
val matcher = pattern.matcher(testFile.expectedText)
if (matcher.find()) {
return testFile.expectedText
.substring(matcher.start(), matcher.end())
.split(" ")
.last()
.filter { !it.isWhitespace() }
.let { Name.identifier(it.split(".").first()) }
}
return SpecialNames.ROOT_PACKAGE
}
private fun createdAffectedPackagesConfiguration(
testFiles: List<TestFile>,
modules: Collection<ModuleDescriptor>
): RecursiveDescriptorComparator.Configuration {
val packagesNames = (
testFiles.filter { it.ktFile == null }
.map { getJavaFilePackage(it) } +
getTopLevelPackagesFromFileList(getKtFiles(testFiles, false))
).toSet()
val stepIntoFilter = Predicate<DeclarationDescriptor> { descriptor ->
val module = DescriptorUtils.getContainingModuleOrNull(descriptor)
if (module !in modules) return@Predicate false
if (descriptor is PackageViewDescriptor) {
val fqName = descriptor.fqName
return@Predicate fqName.isRoot || fqName.pathSegments().first() in packagesNames
}
true
}
return RECURSIVE.filterRecursion(stepIntoFilter)
.withValidationStrategy(DescriptorValidator.ValidationVisitor.errorTypesAllowed())
.checkFunctionContracts(true)
}
private fun getTopLevelPackagesFromFileList(files: List<KtFile>): Set<Name> =
files.mapTo(LinkedHashSet<Name>()) { file ->
file.packageFqName.pathSegments().firstOrNull() ?: SpecialNames.ROOT_PACKAGE
}
private fun createModules(
groupedByModule: Map<TestModule?, List<TestFile>>,
storageManager: StorageManager
): MutableMap<TestModule?, ModuleDescriptorImpl> {
val modules = HashMap<TestModule?, ModuleDescriptorImpl>()
for (testModule in groupedByModule.keys) {
val module = if (testModule == null)
createSealedModule(storageManager)
else
createModule(testModule.name, storageManager)
modules.put(testModule, module)
}
for (testModule in groupedByModule.keys) {
if (testModule == null) continue
val module = modules[testModule]!!
val dependencies = ArrayList<ModuleDescriptorImpl>()
dependencies.add(module)
for (dependency in testModule.getDependencies()) {
dependencies.add(modules[dependency]!!)
}
dependencies.add(module.builtIns.builtInsModule)
dependencies.addAll(getAdditionalDependencies(module))
module.setDependencies(dependencies)
}
return modules
}
protected open fun getAdditionalDependencies(module: ModuleDescriptorImpl): List<ModuleDescriptorImpl> =
emptyList()
protected open fun createModule(moduleName: String, storageManager: StorageManager): ModuleDescriptorImpl {
val nameSuffix = moduleName.substringAfterLast("-", "")
val platform =
if (nameSuffix.isEmpty()) null
else if (nameSuffix == "common") MultiTargetPlatform.Common else MultiTargetPlatform.Specific(nameSuffix.toUpperCase())
return ModuleDescriptorImpl(Name.special("<$moduleName>"), storageManager, JvmBuiltIns(storageManager), platform)
}
protected open fun createSealedModule(storageManager: StorageManager): ModuleDescriptorImpl =
createModule("test-module", storageManager).apply {
setDependencies(this, builtIns.builtInsModule)
}
private fun checkAllResolvedCallsAreCompleted(ktFiles: List<KtFile>, bindingContext: BindingContext) {
if (ktFiles.any { file -> AnalyzingUtils.getSyntaxErrorRanges(file).isNotEmpty() }) return
val resolvedCallsEntries = bindingContext.getSliceContents(BindingContext.RESOLVED_CALL)
for ((call, resolvedCall) in resolvedCallsEntries) {
val element = call.callElement
val lineAndColumn = DiagnosticUtils.getLineAndColumnInPsiFile(element.containingFile, element.textRange)
if (!USE_NEW_INFERENCE) {
assertTrue("Resolved call for '${element.text}'$lineAndColumn is not completed",
(resolvedCall as MutableResolvedCall<*>).isCompleted)
}
}
checkResolvedCallsInDiagnostics(bindingContext)
}
private fun checkResolvedCallsInDiagnostics(bindingContext: BindingContext) {
val diagnosticsStoringResolvedCalls1 = setOf(
OVERLOAD_RESOLUTION_AMBIGUITY, NONE_APPLICABLE, CANNOT_COMPLETE_RESOLVE, UNRESOLVED_REFERENCE_WRONG_RECEIVER,
ASSIGN_OPERATOR_AMBIGUITY, ITERATOR_AMBIGUITY
)
val diagnosticsStoringResolvedCalls2 = setOf(
COMPONENT_FUNCTION_AMBIGUITY, DELEGATE_SPECIAL_FUNCTION_AMBIGUITY, DELEGATE_SPECIAL_FUNCTION_NONE_APPLICABLE
)
for (diagnostic in bindingContext.diagnostics) {
when (diagnostic.factory) {
in diagnosticsStoringResolvedCalls1 -> assertResolvedCallsAreCompleted(
diagnostic, DiagnosticFactory.cast(diagnostic, diagnosticsStoringResolvedCalls1).a
)
in diagnosticsStoringResolvedCalls2 -> assertResolvedCallsAreCompleted(
diagnostic, DiagnosticFactory.cast(diagnostic, diagnosticsStoringResolvedCalls2).b
)
}
}
}
private fun assertResolvedCallsAreCompleted(diagnostic: Diagnostic, resolvedCalls: Collection<ResolvedCall<*>>) {
val element = diagnostic.psiElement
val lineAndColumn = DiagnosticUtils.getLineAndColumnInPsiFile(element.containingFile, element.textRange)
if (USE_NEW_INFERENCE) return
assertTrue("Resolved calls stored in ${diagnostic.factory.name}\nfor '${element.text}'$lineAndColumn are not completed",
resolvedCalls.all { (it as MutableResolvedCall<*>).isCompleted })
}
companion object {
private val HASH_SANITIZER = fun(s: String): String = s.replace("@(\\d)+".toRegex(), "")
private val MODULE_FILES = ModuleDescriptor.Capability<List<KtFile>>("")
}
}
@@ -0,0 +1,375 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.checkers
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Condition
import com.intellij.openapi.util.Conditions
import com.intellij.openapi.util.TextRange
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.containers.ContainerUtil
import org.jetbrains.kotlin.asJava.getJvmSignatureDiagnostics
import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest.TestFile
import org.jetbrains.kotlin.checkers.BaseDiagnosticsTest.TestModule
import org.jetbrains.kotlin.checkers.CheckerTestUtil.ActualDiagnostic
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.LanguageVersionSettings
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.diagnostics.*
import org.jetbrains.kotlin.load.java.InternalFlexibleTypeTransformer
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.MultiTargetPlatform
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.utils.addIfNotNull
import org.junit.Assert
import java.io.File
import java.util.*
import java.util.regex.Pattern
import kotlin.reflect.jvm.javaField
abstract class BaseDiagnosticsTest : KotlinMultiFileTestWithJava<TestModule, TestFile>() {
protected lateinit var environment: KotlinCoreEnvironment
protected val project: Project
get() = environment.project
override fun tearDown() {
this::environment.javaField!![this] = null
super.tearDown()
}
override fun createTestModule(name: String): TestModule =
TestModule(name)
override fun createTestFile(module: TestModule?, fileName: String, text: String, directives: Map<String, String>): TestFile =
TestFile(module, fileName, text, directives)
override fun doMultiFileTest(
file: File,
modules: @JvmSuppressWildcards Map<String, ModuleAndDependencies>,
testFiles: List<TestFile>
) {
for (moduleAndDependencies in modules.values) {
moduleAndDependencies.module.getDependencies().addAll(moduleAndDependencies.dependencies.map { name ->
modules[name]?.module ?: error("Dependency not found: $name for module ${moduleAndDependencies.module.name}")
})
}
environment = createEnvironment(file)
analyzeAndCheck(file, testFiles)
}
protected abstract fun analyzeAndCheck(testDataFile: File, files: List<TestFile>)
protected fun getKtFiles(testFiles: List<TestFile>, includeExtras: Boolean): List<KtFile> {
var declareFlexibleType = false
var declareCheckType = false
val ktFiles = arrayListOf<KtFile>()
for (testFile in testFiles) {
ktFiles.addIfNotNull(testFile.ktFile)
declareFlexibleType = declareFlexibleType or testFile.declareFlexibleType
declareCheckType = declareCheckType or testFile.declareCheckType
}
if (includeExtras) {
if (declareFlexibleType) {
ktFiles.add(KotlinTestUtils.createFile("EXPLICIT_FLEXIBLE_TYPES.kt", EXPLICIT_FLEXIBLE_TYPES_DECLARATIONS, project))
}
if (declareCheckType) {
ktFiles.add(KotlinTestUtils.createFile("CHECK_TYPE.kt", CHECK_TYPE_DECLARATIONS, project))
}
}
return ktFiles
}
class TestModule(val name: String) : Comparable<TestModule> {
private val dependencies = ArrayList<TestModule>()
fun getDependencies(): MutableList<TestModule> = dependencies
override fun compareTo(other: TestModule): Int = name.compareTo(other.name)
override fun toString(): String = name
}
inner class TestFile(
val module: TestModule?,
fileName: String,
textWithMarkers: String,
directives: Map<String, String>
) {
private val diagnosedRanges: List<CheckerTestUtil.DiagnosedRange> = ArrayList()
val expectedText: String
private val clearText: String
private val createKtFile: Lazy<KtFile?>
private val whatDiagnosticsToConsider: Condition<Diagnostic>
val customLanguageVersionSettings: LanguageVersionSettings?
val declareCheckType: Boolean
val declareFlexibleType: Boolean
val checkLazyLog: Boolean
private val markDynamicCalls: Boolean
val dynamicCallDescriptors: List<DeclarationDescriptor> = ArrayList()
init {
this.declareCheckType = CHECK_TYPE_DIRECTIVE in directives
this.whatDiagnosticsToConsider = parseDiagnosticFilterDirective(directives, declareCheckType)
this.customLanguageVersionSettings = parseLanguageVersionSettings(directives)
this.checkLazyLog = CHECK_LAZY_LOG_DIRECTIVE in directives || CHECK_LAZY_LOG_DEFAULT
this.declareFlexibleType = EXPLICIT_FLEXIBLE_TYPES_DIRECTIVE in directives
this.markDynamicCalls = MARK_DYNAMIC_CALLS_DIRECTIVE in directives
if (fileName.endsWith(".java")) {
// TODO: check there are no syntax errors in .java sources
this.createKtFile = lazyOf(null)
this.clearText = textWithMarkers
this.expectedText = this.clearText
}
else {
this.expectedText = textWithMarkers
this.clearText = CheckerTestUtil.parseDiagnosedRanges(addExtras(expectedText), diagnosedRanges)
this.createKtFile = lazy { TestCheckerUtil.createCheckAndReturnPsiFile(fileName, clearText, project) }
}
}
val ktFile: KtFile? by createKtFile
private val imports: String
get() = buildString {
// Line separator is "\n" intentionally here (see DocumentImpl.assertValidSeparators)
if (declareCheckType) {
append(CHECK_TYPE_IMPORT + "\n")
}
if (declareFlexibleType) {
append(EXPLICIT_FLEXIBLE_TYPES_IMPORT + "\n")
}
}
private val extras: String
get() = "/*extras*/\n$imports/*extras*/\n\n"
private fun addExtras(text: String): String =
addImports(text, extras)
private fun stripExtras(actualText: StringBuilder) {
val extras = extras
val start = actualText.indexOf(extras)
if (start >= 0) {
actualText.delete(start, start + extras.length)
}
}
private fun addImports(text: String, imports: String): String {
var result = text
val pattern = Pattern.compile("^package [\\.\\w\\d]*\n", Pattern.MULTILINE)
val matcher = pattern.matcher(result)
if (matcher.find()) {
// add imports after the package directive
result = result.substring(0, matcher.end()) + imports + result.substring(matcher.end())
}
else {
// add imports at the beginning
result = imports + result
}
return result
}
fun getActualText(
bindingContext: BindingContext,
implementingModulesBindings: List<Pair<MultiTargetPlatform, BindingContext>>,
actualText: StringBuilder,
skipJvmSignatureDiagnostics: Boolean
): Boolean {
val ktFile = this.ktFile
if (ktFile == null) {
// TODO: check java files too
actualText.append(this.clearText)
return true
}
// TODO: report JVM signature diagnostics also for implementing modules
val jvmSignatureDiagnostics = if (skipJvmSignatureDiagnostics)
emptySet<ActualDiagnostic>()
else
computeJvmSignatureDiagnostics(bindingContext)
val ok = booleanArrayOf(true)
val diagnostics = ContainerUtil.filter(
CheckerTestUtil.getDiagnosticsIncludingSyntaxErrors(
bindingContext, implementingModulesBindings, ktFile, markDynamicCalls, dynamicCallDescriptors
) + jvmSignatureDiagnostics,
{ whatDiagnosticsToConsider.value(it.diagnostic) }
)
val diagnosticToExpectedDiagnostic = CheckerTestUtil.diagnosticsDiff(diagnosedRanges, diagnostics, object : CheckerTestUtil.DiagnosticDiffCallbacks {
override fun missingDiagnostic(diagnostic: CheckerTestUtil.TextDiagnostic, expectedStart: Int, expectedEnd: Int) {
val message = "Missing " + diagnostic.description + DiagnosticUtils.atLocation(ktFile, TextRange(expectedStart, expectedEnd))
System.err.println(message)
ok[0] = false
}
override fun wrongParametersDiagnostic(
expectedDiagnostic: CheckerTestUtil.TextDiagnostic,
actualDiagnostic: CheckerTestUtil.TextDiagnostic,
start: Int,
end: Int
) {
val message = "Parameters of diagnostic not equal at position " +
DiagnosticUtils.atLocation(ktFile, TextRange(start, end)) +
". Expected: ${expectedDiagnostic.asString()}, actual: $actualDiagnostic"
System.err.println(message)
ok[0] = false
}
override fun unexpectedDiagnostic(diagnostic: CheckerTestUtil.TextDiagnostic, actualStart: Int, actualEnd: Int) {
val message = "Unexpected ${diagnostic.description}${DiagnosticUtils.atLocation(ktFile, TextRange(actualStart, actualEnd))}"
System.err.println(message)
ok[0] = false
}
})
actualText.append(
CheckerTestUtil.addDiagnosticMarkersToText(ktFile, diagnostics, diagnosticToExpectedDiagnostic, { file -> file.text })
)
stripExtras(actualText)
return ok[0]
}
private fun computeJvmSignatureDiagnostics(bindingContext: BindingContext): Set<ActualDiagnostic> {
val jvmSignatureDiagnostics = HashSet<ActualDiagnostic>()
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).map { ActualDiagnostic(it, null) })
}
return jvmSignatureDiagnostics
}
override fun toString(): String = ktFile?.name ?: "Java file"
}
companion object {
val DIAGNOSTICS_DIRECTIVE = "DIAGNOSTICS"
val DIAGNOSTICS_PATTERN: Pattern = Pattern.compile("([\\+\\-!])(\\w+)\\s*")
val DIAGNOSTICS_TO_INCLUDE_ANYWAY: Set<DiagnosticFactory<*>> = setOf(
Errors.UNRESOLVED_REFERENCE,
Errors.UNRESOLVED_REFERENCE_WRONG_RECEIVER,
CheckerTestUtil.SyntaxErrorDiagnosticFactory.INSTANCE,
CheckerTestUtil.DebugInfoDiagnosticFactory.ELEMENT_WITH_ERROR_TYPE,
CheckerTestUtil.DebugInfoDiagnosticFactory.MISSING_UNRESOLVED,
CheckerTestUtil.DebugInfoDiagnosticFactory.UNRESOLVED_WITH_TARGET
)
val DEFAULT_DIAGNOSTIC_TESTS_FEATURES = mapOf(
LanguageFeature.Coroutines to LanguageFeature.State.ENABLED
)
val CHECK_TYPE_DIRECTIVE = "CHECK_TYPE"
val CHECK_TYPE_PACKAGE = "tests._checkType"
private val CHECK_TYPE_DECLARATIONS = "\npackage " + CHECK_TYPE_PACKAGE +
"\nfun <T> checkSubtype(t: T) = t" +
"\nclass Inv<T>" +
"\nfun <E> Inv<E>._() {}" +
"\ninfix fun <T> T.checkType(f: Inv<T>.() -> Unit) {}"
val CHECK_TYPE_IMPORT = "import $CHECK_TYPE_PACKAGE.*"
val EXPLICIT_FLEXIBLE_TYPES_DIRECTIVE = "EXPLICIT_FLEXIBLE_TYPES"
val EXPLICIT_FLEXIBLE_PACKAGE = InternalFlexibleTypeTransformer.FLEXIBLE_TYPE_CLASSIFIER.packageFqName.asString()
val EXPLICIT_FLEXIBLE_CLASS_NAME = InternalFlexibleTypeTransformer.FLEXIBLE_TYPE_CLASSIFIER.relativeClassName.asString()
private val EXPLICIT_FLEXIBLE_TYPES_DECLARATIONS = "\npackage " + EXPLICIT_FLEXIBLE_PACKAGE +
"\npublic class " + EXPLICIT_FLEXIBLE_CLASS_NAME + "<L, U>"
private val EXPLICIT_FLEXIBLE_TYPES_IMPORT = "import $EXPLICIT_FLEXIBLE_PACKAGE.$EXPLICIT_FLEXIBLE_CLASS_NAME"
val CHECK_LAZY_LOG_DIRECTIVE = "CHECK_LAZY_LOG"
val CHECK_LAZY_LOG_DEFAULT = "true" == System.getProperty("check.lazy.logs", "false")
val MARK_DYNAMIC_CALLS_DIRECTIVE = "MARK_DYNAMIC_CALLS"
private fun parseDiagnosticFilterDirective(directiveMap: Map<String, String>, allowUnderscoreUsage: Boolean): Condition<Diagnostic> {
val directives = directiveMap[DIAGNOSTICS_DIRECTIVE]
val initialCondition =
if (allowUnderscoreUsage)
Condition<Diagnostic> { it.factory.name != "UNDERSCORE_USAGE_WITHOUT_BACKTICKS" }
else
Conditions.alwaysTrue()
if (directives == null) {
// If "!API_VERSION" is present, disable the NEWER_VERSION_IN_SINCE_KOTLIN diagnostic.
// Otherwise it would be reported in any non-trivial test on the @SinceKotlin value.
if (API_VERSION_DIRECTIVE in directiveMap) {
return Conditions.and(initialCondition, Condition {
diagnostic -> diagnostic.factory !== Errors.NEWER_VERSION_IN_SINCE_KOTLIN
})
}
return initialCondition
}
var condition = initialCondition
val matcher = DIAGNOSTICS_PATTERN.matcher(directives)
if (!matcher.find()) {
Assert.fail("Wrong syntax in the '// !$DIAGNOSTICS_DIRECTIVE: ...' directive:\n" +
"found: '$directives'\n" +
"Must be '([+-!]DIAGNOSTIC_FACTORY_NAME|ERROR|WARNING|INFO)+'\n" +
"where '+' means 'include'\n" +
" '-' means 'exclude'\n" +
" '!' means 'exclude everything but this'\n" +
"directives are applied in the order of appearance, i.e. !FOO +BAR means include only FOO and BAR")
}
var first = true
do {
val operation = matcher.group(1)
val name = matcher.group(2)
val newCondition: Condition<Diagnostic> =
if (name in setOf("ERROR", "WARNING", "INFO")) {
Condition { diagnostic -> diagnostic.severity == Severity.valueOf(name) }
}
else {
Condition { diagnostic -> name == diagnostic.factory.name }
}
when (operation) {
"!" -> {
if (!first) {
Assert.fail("'$operation$name' appears in a position rather than the first one, " +
"which effectively cancels all the previous filters in this directive")
}
condition = newCondition
}
"+" -> condition = Conditions.or(condition, newCondition)
"-" -> condition = Conditions.and(condition, Conditions.not(newCondition))
}
first = false
}
while (matcher.find())
// We always include UNRESOLVED_REFERENCE and SYNTAX_ERROR because they are too likely to indicate erroneous test data
return Conditions.or(
condition,
Condition { diagnostic -> diagnostic.factory in DIAGNOSTICS_TO_INCLUDE_ANYWAY }
)
}
}
}
@@ -0,0 +1,193 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.checkers;
import com.intellij.openapi.util.io.FileUtil;
import kotlin.collections.CollectionsKt;
import kotlin.io.FilesKt;
import kotlin.text.Charsets;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.config.ContentRootsKt;
import org.jetbrains.kotlin.config.JVMConfigurationKeys;
import org.jetbrains.kotlin.script.StandardScriptDefinition;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestJdkKind;
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase;
import java.io.File;
import java.util.*;
public abstract class KotlinMultiFileTestWithJava<M, F> extends KtUsefulTestCase {
protected File javaFilesDir;
private File kotlinSourceRoot;
@Override
public void setUp() throws Exception {
super.setUp();
// TODO: do not create temporary directory for tests without Java sources
javaFilesDir = KotlinTestUtils.tmpDir("java-files");
if (isKotlinSourceRootNeeded()) {
kotlinSourceRoot = KotlinTestUtils.tmpDir("kotlin-src");
}
}
@Override
protected void tearDown() throws Exception {
if (javaFilesDir != null) FileUtil.delete(javaFilesDir);
if (kotlinSourceRoot != null) FileUtil.delete(kotlinSourceRoot);
super.tearDown();
}
public class ModuleAndDependencies {
final M module;
final List<String> dependencies;
final List<String> friends;
ModuleAndDependencies(M module, List<String> dependencies, List<String> friends) {
this.module = module;
this.dependencies = dependencies;
this.friends = friends;
}
}
@NotNull
protected KotlinCoreEnvironment createEnvironment(@NotNull File file) {
CompilerConfiguration configuration = KotlinTestUtils.newConfiguration(
getConfigurationKind(),
getTestJdkKind(file),
getClasspath(file),
isJavaSourceRootNeeded() ? Collections.singletonList(javaFilesDir) : Collections.emptyList()
);
configuration.add(JVMConfigurationKeys.SCRIPT_DEFINITIONS, StandardScriptDefinition.INSTANCE);
if (isKotlinSourceRootNeeded()) {
ContentRootsKt.addKotlinSourceRoot(configuration, kotlinSourceRoot.getPath());
}
performCustomConfiguration(configuration);
return KotlinCoreEnvironment.createForTests(getTestRootDisposable(), configuration, getEnvironmentConfigFiles());
}
protected boolean isJavaSourceRootNeeded() {
return true;
}
protected void performCustomConfiguration(@NotNull CompilerConfiguration configuration) {
}
@NotNull
protected ConfigurationKind getConfigurationKind() {
return ConfigurationKind.JDK_ONLY;
}
@NotNull
protected TestJdkKind getTestJdkKind(@NotNull File file) {
return InTextDirectivesUtils.isDirectiveDefined(FilesKt.readText(file, Charsets.UTF_8), "FULL_JDK")
? TestJdkKind.FULL_JDK
: TestJdkKind.MOCK_JDK;
}
private List<File> getClasspath(File file) {
List<File> result = new ArrayList<>();
result.add(KotlinTestUtils.getAnnotationsJar());
result.addAll(getExtraClasspath());
boolean loadAndroidAnnotations = InTextDirectivesUtils.isDirectiveDefined(
FilesKt.readText(file, Charsets.UTF_8), "ANDROID_ANNOTATIONS"
);
if (loadAndroidAnnotations) {
result.add(ForTestCompileRuntime.androidAnnotationsForTests());
}
return result;
}
@NotNull
protected List<File> getExtraClasspath() {
return Collections.emptyList();
}
@NotNull
protected EnvironmentConfigFiles getEnvironmentConfigFiles() {
return EnvironmentConfigFiles.JVM_CONFIG_FILES;
}
protected boolean isKotlinSourceRootNeeded() {
return false;
}
protected void doTest(String filePath) throws Exception {
File file = new File(filePath);
String expectedText = KotlinTestUtils.doLoadFile(file);
Map<String, ModuleAndDependencies> modules = new HashMap<>();
List<F> testFiles = createTestFiles(file, expectedText, modules);
doMultiFileTest(file, modules, testFiles);
}
protected abstract M createTestModule(@NotNull String name);
protected abstract F createTestFile(M module, String fileName, String text, Map<String, String> directives);
protected abstract void doMultiFileTest(File file, Map<String, ModuleAndDependencies> modules, List<F> files) throws Exception;
protected List<F> createTestFiles(File file, String expectedText, Map<String, ModuleAndDependencies> modules) {
return KotlinTestUtils.createTestFiles(file.getName(), expectedText, new KotlinTestUtils.TestFileFactory<M, F>() {
@Override
public F createFile(
@Nullable M module,
@NotNull String fileName,
@NotNull String text,
@NotNull Map<String, String> directives
) {
if (fileName.endsWith(".java")) {
writeSourceFile(fileName, text, javaFilesDir);
}
if ((fileName.endsWith(".kt") || fileName.endsWith(".kts")) && kotlinSourceRoot != null) {
writeSourceFile(fileName, text, kotlinSourceRoot);
}
return createTestFile(module, fileName, text, directives);
}
@Override
public M createModule(@NotNull String name, @NotNull List<String> dependencies, @NotNull List<String> friends) {
M module = createTestModule(name);
ModuleAndDependencies oldValue = modules.put(name, new ModuleAndDependencies(module, dependencies, friends));
assert oldValue == null : "Module " + name + " declared more than once";
return module;
}
private void writeSourceFile(@NotNull String fileName, @NotNull String content, @NotNull File targetDir) {
File file = new File(targetDir, fileName);
KotlinTestUtils.mkdirs(file.getParentFile());
FilesKt.writeText(file, content, Charsets.UTF_8);
}
});
}
}
@@ -0,0 +1,214 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.checkers
import org.jetbrains.kotlin.descriptors.Named
import org.jetbrains.kotlin.load.java.structure.JavaNamedElement
import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl
import org.jetbrains.kotlin.load.java.structure.impl.JavaTypeImpl
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.tasks.ResolutionCandidate
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.serialization.ProtoBuf
import org.jetbrains.kotlin.serialization.deserialization.DeserializationContext
import org.jetbrains.kotlin.serialization.deserialization.TypeDeserializer
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.utils.Printer
import java.lang.reflect.Constructor
import java.lang.reflect.GenericDeclaration
import java.lang.reflect.Method
import java.util.*
import java.util.regex.Pattern
class LazyOperationsLog(
val stringSanitizer: (String) -> String
) {
val ids = IdentityHashMap<Any, Int>()
private fun objectId(o: Any): Int = ids.getOrPut(o, { ids.size })
private class Record(
val lambda: Any,
val data: LoggingStorageManager.CallData
)
private val records = ArrayList<Record>()
val addRecordFunction: (lambda: Any, LoggingStorageManager.CallData) -> Unit = {
lambda, data ->
records.add(Record(lambda, data))
}
fun getText(): String {
val groupedByOwner = records.groupByTo(IdentityHashMap()) {
it.data.fieldOwner
}.map { Pair(it.key, it.value) }
return groupedByOwner.map {
val (owner, records) = it
renderOwner(owner, records)
}.sortedBy(stringSanitizer).joinToString("\n").renumberObjects()
}
/**
* Replaces ids in the given string so that they increase
* Example:
* input = "A@21 B@6"
* output = "A@0 B@1"
*/
private fun String.renumberObjects(): String {
val ids = HashMap<String, String>()
fun newId(objectId: String): String {
return ids.getOrPut(objectId, { "@" + ids.size })
}
val m = Pattern.compile("@\\d+").matcher(this)
val sb = StringBuffer()
while (m.find()) {
m.appendReplacement(sb, newId(m.group(0)))
}
m.appendTail(sb)
return sb.toString()
}
private fun renderOwner(owner: Any?, records: List<Record>): String {
val sb = StringBuilder()
with (Printer(sb)) {
println(render(owner), " {")
indent {
records.map { renderRecord(it) }.sortedBy(stringSanitizer).forEach {
println(it)
}
}
println("}")
}
return sb.toString()
}
private fun renderRecord(record: Record): String {
val data = record.data
val sb = StringBuilder()
sb.append(data.field?.name ?: "in ${data.lambdaCreatedIn.getDeclarationName()}")
if (!data.arguments.isEmpty()) {
data.arguments.joinTo(sb, ", ", "(", ")") { render(it) }
}
sb.append(" = ${render(data.result)}")
if (data.fieldOwner is MemberScope) {
sb.append(" // through ${render(data.fieldOwner)}")
}
return sb.toString()
}
private fun render(o: Any?): String {
if (o == null) return "null"
val sb = StringBuilder()
if (o is FqName || o is Name || o is String || o is Number || o is Boolean) {
sb.append("'$o': ")
}
val id = objectId(o)
val aClass = o::class.java
sb.append(if (aClass.isAnonymousClass) aClass.name.substringAfterLast('.') else aClass.simpleName).append("@$id")
fun Any.appendQuoted() {
sb.append("['").append(this).append("']")
}
when {
o is Named -> o.name.appendQuoted()
o::class.java.simpleName == "LazyJavaClassifierType" -> {
val javaType = o.field<JavaTypeImpl<*>>("javaType")
javaType.psi.presentableText.appendQuoted()
}
o::class.java.simpleName == "LazyJavaClassTypeConstructor" -> {
val javaClass = o.field<Any>("this\$0").field<JavaClassImpl>("jClass")
javaClass.psi.name!!.appendQuoted()
}
o::class.java.simpleName == "DeserializedType" -> {
val typeDeserializer = o.field<TypeDeserializer>("typeDeserializer")
val context = typeDeserializer.field<DeserializationContext>("c")
val typeProto = o.field<ProtoBuf.Type>("typeProto")
val text = when {
typeProto.hasClassName() -> context.nameResolver.getClassId(typeProto.className).asSingleFqName().asString()
typeProto.hasTypeParameter() -> {
val classifier = (o as KotlinType).constructor.declarationDescriptor!!
"" + classifier.name + " in " + DescriptorUtils.getFqName(classifier.containingDeclaration)
}
else -> "???"
}
text.appendQuoted()
}
o is JavaNamedElement -> {
o.name.appendQuoted()
}
o is JavaTypeImpl<*> -> {
o.psi.presentableText.appendQuoted()
}
o is Collection<*> -> {
if (o.isEmpty()) {
sb.append("[empty]")
}
else {
sb.append("[${o.size}] ")
o.joinTo(sb, ", ", prefix = "{", postfix = "}", limit = 3) { render(it) }
}
}
o is KotlinType -> {
StringBuilder().apply {
append(o.constructor)
if (!o.arguments.isEmpty()) {
append("<${o.arguments.size}>")
}
}.appendQuoted()
}
o is ResolutionCandidate<*> -> DescriptorRenderer.COMPACT.render(o.descriptor).appendQuoted()
}
return sb.toString()
}
}
private fun <T> Any.field(name: String): T {
val field = this::class.java.getDeclaredField(name)
field.isAccessible = true
@Suppress("UNCHECKED_CAST")
return field.get(this) as T
}
private fun Printer.indent(body: Printer.() -> Unit): Printer {
pushIndent()
body()
popIndent()
return this
}
private fun GenericDeclaration?.getDeclarationName(): String? {
return when (this) {
is Class<*> -> getName().substringAfterLast(".")
is Method -> declaringClass.getDeclarationName() + "::" + name + "()"
is Constructor<*> -> getDeclaringClass().getDeclarationName() + "::" + getName() + "()"
else -> "<no name>"
}
}
@@ -0,0 +1,122 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.checkers
import org.jetbrains.kotlin.storage.ObservableStorageManager
import org.jetbrains.kotlin.storage.StorageManager
import java.lang.reflect.Field
import java.lang.reflect.GenericDeclaration
class LoggingStorageManager(
private val delegate: StorageManager,
private val callHandler: (lambda: Any, call: LoggingStorageManager.CallData) -> Unit) : ObservableStorageManager(delegate) {
class CallData(
val fieldOwner: Any?,
val field: Field?,
val lambdaCreatedIn: GenericDeclaration?,
val arguments: List<Any?>,
val result: Any?
)
// Creating objects here because we need a reference to it
override val <T> (() -> T).observable: () -> T
get() = object : () -> T {
override fun invoke(): T {
val result = this@observable()
callHandler(this@observable, computeCallerData(this@observable, this, listOf(), result))
return result
}
}
// Creating objects here because we need a reference to it
override val <K, V> ((K) -> V).observable: (K) -> V
get() = object : (K) -> V {
override fun invoke(p1: K): V {
val result = this@observable(p1)
callHandler(this@observable, computeCallerData(this@observable, this, listOf(p1), result))
return result
}
}
private fun computeCallerData(lambda: Any, wrapper: Any, arguments: List<Any?>, result: Any?): CallData {
val lambdaClass = lambda::class.java
val outerClass: Class<out Any?>? = lambdaClass.enclosingClass
// fields named "this" or "this$0"
val referenceToOuter = lambdaClass.getAllDeclaredFields().firstOrNull {
field ->
field.type == outerClass && field.name!!.contains("this")
}
referenceToOuter?.isAccessible = true
val outerInstance = referenceToOuter?.get(lambda)
fun Class<*>.findFunctionField(): Field? {
return this.getAllDeclaredFields().firstOrNull {
it.type?.name?.startsWith("kotlin.Function") ?: false
}
}
val containingField = if (outerInstance == null) null
else outerClass?.getAllDeclaredFields()?.firstOrNull {
field ->
field.isAccessible = true
val value = field.get(outerInstance)
if (value == null) return@firstOrNull false
val valueClass = value::class.java
val functionField = valueClass.findFunctionField()
if (functionField == null) return@firstOrNull false
functionField.isAccessible = true
val functionValue = functionField.get(value)
functionValue == wrapper
}
if (containingField == null) {
val wrappedLambdaField = lambdaClass.findFunctionField()
if (wrappedLambdaField != null) {
wrappedLambdaField.isAccessible = true
val wrappedLambda = wrappedLambdaField.get(lambda)
return CallData(outerInstance, null, enclosingEntity(wrappedLambda::class.java), arguments, result)
}
}
val enclosingEntity = enclosingEntity(lambdaClass)
return CallData(outerInstance, containingField, enclosingEntity, arguments, result)
}
private fun enclosingEntity(klass: Class<out Any>): GenericDeclaration? =
klass.enclosingConstructor ?: klass.enclosingMethod ?: klass.enclosingClass
private fun Class<*>.getAllDeclaredFields(): List<Field> {
val result = arrayListOf<Field>()
var c = this
while (true) {
result.addAll(c.declaredFields.toList())
@Suppress("UNCHECKED_CAST")
val superClass = (c as Class<Any>).superclass ?: break
if (c == superClass) break
c = superClass
}
return result
}
}
@@ -0,0 +1,52 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.checkers;
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.LightVirtualFile;
import junit.framework.TestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.KotlinTestUtils;
public class TestCheckerUtil {
@NotNull
public static KtFile createCheckAndReturnPsiFile(@NotNull String fileName, @NotNull String text, @NotNull Project project) {
KtFile myFile = KotlinTestUtils.createFile(fileName, text, project);
ensureParsed(myFile);
TestCase.assertEquals("light virtual file text mismatch", text, ((LightVirtualFile) myFile.getVirtualFile()).getContent().toString());
TestCase.assertEquals("virtual file text mismatch", text, LoadTextUtil.loadText(myFile.getVirtualFile()));
//noinspection ConstantConditions
TestCase.assertEquals("doc text mismatch", text, myFile.getViewProvider().getDocument().getText());
TestCase.assertEquals("psi text mismatch", text, myFile.getText());
return myFile;
}
private static void ensureParsed(PsiFile file) {
file.accept(new PsiElementVisitor() {
@Override
public void visitElement(@NotNull PsiElement element) {
element.acceptChildren(this);
}
});
}
}
@@ -0,0 +1,47 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.checkers.javac
import org.jetbrains.kotlin.checkers.AbstractDiagnosticsTest
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils.getHomeDirectory
import java.io.File
abstract class AbstractDiagnosticsUsingJavacTest : AbstractDiagnosticsTest() {
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
val testDataFileText = testDataFile.readText()
if (InTextDirectivesUtils.isDirectiveDefined(testDataFileText, "// JAVAC_SKIP")) {
println("${testDataFile.name} test is skipped")
return
}
val groupedByModule = files.groupBy(TestFile::module)
val allKtFiles = groupedByModule.values.flatMap { getKtFiles(it, true) }
if (InTextDirectivesUtils.isDirectiveDefined(testDataFileText, "// FULL_JDK")) {
environment.registerJavac(kotlinFiles = allKtFiles)
}
else {
val mockJdk = listOf(File(getHomeDirectory(), "compiler/testData/mockJDK/jre/lib/rt.jar"))
environment.registerJavac(kotlinFiles = allKtFiles, bootClasspath = mockJdk)
}
environment.configuration.put(JVMConfigurationKeys.USE_JAVAC, true)
super.analyzeAndCheck(testDataFile, files)
}
}
@@ -0,0 +1,43 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.checkers.javac
import org.jetbrains.kotlin.checkers.AbstractDiagnosticsTest
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import java.io.File
abstract class AbstractJavacDiagnosticsTest : AbstractDiagnosticsTest() {
private var useJavac = true
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
if (useJavac) {
val groupedByModule = files.groupBy(TestFile::module)
val allKtFiles = groupedByModule.values.flatMap { getKtFiles(it, true) }
environment.registerJavac(kotlinFiles = allKtFiles)
environment.configuration.put(JVMConfigurationKeys.USE_JAVAC, true)
}
super.analyzeAndCheck(testDataFile, files)
}
fun doTestWithoutJavacWrapper(path: String) {
useJavac = false
super.doTest(path)
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.checkers.javac
import org.jetbrains.kotlin.checkers.AbstractDiagnosticsTest
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import java.io.File
abstract class AbstractJavacFieldResolutionTest : AbstractDiagnosticsTest() {
private var useJavac = true
override fun analyzeAndCheck(testDataFile: File, files: List<TestFile>) {
if (useJavac) {
val groupedByModule = files.groupBy(TestFile::module)
val allKtFiles = groupedByModule.values.flatMap { getKtFiles(it, true) }
environment.registerJavac(kotlinFiles = allKtFiles)
environment.configuration.put(JVMConfigurationKeys.USE_JAVAC, true)
}
super.analyzeAndCheck(testDataFile, files)
}
fun doTestWithoutJavacWrapper(path: String) {
useJavac = false
super.doTest(path)
}
}
@@ -0,0 +1,130 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.intellij.openapi.util.io.FileUtil;
import kotlin.io.FilesKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
import org.jetbrains.kotlin.psi.KtDeclaration;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.psi.KtNamedFunction;
import org.jetbrains.kotlin.psi.KtProperty;
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.io.File;
import java.lang.reflect.Method;
import java.util.List;
import static org.jetbrains.kotlin.codegen.TestUtilsKt.*;
import static org.jetbrains.kotlin.test.KotlinTestUtils.assertEqualsToFile;
import static org.jetbrains.kotlin.test.clientserver.TestProcessServerKt.getBoxMethodOrNull;
import static org.jetbrains.kotlin.test.clientserver.TestProcessServerKt.getGeneratedClass;
public abstract class AbstractBlackBoxCodegenTest extends CodegenTestCase {
@Override
protected void doMultiFileTest(@NotNull File wholeFile, @NotNull List<TestFile> files, @Nullable File javaFilesDir) throws Exception {
try {
compile(files, javaFilesDir);
blackBox();
}
catch (Throwable t) {
try {
// To create .txt file in case of failure
doBytecodeListingTest(wholeFile);
}
catch (Throwable ignored) {
}
throw t;
}
doBytecodeListingTest(wholeFile);
}
private void doBytecodeListingTest(@NotNull File wholeFile) throws Exception {
if (!InTextDirectivesUtils.isDirectiveDefined(FileUtil.loadFile(wholeFile), "CHECK_BYTECODE_LISTING")) return;
File expectedFile = new File(wholeFile.getParent(), FilesKt.getNameWithoutExtension(wholeFile) + ".txt");
String text =
BytecodeListingTextCollectingVisitor.Companion.getText(
classFileFactory,
new BytecodeListingTextCollectingVisitor.Filter() {
@Override
public boolean shouldWriteClass(int access, @NotNull String name) {
return !name.startsWith("helpers/");
}
@Override
public boolean shouldWriteMethod(int access, @NotNull String name, @NotNull String desc) {
return true;
}
@Override
public boolean shouldWriteField(int access, @NotNull String name, @NotNull String desc) {
return true;
}
@Override
public boolean shouldWriteInnerClass(@NotNull String name) {
return true;
}
}
);
assertEqualsToFile(expectedFile, text);
}
protected void blackBox() {
// If there are many files, the first 'box(): String' function will be executed.
GeneratedClassLoader generatedClassLoader = generateAndCreateClassLoader();
for (KtFile firstFile : myFiles.getPsiFiles()) {
String className = getFacadeFqName(firstFile);
if (className == null) continue;
Class<?> aClass = getGeneratedClass(generatedClassLoader, className);
try {
Method method = getBoxMethodOrNull(aClass);
if (method != null) {
callBoxMethodAndCheckResult(generatedClassLoader, aClass, method);
return;
}
}
catch (Throwable e) {
System.out.println(generateToText());
throw ExceptionUtilsKt.rethrow(e);
}
finally {
clearReflectionCache(generatedClassLoader);
}
}
fail("Can't find box method!");
}
@Nullable
private static String getFacadeFqName(@NotNull KtFile firstFile) {
for (KtDeclaration declaration : firstFile.getDeclarations()) {
if (declaration instanceof KtProperty || declaration instanceof KtNamedFunction) {
return JvmFileClassUtil.getFileClassInfoNoResolve(firstFile).getFacadeClassFqName().asString();
}
}
return null;
}
}
@@ -0,0 +1,232 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.Opcodes.*
import java.io.File
abstract class AbstractBytecodeListingTest : CodegenTestCase() {
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
val txtFile = File(wholeFile.parentFile, wholeFile.nameWithoutExtension + ".txt")
compile(files, javaFilesDir)
val actualTxt = BytecodeListingTextCollectingVisitor.getText(classFileFactory, withSignatures = isWithSignatures(wholeFile))
KotlinTestUtils.assertEqualsToFile(txtFile, actualTxt)
}
private fun isWithSignatures(wholeFile: File): Boolean =
WITH_SIGNATURES.containsMatchIn(wholeFile.readText())
companion object {
private val WITH_SIGNATURES = Regex.fromLiteral("// WITH_SIGNATURES")
}
}
class BytecodeListingTextCollectingVisitor(val filter: Filter, val withSignatures: Boolean) : ClassVisitor(ASM5) {
companion object {
@JvmOverloads
fun getText(
factory: ClassFileFactory,
filter: Filter = Filter.EMPTY,
replaceHash: Boolean = true,
withSignatures: Boolean = false
) =
factory.getClassFiles()
.sortedBy { it.relativePath }
.mapNotNull {
val cr = ClassReader(it.asByteArray())
val visitor = BytecodeListingTextCollectingVisitor(filter, withSignatures)
cr.accept(visitor, ClassReader.SKIP_CODE)
if (!filter.shouldWriteClass(cr.access, cr.className)) {
return@mapNotNull null
}
if (replaceHash) {
KotlinTestUtils.replaceHash(visitor.text, "HASH")
}
else {
visitor.text
}
}.joinToString("\n\n", postfix = "\n")
}
interface Filter {
fun shouldWriteClass(access: Int, name: String): Boolean
fun shouldWriteMethod(access: Int, name: String, desc: String): Boolean
fun shouldWriteField(access: Int, name: String, desc: String): Boolean
fun shouldWriteInnerClass(name: String): Boolean
object EMPTY : Filter {
override fun shouldWriteClass(access: Int, name: String) = true
override fun shouldWriteMethod(access: Int, name: String, desc: String) = true
override fun shouldWriteField(access: Int, name: String, desc: String) = true
override fun shouldWriteInnerClass(name: String) = true
}
}
private class Declaration(val text: String, val annotations: MutableList<String> = arrayListOf())
private val declarationsInsideClass = arrayListOf<Declaration>()
private val classAnnotations = arrayListOf<String>()
private var className = ""
private var classAccess = 0
private var classSignature: String? = ""
private fun addAnnotation(desc: String, list: MutableList<String> = declarationsInsideClass.last().annotations) {
val name = Type.getType(desc).className
list.add("@$name ")
}
private fun addModifier(text: String, list: MutableList<String>) {
list.add("$text ")
}
private fun handleModifiers(access: Int, list: MutableList<String> = declarationsInsideClass.last().annotations) {
if (access and ACC_PUBLIC != 0) addModifier("public", list)
if (access and ACC_PROTECTED != 0) addModifier("protected", list)
if (access and ACC_PRIVATE != 0) addModifier("private", list)
if (access and ACC_SYNTHETIC != 0) addModifier("synthetic", list)
if (access and ACC_DEPRECATED != 0) addModifier("deprecated", list)
if (access and ACC_FINAL != 0) addModifier("final", list)
if (access and ACC_ABSTRACT != 0 && access and ACC_INTERFACE == 0) addModifier("abstract", list)
if (access and ACC_STATIC != 0) addModifier("static", list)
}
private fun classOrInterface(access: Int): String {
return when {
access and ACC_ANNOTATION != 0 -> "annotation class"
access and ACC_ENUM != 0 -> "enum class"
access and ACC_INTERFACE != 0 -> "interface"
else -> "class"
}
}
val text: String
get() = StringBuilder().apply {
if (classAnnotations.isNotEmpty()) {
append(classAnnotations.joinToString("\n", postfix = "\n"))
}
arrayListOf<String>().apply { handleModifiers(classAccess, this) }.forEach { append(it) }
append(classOrInterface(classAccess))
if (withSignatures) {
append("<$classSignature> ")
}
append(" ")
append(className)
if (declarationsInsideClass.isNotEmpty()) {
append(" {\n")
for (declaration in declarationsInsideClass.sortedBy { it.text }) {
append(" ").append(declaration.annotations.joinToString("")).append(declaration.text).append("\n")
}
append("}")
}
}.toString()
override fun visitMethod(
access: Int,
name: String,
desc: String,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor? {
if (!filter.shouldWriteMethod(access, name, desc)) {
return null
}
val returnType = Type.getReturnType(desc).className
val parameterTypes = Type.getArgumentTypes(desc).map { it.className }
val methodAnnotations = arrayListOf<String>()
val parameterAnnotations = hashMapOf<Int, MutableList<String>>()
handleModifiers(access, methodAnnotations)
return object : MethodVisitor(ASM5) {
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
val type = Type.getType(desc).className
methodAnnotations += "@$type "
return super.visitAnnotation(desc, visible)
}
override fun visitParameterAnnotation(parameter: Int, desc: String, visible: Boolean): AnnotationVisitor? {
val type = Type.getType(desc).className
parameterAnnotations.getOrPut(parameter, { arrayListOf() }).add("@$type ")
return super.visitParameterAnnotation(parameter, desc, visible)
}
override fun visitEnd() {
val parameterWithAnnotations = parameterTypes.mapIndexed { index, parameter ->
val annotations = parameterAnnotations.getOrElse(index, { emptyList<String>() }).joinToString("")
"${annotations}p$index: $parameter"
}.joinToString()
val signatureIfRequired = if (withSignatures) "<$signature> " else ""
declarationsInsideClass.add(
Declaration("${signatureIfRequired}method $name($parameterWithAnnotations): $returnType", methodAnnotations)
)
super.visitEnd()
}
}
}
override fun visitField(access: Int, name: String, desc: String, signature: String?, value: Any?): FieldVisitor? {
if (!filter.shouldWriteField(access, name, desc)) {
return null
}
val type = Type.getType(desc).className
val fieldSignature = if (withSignatures) "<$signature> " else ""
val fieldDeclaration = Declaration("field $fieldSignature$name: $type")
declarationsInsideClass.add(fieldDeclaration)
handleModifiers(access)
if (access and ACC_VOLATILE != 0) addModifier("volatile", fieldDeclaration.annotations)
return object : FieldVisitor(ASM5) {
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
addAnnotation(desc)
return super.visitAnnotation(desc, visible)
}
}
}
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
val name = Type.getType(desc).className
classAnnotations.add("@$name")
return super.visitAnnotation(desc, visible)
}
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String?,
interfaces: Array<out String>?
) {
className = name
classAccess = access
classSignature = signature
}
override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) {
if (!filter.shouldWriteInnerClass(name)) {
return
}
declarationsInsideClass.add(Declaration("inner class $name"))
}
}
@@ -0,0 +1,168 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.utils.rethrow
import org.junit.Assert
import java.io.File
import java.util.*
import java.util.regex.Matcher
import java.util.regex.Pattern
abstract class AbstractBytecodeTextTest : CodegenTestCase() {
@Throws(Exception::class)
override fun doMultiFileTest(wholeFile: File, files: List<CodegenTestCase.TestFile>, javaFilesDir: File?) {
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.ALL, files, javaFilesDir)
loadMultiFiles(files)
if (isMultiFileTest(files) && !InTextDirectivesUtils.isDirectiveDefined(wholeFile.readText(), "TREAT_AS_ONE_FILE")) {
doTestMultiFile(files)
}
else {
val expected = readExpectedOccurrences(wholeFile.path)
val actual = generateToText()
checkGeneratedTextAgainstExpectedOccurrences(actual, expected)
}
}
@Throws(Exception::class)
private fun doTestMultiFile(files: List<CodegenTestCase.TestFile>) {
val expectedOccurrencesByOutputFile = LinkedHashMap<String, List<OccurrenceInfo>>()
for (file in files) {
readExpectedOccurrencesForMultiFileTest(file, expectedOccurrencesByOutputFile)
}
val generated = generateEachFileToText()
for (expectedOutputFile in expectedOccurrencesByOutputFile.keys) {
assertTextWasGenerated(expectedOutputFile, generated)
val generatedText = generated[expectedOutputFile]!!
val expectedOccurrences = expectedOccurrencesByOutputFile[expectedOutputFile]!!
checkGeneratedTextAgainstExpectedOccurrences(generatedText, expectedOccurrences)
}
}
@Throws(Exception::class)
protected fun readExpectedOccurrences(filename: String): List<OccurrenceInfo> {
val result = ArrayList<OccurrenceInfo>()
val lines = FileUtil.loadFile(File(filename), Charsets.UTF_8.name(), true).split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
for (line in lines) {
val matcher = EXPECTED_OCCURRENCES_PATTERN.matcher(line)
if (matcher.matches()) {
result.add(parseOccurrenceInfo(matcher))
}
}
return result
}
class OccurrenceInfo constructor(private val numberOfOccurrences: Int, private val needle: String) {
fun getActualOccurrence(text: String): String? {
val actualCount = StringUtil.findMatches(text, Pattern.compile("($needle)")).size
return "$actualCount $needle"
}
override fun toString(): String {
return "$numberOfOccurrences $needle"
}
}
companion object {
private val AT_OUTPUT_FILE_PATTERN = Pattern.compile("^\\s*//\\s*@(.*):$")
private val EXPECTED_OCCURRENCES_PATTERN = Pattern.compile("^\\s*//\\s*(\\d+)\\s*(.*)$")
private fun isMultiFileTest(files: List<CodegenTestCase.TestFile>): Boolean {
var kotlinFiles = 0
for (file in files) {
if (file.name.endsWith(".kt")) {
kotlinFiles++
}
}
return kotlinFiles > 1
}
fun checkGeneratedTextAgainstExpectedOccurrences(text: String,
expectedOccurrences: List<OccurrenceInfo>) {
val expected = StringBuilder()
val actual = StringBuilder()
for (info in expectedOccurrences) {
expected.append(info).append("\n")
actual.append(info.getActualOccurrence(text)).append("\n")
}
try {
Assert.assertEquals(text, expected.toString(), actual.toString())
}
catch (e: Throwable) {
println(text)
throw rethrow(e)
}
}
private fun assertTextWasGenerated(expectedOutputFile: String, generated: Map<String, String>) {
if (!generated.containsKey(expectedOutputFile)) {
val failMessage = StringBuilder()
failMessage.append("Missing output file ").append(expectedOutputFile).append(", got ").append(generated.size).append(": ")
for (generatedFile in generated.keys) {
failMessage.append(generatedFile).append(" ")
}
Assert.fail(failMessage.toString())
}
}
private fun readExpectedOccurrencesForMultiFileTest(
file: CodegenTestCase.TestFile,
occurrenceMap: MutableMap<String, List<OccurrenceInfo>>) {
var currentOccurrenceInfos: MutableList<OccurrenceInfo>? = null
for (line in file.content.split("\n".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()) {
val atOutputFileMatcher = AT_OUTPUT_FILE_PATTERN.matcher(line)
if (atOutputFileMatcher.matches()) {
val outputFileName = atOutputFileMatcher.group(1)
if (occurrenceMap.containsKey(outputFileName)) {
throw AssertionError(
file.name + ": Expected occurrences for output file " + outputFileName + " were already provided")
}
currentOccurrenceInfos = ArrayList<OccurrenceInfo>()
occurrenceMap.put(outputFileName, currentOccurrenceInfos)
}
val expectedOccurrencesMatcher = EXPECTED_OCCURRENCES_PATTERN.matcher(line)
if (expectedOccurrencesMatcher.matches()) {
if (currentOccurrenceInfos == null) {
throw AssertionError(
file.name + ": Should specify output file with '// @<OUTPUT_FILE_NAME>:' before expectations")
}
val occurrenceInfo = parseOccurrenceInfo(expectedOccurrencesMatcher)
currentOccurrenceInfos.add(occurrenceInfo)
}
}
}
private fun parseOccurrenceInfo(matcher: Matcher): OccurrenceInfo {
val numberOfOccurrences = Integer.parseInt(matcher.group(1))
val needle = matcher.group(2)
return OccurrenceInfo(numberOfOccurrences, needle)
}
}
}
@@ -0,0 +1,171 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.google.common.io.Files;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.backend.common.output.OutputFileCollection;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestCaseWithTmpdir;
import org.jetbrains.org.objectweb.asm.*;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Test correctness of written local variables in class file for specified method
*/
public abstract class AbstractCheckLocalVariablesTableTest extends TestCaseWithTmpdir {
private File ktFile;
private KotlinCoreEnvironment environment;
public AbstractCheckLocalVariablesTableTest() {
}
@Override
protected void setUp() throws Exception {
super.setUp();
environment = KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(myTestRootDisposable);
}
@Override
protected void tearDown() throws Exception {
environment = null;
super.tearDown();
}
protected void doTest(@NotNull String ktFileName) throws Exception {
ktFile = new File(ktFileName);
String text = FileUtil.loadFile(ktFile, true);
KtFile psiFile = KotlinTestUtils.createFile(ktFile.getName(), text, environment.getProject());
OutputFileCollection outputFiles = GenerationUtils.compileFile(psiFile, environment);
String classAndMethod = parseClassAndMethodSignature();
String[] split = classAndMethod.split("\\.");
assert split.length == 2 : "Exactly one dot is expected: " + classAndMethod;
String classFileRegex = StringUtil.escapeToRegexp(split[0] + ".class").replace("\\*", ".+");
String methodName = split[1];
OutputFile outputFile = ContainerUtil.find(outputFiles.asList(), file -> file.getRelativePath().matches(classFileRegex));
String pathsString = StringUtil.join(outputFiles.asList(), OutputFile::getRelativePath, ", ");
assertNotNull("Couldn't find class file for pattern " + classFileRegex + " in: " + pathsString, outputFile);
ClassReader cr = new ClassReader(outputFile.asByteArray());
List<LocalVariable> actualLocalVariables = readLocalVariable(cr, methodName);
KotlinTestUtils.assertEqualsToFile(ktFile, text.substring(0, text.indexOf("// VARIABLE : ")) + getActualVariablesAsString(actualLocalVariables));
}
private static String getActualVariablesAsString(List<LocalVariable> list) {
StringBuilder builder = new StringBuilder();
for (LocalVariable variable : list) {
builder.append(variable.toString()).append("\n");
}
return builder.toString();
}
private static class LocalVariable {
private final String name;
private final String type;
private final int index;
private LocalVariable(
@NotNull String name,
@NotNull String type,
int index
) {
this.name = name;
this.type = type;
this.index = index;
}
@Override
public String toString() {
return "// VARIABLE : NAME=" + name + " TYPE=" + type + " INDEX=" + index;
}
}
@NotNull
private static final Pattern methodPattern = Pattern.compile("^// METHOD : *(.*)");
@NotNull
private String parseClassAndMethodSignature() throws IOException {
List<String> lines = Files.readLines(ktFile, Charset.forName("utf-8"));
for (String line : lines) {
Matcher methodMatcher = methodPattern.matcher(line);
if (methodMatcher.matches()) {
return methodMatcher.group(1);
}
}
throw new AssertionError("method instructions not found");
}
@NotNull
private static List<LocalVariable> readLocalVariable(ClassReader cr, String methodName) throws Exception {
class Visitor extends ClassVisitor {
List<LocalVariable> readVariables = new ArrayList<>();
public Visitor() {
super(Opcodes.ASM5);
}
@Override
public MethodVisitor visitMethod(
int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions
) {
if (methodName.equals(name + desc)) {
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitLocalVariable(
@NotNull String name, @NotNull String desc, String signature, @NotNull Label start, @NotNull Label end, int index
) {
readVariables.add(new LocalVariable(name, desc, index));
}
};
}
else {
return super.visitMethod(access, name, desc, signature, exceptions);
}
}
}
Visitor visitor = new Visitor();
cr.accept(visitor, ClassReader.SKIP_FRAMES);
assertFalse("method not found: " + methodName, visitor.readVariables.size() == 0);
return visitor.readVariables;
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen
import java.io.File
abstract class AbstractCompileKotlinAgainstInlineKotlinTest : AbstractCompileKotlinAgainstKotlinTest() {
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
val (factory1, factory2) = doTwoFileTest(files.filter { it.name.endsWith(".kt") })
try {
val allGeneratedFiles = factory1.asList() + factory2.asList()
val sourceFiles = factory1.inputFiles + factory2.inputFiles
InlineTestUtil.checkNoCallsToInline(allGeneratedFiles.filterClassFiles(), sourceFiles)
SMAPTestUtil.checkSMAP(files, allGeneratedFiles.filterClassFiles())
}
catch (e: Throwable) {
println("FIRST:\n\n${factory1.createText()}\n\nSECOND:\n\n${factory2.createText()}")
throw e
}
}
}
@@ -0,0 +1,151 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.google.common.collect.Lists;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Disposer;
import kotlin.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.common.modules.ModuleBuilder;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.load.kotlin.ModuleVisibilityManager;
import org.jetbrains.kotlin.load.kotlin.PackagePartClassUtils;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collections;
import java.util.List;
public abstract class AbstractCompileKotlinAgainstKotlinTest extends CodegenTestCase {
private File tmpdir;
private File aDir;
private File bDir;
@Override
protected void setUp() throws Exception {
super.setUp();
tmpdir = KotlinTestUtils.tmpDirForTest(this);
aDir = new File(tmpdir, "a");
bDir = new File(tmpdir, "b");
KotlinTestUtils.mkdirs(aDir);
KotlinTestUtils.mkdirs(bDir);
}
@Override
protected void doMultiFileTest(@NotNull File wholeFile, @NotNull List<TestFile> files, @Nullable File javaFilesDir) throws Exception {
assert javaFilesDir == null : ".java files are not supported yet in this test";
doTwoFileTest(files);
}
@NotNull
protected Pair<ClassFileFactory, ClassFileFactory> doTwoFileTest(@NotNull List<TestFile> files) throws Exception {
// Note that it may be beneficial to improve this test to handle many files, compiling them successively against all previous
assert files.size() == 2 : "There should be exactly two files in this test";
TestFile fileA = files.get(0);
TestFile fileB = files.get(1);
ClassFileFactory factoryA = compileA(fileA, files);
ClassFileFactory factoryB = null;
try {
factoryB = compileB(fileB, files);
invokeBox(PackagePartClassUtils.getFilePartShortName(new File(fileB.name).getName()));
}
catch (Throwable e) {
String result = "FIRST: \n\n" + factoryA.createText();
if (factoryB != null) {
result += "\n\nSECOND: \n\n" + factoryB.createText();
}
System.out.println(result);
throw ExceptionUtilsKt.rethrow(e);
}
return new Pair<>(factoryA, factoryB);
}
private void invokeBox(@NotNull String className) throws Exception {
callBoxMethodAndCheckResult(createGeneratedClassLoader(), className);
}
@NotNull
private URLClassLoader createGeneratedClassLoader() throws Exception {
return new URLClassLoader(
new URL[]{ bDir.toURI().toURL(), aDir.toURI().toURL() },
ForTestCompileRuntime.runtimeAndReflectJarClassLoader()
);
}
@NotNull
private ClassFileFactory compileA(@NotNull TestFile testFile, List<TestFile> files) throws IOException {
Disposable compileDisposable = createDisposable("compileA");
CompilerConfiguration configuration =
createConfiguration(ConfigurationKind.ALL, getJdkKind(files),
Collections.singletonList(KotlinTestUtils.getAnnotationsJar()),
Collections.emptyList(), Collections.singletonList(testFile));
KotlinCoreEnvironment environment = KotlinCoreEnvironment.createForTests(
compileDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
return compileKotlin(testFile.name, testFile.content, aDir, environment, compileDisposable);
}
@NotNull
private ClassFileFactory compileB(@NotNull TestFile testFile, List<TestFile> files) throws IOException {
CompilerConfiguration configurationWithADirInClasspath =
createConfiguration(ConfigurationKind.ALL, getJdkKind(files),
Lists.newArrayList(KotlinTestUtils.getAnnotationsJar(), aDir),
Collections.emptyList(), Collections.singletonList(testFile));
Disposable compileDisposable = createDisposable("compileB");
KotlinCoreEnvironment environment = KotlinCoreEnvironment.createForTests(
compileDisposable, configurationWithADirInClasspath, EnvironmentConfigFiles.JVM_CONFIG_FILES
);
return compileKotlin(testFile.name, testFile.content, bDir, environment, compileDisposable);
}
private Disposable createDisposable(String debugName) {
Disposable disposable = Disposer.newDisposable("CompileDisposable" + debugName);
Disposer.register(getTestRootDisposable(), disposable);
return disposable;
}
@NotNull
private ClassFileFactory compileKotlin(
@NotNull String fileName, @NotNull String content, @NotNull File outputDir, @NotNull KotlinCoreEnvironment environment,
@NotNull Disposable disposable
) throws IOException {
KtFile psiFile = KotlinTestUtils.createFile(fileName, content, environment.getProject());
ModuleVisibilityManager.SERVICE.getInstance(environment.getProject()).addModule(
new ModuleBuilder("module for test", tmpdir.getAbsolutePath(), "test")
);
ClassFileFactory outputFiles = GenerationUtils.compileFileTo(psiFile, environment, outputDir);
Disposer.dispose(disposable);
return outputFiles;
}
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractDumpDeclarationsTest : CodegenTestCase() {
private lateinit var dumpToFile: File
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
val expectedResult = KotlinTestUtils.replaceExtension(wholeFile, "json")
dumpToFile = KotlinTestUtils.tmpDirForTest(this).resolve(name + ".json")
compile(files, null)
classFileFactory.generationState.destroy()
KotlinTestUtils.assertEqualsToFile(expectedResult, dumpToFile.readText())
}
override fun updateConfiguration(configuration: CompilerConfiguration) {
configuration.put(JVMConfigurationKeys.DECLARATIONS_JSON_PATH, dumpToFile.path)
}
override fun extractConfigurationKind(files: MutableList<TestFile>): ConfigurationKind {
return ConfigurationKind.NO_KOTLIN_REFLECT
}
}
@@ -0,0 +1,134 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.`when`.WhenByEnumsMapping.MAPPINGS_CLASS_NAME_POSTFIX
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.extensions.AnalysisHandlerExtension
import org.jetbrains.kotlin.resolve.jvm.extensions.PartialAnalysisHandlerExtension
import org.jetbrains.kotlin.test.KotlinTestUtils.getAnnotationsJar
import org.jetbrains.org.objectweb.asm.Opcodes.*
import java.io.File
abstract class AbstractLightAnalysisModeTest : CodegenTestCase() {
private companion object {
var TEST_LIGHT_ANALYSIS: ClassBuilderFactory = object : ClassBuilderFactories.TestClassBuilderFactory(false) {
override fun getClassBuilderMode() = ClassBuilderMode.LIGHT_ANALYSIS_FOR_TESTS
}
}
override fun doMultiFileTest(wholeFile: File, files: List<CodegenTestCase.TestFile>, javaFilesDir: File?) {
for (file in files) {
if (file.content.contains("// IGNORE_LIGHT_ANALYSIS")) {
return
}
}
val fullTxt = compileWithFullAnalysis(files, javaFilesDir)
.replace("final enum class", "enum class")
val liteTxt = compileWithLightAnalysis(wholeFile, files, javaFilesDir)
.replace("@synthetic.kotlin.jvm.GeneratedByJvmOverloads ", "")
assertEquals(fullTxt, liteTxt)
}
override fun verifyWithDex(): Boolean {
return false
}
private fun compileWithLightAnalysis(wholeFile: File, files: List<CodegenTestCase.TestFile>, javaFilesDir: File?): String {
val boxTestsDir = File("compiler/testData/codegen/box")
val relativePath = wholeFile.toRelativeString(boxTestsDir)
// Fail if this test is not under codegen/box
assert(!relativePath.startsWith(".."))
val configuration = createConfiguration(
configurationKind, getJdkKind(files), listOf(getAnnotationsJar()), javaFilesDir?.let(::listOf).orEmpty(), files
)
val environment = KotlinCoreEnvironment.createForTests(testRootDisposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES)
AnalysisHandlerExtension.registerExtension(environment.project, PartialAnalysisHandlerExtension())
val testFiles = loadMultiFiles(files, environment.project)
val classFileFactory = GenerationUtils.compileFiles(testFiles.psiFiles, environment, TEST_LIGHT_ANALYSIS).factory
return BytecodeListingTextCollectingVisitor.getText(classFileFactory, ListAnalysisFilter(), replaceHash = false)
}
protected fun compileWithFullAnalysis(
files: List<TestFile>,
javaSourceDir: File?
): String {
compile(files, javaSourceDir)
classFileFactory.getClassFiles()
val classInternalNames = classFileFactory.generationState.bindingContext
.getSliceContents(CodegenBinding.ASM_TYPE).map { it.value.internalName to it.key }.toMap()
return BytecodeListingTextCollectingVisitor.getText(classFileFactory, object : ListAnalysisFilter() {
override fun shouldWriteClass(access: Int, name: String): Boolean {
val classDescriptor = classInternalNames[name]
if (classDescriptor != null && shouldFilterClass(classDescriptor)) {
return false
}
return super.shouldWriteClass(access, name)
}
override fun shouldWriteInnerClass(name: String): Boolean {
val classDescriptor = classInternalNames[name]
if (classDescriptor != null && shouldFilterClass(classDescriptor)) {
return false
}
return super.shouldWriteInnerClass(name)
}
private fun shouldFilterClass(descriptor: ClassDescriptor): Boolean {
return descriptor.visibility == Visibilities.LOCAL || descriptor is SyntheticClassDescriptorForLambda
}
}, replaceHash = false)
}
private open class ListAnalysisFilter : BytecodeListingTextCollectingVisitor.Filter {
override fun shouldWriteClass(access: Int, name: String) = when {
name.endsWith(MAPPINGS_CLASS_NAME_POSTFIX) && (access and ACC_SYNTHETIC != 0) && (access and ACC_FINAL != 0) -> false
name.contains("\$\$inlined") && (access and ACC_FINAL != 0) -> false
name.contains("\$sam\$") -> false
else -> true
}
override fun shouldWriteMethod(access: Int, name: String, desc: String) = when {
name == "<clinit>" -> false
AsmTypes.DEFAULT_CONSTRUCTOR_MARKER.descriptor in desc -> false
name.startsWith("access$") && (access and ACC_STATIC != 0) && (access and ACC_SYNTHETIC != 0) -> false
else -> true
}
override fun shouldWriteField(access: Int, name: String, desc: String) = when {
name == "\$VALUES" && (access and ACC_PRIVATE != 0) && (access and ACC_FINAL != 0) && (access and ACC_SYNTHETIC != 0) -> false
name == JvmAbi.DELEGATED_PROPERTIES_ARRAY_NAME && (access and ACC_SYNTHETIC != 0) -> false
else -> true
}
override fun shouldWriteInnerClass(name: String) = true
}
}
@@ -0,0 +1,225 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import kotlin.Pair;
import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestCaseWithTmpdir;
import org.jetbrains.kotlin.test.TestJdkKind;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.jetbrains.org.objectweb.asm.*;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public abstract class AbstractLineNumberTest extends TestCaseWithTmpdir {
private static final String LINE_NUMBER_FUN = "lineNumber";
private static final Pattern TEST_LINE_NUMBER_PATTERN = Pattern.compile("^.*test." + LINE_NUMBER_FUN + "\\(\\).*$");
@NotNull
private KotlinCoreEnvironment createEnvironment() {
return KotlinCoreEnvironment.createForTests(
myTestRootDisposable,
KotlinTestUtils.newConfiguration(
ConfigurationKind.JDK_ONLY, TestJdkKind.MOCK_JDK, KotlinTestUtils.getAnnotationsJar(), tmpdir
),
EnvironmentConfigFiles.JVM_CONFIG_FILES
);
}
@Override
public void setUp() throws Exception {
super.setUp();
KotlinCoreEnvironment environment = createEnvironment();
KtFile psiFile = KotlinTestUtils.createFile(
LINE_NUMBER_FUN + ".kt",
"package test;\n\npublic fun " + LINE_NUMBER_FUN + "(): Int = 0\n",
environment.getProject()
);
GenerationUtils.compileFileTo(psiFile, environment, tmpdir);
}
@NotNull
private Pair<KtFile, KotlinCoreEnvironment> createPsiFile(@NotNull String filename) {
File file = new File(filename);
KotlinCoreEnvironment environment = createEnvironment();
String text;
try {
text = FileUtil.loadFile(file, true);
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
return new Pair<>(KotlinTestUtils.createFile(file.getName(), text, environment.getProject()), environment);
}
private void doTest(@NotNull String filename, boolean custom) {
Pair<KtFile, KotlinCoreEnvironment> fileAndEnv = createPsiFile(filename);
KtFile psiFile = fileAndEnv.getFirst();
KotlinCoreEnvironment environment = fileAndEnv.getSecond();
ClassFileFactory classFileFactory = GenerationUtils.compileFile(psiFile, environment);
try {
if (custom) {
List<String> actualLineNumbers = extractActualLineNumbersFromBytecode(classFileFactory, false);
String text = psiFile.getText();
String newFileText = text.substring(0, text.indexOf("// ")) + getActualLineNumbersAsString(actualLineNumbers);
KotlinTestUtils.assertEqualsToFile(new File(filename), newFileText);
}
else {
List<String> expectedLineNumbers = extractSelectedLineNumbersFromSource(psiFile);
List<String> actualLineNumbers = extractActualLineNumbersFromBytecode(classFileFactory, true);
assertSameElements(actualLineNumbers, expectedLineNumbers);
}
} catch (Throwable e) {
System.out.println(classFileFactory.createText());
throw ExceptionUtilsKt.rethrow(e);
}
}
private static String getActualLineNumbersAsString(List<String> lines) {
return CollectionsKt.joinToString(lines, " ", "// ", "", -1, "...", lineNumber -> lineNumber);
}
@NotNull
private static List<String> extractActualLineNumbersFromBytecode(@NotNull ClassFileFactory factory, boolean testFunInvoke) {
List<String> actualLineNumbers = Lists.newArrayList();
for (OutputFile outputFile : ClassFileUtilsKt.getClassFiles(factory)) {
ClassReader cr = new ClassReader(outputFile.asByteArray());
try {
List<String> lineNumbers = testFunInvoke ? readTestFunLineNumbers(cr) : readAllLineNumbers(cr);
actualLineNumbers.addAll(lineNumbers);
}
catch (Throwable e) {
System.out.println(factory.createText());
throw ExceptionUtilsKt.rethrow(e);
}
}
return actualLineNumbers;
}
protected void doTest(String path) {
doTest(path, false);
}
protected void doTestCustom(String path) {
doTest(path, true);
}
@NotNull
private static List<String> extractSelectedLineNumbersFromSource(@NotNull KtFile file) {
String fileContent = file.getText();
List<String> lineNumbers = Lists.newArrayList();
String[] lines = StringUtil.convertLineSeparators(fileContent).split("\n");
for (int i = 0; i < lines.length; i++) {
Matcher matcher = TEST_LINE_NUMBER_PATTERN.matcher(lines[i]);
if (matcher.matches()) {
lineNumbers.add(Integer.toString(i + 1));
}
}
return lineNumbers;
}
@NotNull
private static List<String> readTestFunLineNumbers(@NotNull ClassReader cr) {
List<Label> labels = Lists.newArrayList();
Map<Label, String> labels2LineNumbers = Maps.newHashMap();
ClassVisitor visitor = new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM5) {
private Label lastLabel;
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
if (LINE_NUMBER_FUN.equals(name)) {
assert lastLabel != null : "A function call with no preceding label";
labels.add(lastLabel);
}
lastLabel = null;
}
@Override
public void visitLabel(@NotNull Label label) {
lastLabel = label;
}
@Override
public void visitLineNumber(int line, @NotNull Label start) {
labels2LineNumbers.put(start, Integer.toString(line));
}
};
}
};
cr.accept(visitor, ClassReader.SKIP_FRAMES);
List<String> lineNumbers = Lists.newArrayList();
for (Label label : labels) {
String lineNumber = labels2LineNumbers.get(label);
assert lineNumber != null : "No line number found for a label";
lineNumbers.add(lineNumber);
}
return lineNumbers;
}
@NotNull
private static List<String> readAllLineNumbers(@NotNull ClassReader reader) {
List<String> result = new ArrayList<>();
Set<String> visitedLabels = new HashSet<>();
reader.accept(new ClassVisitor(Opcodes.ASM5) {
@Override
public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitLineNumber(int line, @NotNull Label label) {
boolean overrides = !visitedLabels.add(label.toString());
result.add((overrides ? "+" : "") + line);
}
};
}
}, ClassReader.SKIP_FRAMES);
return result;
}
}
@@ -0,0 +1,84 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.intellij.openapi.util.Pair;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
public abstract class AbstractScriptCodegenTest extends CodegenTestCase {
@Override
protected void setUp() throws Exception {
super.setUp();
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.JDK_ONLY);
}
@Override
protected void doTest(@NotNull String filename) {
loadFileByFullPath(filename);
try {
//noinspection ConstantConditions
FqName fqName = myFiles.getPsiFile().getScript().getFqName();
Class<?> scriptClass = generateClass(fqName.asString());
Constructor constructor = getTheOnlyConstructor(scriptClass);
Object scriptInstance = constructor.newInstance(myFiles.getScriptParameterValues().toArray());
assertFalse("expecting at least one expectation", myFiles.getExpectedValues().isEmpty());
for (Pair<String, String> nameValue : myFiles.getExpectedValues()) {
String fieldName = nameValue.first;
String expectedValue = nameValue.second;
if (expectedValue.equals("<nofield>")) {
try {
scriptClass.getDeclaredField(fieldName);
fail("must have no field " + fieldName);
}
catch (NoSuchFieldException e) {
continue;
}
}
Field field = scriptClass.getDeclaredField(fieldName);
field.setAccessible(true);
Object result = field.get(scriptInstance);
String resultString = result != null ? result.toString() : "null";
assertEquals("comparing field " + fieldName, expectedValue, resultString);
}
}
catch (Throwable e) {
System.out.println(generateToText());
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
private static Constructor getTheOnlyConstructor(@NotNull Class<?> clazz) {
Constructor[] constructors = clazz.getConstructors();
if (constructors.length != 1) {
throw new IllegalArgumentException("Script class should have one constructor: " + clazz);
}
return constructors[0];
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.ArrayUtil;
import kotlin.collections.CollectionsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.MockLibraryUtil;
import org.jetbrains.kotlin.test.TestJdkKind;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public abstract class AbstractTopLevelMembersInvocationTest extends AbstractBytecodeTextTest {
private static final String LIBRARY = "library";
@Override
public void doTest(@NotNull String filename) throws Exception {
File root = new File(filename);
List<String> sourceFiles = new ArrayList<>(2);
FileUtil.processFilesRecursively(root, file -> {
if (file.getName().endsWith(".kt")) {
sourceFiles.add(relativePath(file));
return true;
}
return true;
}, file -> !LIBRARY.equals(file.getName()));
File library = new File(root, LIBRARY);
List<File> classPath =
library.exists()
? Collections.singletonList(MockLibraryUtil.compileJvmLibraryToJar(library.getPath(), LIBRARY))
: Collections.emptyList();
assert !sourceFiles.isEmpty() : getTestName(true) + " should contain at least one .kt file";
Collections.sort(sourceFiles);
myEnvironment = KotlinCoreEnvironment.createForTests(
getTestRootDisposable(),
KotlinTestUtils.newConfiguration(
ConfigurationKind.JDK_ONLY, TestJdkKind.MOCK_JDK,
CollectionsKt.plus(classPath, KotlinTestUtils.getAnnotationsJar()), Collections.emptyList()
),
EnvironmentConfigFiles.JVM_CONFIG_FILES);
loadFiles(ArrayUtil.toStringArray(sourceFiles));
List<OccurrenceInfo> expected = readExpectedOccurrences(KotlinTestUtils.getTestDataPathBase() + "/codegen/" + sourceFiles.get(0));
String actual = generateToText();
Companion.checkGeneratedTextAgainstExpectedOccurrences(actual, expected);
}
}
@@ -0,0 +1,793 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.testFramework.TestDataFile;
import kotlin.collections.ArraysKt;
import kotlin.collections.CollectionsKt;
import kotlin.io.FilesKt;
import kotlin.script.experimental.dependencies.ScriptDependencies;
import kotlin.text.Charsets;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.backend.common.output.SimpleOutputFileCollection;
import org.jetbrains.kotlin.checkers.CheckerTestUtil;
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys;
import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.config.*;
import org.jetbrains.kotlin.fileClasses.JvmFileClassUtil;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.script.ScriptDependenciesProvider;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestJdkKind;
import org.jetbrains.kotlin.test.clientserver.TestProxy;
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.jetbrains.org.objectweb.asm.ClassReader;
import org.jetbrains.org.objectweb.asm.tree.ClassNode;
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
import org.jetbrains.org.objectweb.asm.tree.analysis.Analyzer;
import org.jetbrains.org.objectweb.asm.tree.analysis.AnalyzerException;
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue;
import org.jetbrains.org.objectweb.asm.tree.analysis.SimpleVerifier;
import org.jetbrains.org.objectweb.asm.util.Textifier;
import org.jetbrains.org.objectweb.asm.util.TraceMethodVisitor;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jetbrains.kotlin.checkers.CompilerTestLanguageVersionSettingsKt.parseLanguageVersionSettings;
import static org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt.writeAllTo;
import static org.jetbrains.kotlin.codegen.CodegenTestUtil.*;
import static org.jetbrains.kotlin.codegen.TestUtilsKt.*;
import static org.jetbrains.kotlin.test.KotlinTestUtils.getAnnotationsJar;
import static org.jetbrains.kotlin.test.clientserver.TestProcessServerKt.getBoxMethodOrNull;
import static org.jetbrains.kotlin.test.clientserver.TestProcessServerKt.getGeneratedClass;
public abstract class CodegenTestCase extends KtUsefulTestCase {
private static final String DEFAULT_TEST_FILE_NAME = "a_test";
private static final String DEFAULT_JVM_TARGET_FOR_TEST = "kotlin.test.default.jvm.target";
private static final String JAVA_COMPILATION_TARGET = "kotlin.test.java.compilation.target";
public static final String RUN_BOX_TEST_IN_SEPARATE_PROCESS_PORT = "kotlin.test.box.in.separate.process.port";
protected KotlinCoreEnvironment myEnvironment;
protected CodegenTestFiles myFiles;
protected ClassFileFactory classFileFactory;
protected GeneratedClassLoader initializedClassLoader;
protected File javaClassesOutputDirectory = null;
protected List<File> additionalDependencies = null;
protected ConfigurationKind configurationKind = ConfigurationKind.JDK_ONLY;
private final String defaultJvmTarget = System.getProperty(DEFAULT_JVM_TARGET_FOR_TEST);
private final String boxInSeparateProcessPort = System.getProperty(RUN_BOX_TEST_IN_SEPARATE_PROCESS_PORT);
private final String javaCompilationTarget = System.getProperty(JAVA_COMPILATION_TARGET);
protected final void createEnvironmentWithMockJdkAndIdeaAnnotations(
@NotNull ConfigurationKind configurationKind,
@Nullable File... javaSourceRoots
) {
createEnvironmentWithMockJdkAndIdeaAnnotations(configurationKind, Collections.emptyList(), javaSourceRoots);
}
@NotNull
protected static TestJdkKind getJdkKind(@NotNull List<TestFile> files) {
for (TestFile file : files) {
if (InTextDirectivesUtils.isDirectiveDefined(file.content, "FULL_JDK")) {
return TestJdkKind.FULL_JDK;
}
}
return TestJdkKind.MOCK_JDK;
}
protected final void createEnvironmentWithMockJdkAndIdeaAnnotations(
@NotNull ConfigurationKind configurationKind,
@NotNull List<TestFile> testFilesWithConfigurationDirectives,
@Nullable File... javaSourceRoots
) {
if (myEnvironment != null) {
throw new IllegalStateException("must not set up myEnvironment twice");
}
CompilerConfiguration configuration = createConfiguration(
configurationKind,
TestJdkKind.MOCK_JDK,
Collections.singletonList(getAnnotationsJar()),
ArraysKt.filterNotNull(javaSourceRoots),
testFilesWithConfigurationDirectives
);
myEnvironment = KotlinCoreEnvironment.createForTests(
getTestRootDisposable(), configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES
);
}
@NotNull
protected CompilerConfiguration createConfiguration(
@NotNull ConfigurationKind kind,
@NotNull TestJdkKind jdkKind,
@NotNull List<File> classpath,
@NotNull List<File> javaSource,
@NotNull List<TestFile> testFilesWithConfigurationDirectives
) {
CompilerConfiguration configuration = KotlinTestUtils.newConfiguration(kind, jdkKind, classpath, javaSource);
updateConfigurationByDirectivesInTestFiles(testFilesWithConfigurationDirectives, configuration);
updateConfiguration(configuration);
setCustomDefaultJvmTarget(configuration);
return configuration;
}
private static void updateConfigurationByDirectivesInTestFiles(
@NotNull List<TestFile> testFilesWithConfigurationDirectives,
@NotNull CompilerConfiguration configuration
) {
LanguageVersionSettings explicitLanguageVersionSettings = null;
LanguageVersion explicitLanguageVersion = null;
List<String> kotlinConfigurationFlags = new ArrayList<>(0);
for (TestFile testFile : testFilesWithConfigurationDirectives) {
kotlinConfigurationFlags.addAll(InTextDirectivesUtils.findListWithPrefixes(testFile.content, "// KOTLIN_CONFIGURATION_FLAGS:"));
List<String> lines = InTextDirectivesUtils.findLinesWithPrefixesRemoved(testFile.content, "// JVM_TARGET:");
if (!lines.isEmpty()) {
String targetString = CollectionsKt.single(lines);
JvmTarget jvmTarget = JvmTarget.Companion.fromString(targetString);
assert jvmTarget != null : "Unknown target: " + targetString;
configuration.put(JVMConfigurationKeys.JVM_TARGET, jvmTarget);
}
String version = InTextDirectivesUtils.findStringWithPrefixes(testFile.content, "// LANGUAGE_VERSION:");
if (version != null) {
assertDirectivesToNull(explicitLanguageVersionSettings, explicitLanguageVersion);
explicitLanguageVersion = LanguageVersion.fromVersionString(version);
}
Map<String, String> directives = KotlinTestUtils.parseDirectives(testFile.content);
LanguageVersionSettings fileLanguageVersionSettings = parseLanguageVersionSettings(directives);
if (fileLanguageVersionSettings != null) {
assertDirectivesToNull(explicitLanguageVersionSettings, explicitLanguageVersion);
explicitLanguageVersionSettings = fileLanguageVersionSettings;
}
}
if (explicitLanguageVersionSettings != null) {
CommonConfigurationKeysKt.setLanguageVersionSettings(configuration, explicitLanguageVersionSettings);
}
else if (explicitLanguageVersion != null) {
CommonConfigurationKeysKt.setLanguageVersionSettings(
configuration,
new LanguageVersionSettingsImpl(explicitLanguageVersion, ApiVersion.createByLanguageVersion(explicitLanguageVersion))
);
}
updateConfigurationWithFlags(configuration, kotlinConfigurationFlags);
}
private static void assertDirectivesToNull(@Nullable LanguageVersionSettings settings, @Nullable LanguageVersion version) {
assert settings == null && version == null : "Should not specify LANGUAGE_VERSION twice or together with !LANGUAGE directive";
}
private static final Map<String, Class<?>> FLAG_NAMESPACE_TO_CLASS = ImmutableMap.of(
"CLI", CLIConfigurationKeys.class,
"JVM", JVMConfigurationKeys.class
);
private static final List<Class<?>> FLAG_CLASSES = ImmutableList.of(CLIConfigurationKeys.class, JVMConfigurationKeys.class);
private static final Pattern BOOLEAN_FLAG_PATTERN = Pattern.compile("([+-])(([a-zA-Z_0-9]*)\\.)?([a-zA-Z_0-9]*)");
private static final Pattern CONSTRUCTOR_CALL_NORMALIZATION_MODE_FLAG_PATTERN = Pattern.compile(
"CONSTRUCTOR_CALL_NORMALIZATION_MODE=([a-zA-Z_0-9]*)");
private static void updateConfigurationWithFlags(@NotNull CompilerConfiguration configuration, @NotNull List<String> flags) {
for (String flag : flags) {
Matcher m = BOOLEAN_FLAG_PATTERN.matcher(flag);
if (m.matches()) {
boolean flagEnabled = !"-".equals(m.group(1));
String flagNamespace = m.group(3);
String flagName = m.group(4);
tryApplyBooleanFlag(configuration, flag, flagEnabled, flagNamespace, flagName);
continue;
}
m = CONSTRUCTOR_CALL_NORMALIZATION_MODE_FLAG_PATTERN.matcher(flag);
if (m.matches()) {
String flagValueString = m.group(1);
JVMConstructorCallNormalizationMode mode = JVMConstructorCallNormalizationMode.fromStringOrNull(flagValueString);
assert mode != null : "Wrong CONSTRUCTOR_CALL_NORMALIZATION_MODE value: " + flagValueString;
configuration.put(JVMConfigurationKeys.CONSTRUCTOR_CALL_NORMALIZATION_MODE, mode);
}
}
}
private static void tryApplyBooleanFlag(
@NotNull CompilerConfiguration configuration,
@NotNull String flag,
boolean flagEnabled,
@Nullable String flagNamespace,
@NotNull String flagName
) {
Class<?> configurationKeysClass;
Field configurationKeyField = null;
if (flagNamespace == null) {
for (Class<?> flagClass : FLAG_CLASSES) {
try {
configurationKeyField = flagClass.getField(flagName);
break;
}
catch (Exception ignored) {
}
}
}
else {
configurationKeysClass = FLAG_NAMESPACE_TO_CLASS.get(flagNamespace);
assert configurationKeysClass != null : "Expected [+|-][namespace.]configurationKey, got: " + flag;
try {
configurationKeyField = configurationKeysClass.getField(flagName);
}
catch (Exception e) {
configurationKeyField = null;
}
}
assert configurationKeyField != null : "Expected [+|-][namespace.]configurationKey, got: " + flag;
try {
//noinspection unchecked
CompilerConfigurationKey<Boolean> configurationKey = (CompilerConfigurationKey<Boolean>) configurationKeyField.get(null);
configuration.put(configurationKey, flagEnabled);
}
catch (Exception e) {
assert false : "Expected [+|-][namespace.]configurationKey, got: " + flag;
}
}
@Override
protected void tearDown() throws Exception {
myFiles = null;
myEnvironment = null;
classFileFactory = null;
if (initializedClassLoader != null) {
initializedClassLoader.dispose();
initializedClassLoader = null;
}
super.tearDown();
}
protected void loadText(@NotNull String text) {
myFiles = CodegenTestFiles.create(DEFAULT_TEST_FILE_NAME + ".kt", text, myEnvironment.getProject());
}
@NotNull
protected String loadFile(@NotNull @TestDataFile String name) {
return loadFileByFullPath(KotlinTestUtils.getTestDataPathBase() + "/codegen/" + name);
}
@NotNull
protected String loadFileByFullPath(@NotNull String fullPath) {
try {
File file = new File(fullPath);
String content = FileUtil.loadFile(file, Charsets.UTF_8.name(), true);
assert myFiles == null : "Should not initialize myFiles twice";
myFiles = CodegenTestFiles.create(file.getName(), content, myEnvironment.getProject());
return content;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
protected void loadFiles(@NotNull String... names) {
myFiles = CodegenTestFiles.create(myEnvironment.getProject(), names);
}
protected void loadFile() {
loadFile(getPrefix() + "/" + getTestName(true) + ".kt");
}
protected void loadMultiFiles(@NotNull List<TestFile> files) {
myFiles = loadMultiFiles(files, myEnvironment.getProject());
}
@NotNull
public static CodegenTestFiles loadMultiFiles(@NotNull List<TestFile> files, @NotNull Project project) {
Collections.sort(files);
List<KtFile> ktFiles = new ArrayList<>(files.size());
for (TestFile file : files) {
if (file.name.endsWith(".kt")) {
String content = CheckerTestUtil.parseDiagnosedRanges(file.content, new ArrayList<>(0));
ktFiles.add(KotlinTestUtils.createFile(file.name, content, project));
}
}
return CodegenTestFiles.create(ktFiles);
}
@NotNull
protected String codegenTestBasePath() {
return "compiler/testData/codegen/";
}
@NotNull
protected String relativePath(@NotNull File file) {
return FilesKt.toRelativeString(file.getAbsoluteFile(), new File(codegenTestBasePath()).getAbsoluteFile());
}
@NotNull
protected String getPrefix() {
throw new UnsupportedOperationException();
}
@NotNull
protected GeneratedClassLoader generateAndCreateClassLoader() {
if (initializedClassLoader != null) {
fail("Double initialization of class loader in same test");
}
initializedClassLoader = createClassLoader();
if (!verifyAllFilesWithAsm(generateClassesInFile(), initializedClassLoader)) {
fail("Verification failed: see exceptions above");
}
return initializedClassLoader;
}
@NotNull
protected GeneratedClassLoader createClassLoader() {
return new GeneratedClassLoader(
generateClassesInFile(),
configurationKind.getWithReflection() ? ForTestCompileRuntime.runtimeAndReflectJarClassLoader()
: ForTestCompileRuntime.runtimeJarClassLoader(),
getClassPathURLs()
);
}
@NotNull
protected URL[] getClassPathURLs() {
List<File> files = new ArrayList<>();
if (javaClassesOutputDirectory != null) {
files.add(javaClassesOutputDirectory);
}
if (additionalDependencies != null) {
files.addAll(additionalDependencies);
}
ScriptDependenciesProvider externalImportsProvider =
ScriptDependenciesProvider.Companion.getInstance(myEnvironment.getProject());
myEnvironment.getSourceFiles().forEach(
file -> {
ScriptDependencies dependencies = externalImportsProvider.getScriptDependencies(file);
if (dependencies != null) {
files.addAll(dependencies.getClasspath());
}
}
);
try {
URL[] result = new URL[files.size()];
for (int i = 0; i < files.size(); i++) {
result[i] = files.get(i).toURI().toURL();
}
return result;
}
catch (MalformedURLException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
protected String generateToText() {
if (classFileFactory == null) {
classFileFactory = generateFiles(myEnvironment, myFiles);
}
return classFileFactory.createText();
}
@NotNull
protected Map<String, String> generateEachFileToText() {
if (classFileFactory == null) {
classFileFactory = generateFiles(myEnvironment, myFiles);
}
return classFileFactory.createTextForEachFile();
}
@NotNull
protected Class<?> generateFacadeClass() {
FqName facadeClassFqName = JvmFileClassUtil.getFileClassInfoNoResolve(myFiles.getPsiFile()).getFacadeClassFqName();
return generateClass(facadeClassFqName.asString());
}
@NotNull
protected Class<?> generateClass(@NotNull String name) {
try {
return generateAndCreateClassLoader().loadClass(name);
}
catch (ClassNotFoundException e) {
fail("No class file was generated for: " + name);
return null;
}
}
@NotNull
protected ClassFileFactory generateClassesInFile() {
if (classFileFactory == null) {
try {
classFileFactory =
GenerationUtils.compileFiles(myFiles.getPsiFiles(), myEnvironment, getClassBuilderFactory()).getFactory();
if (verifyWithDex() && DxChecker.RUN_DX_CHECKER) {
DxChecker.check(classFileFactory);
}
}
catch (Throwable e) {
e.printStackTrace();
System.err.println("Generating instructions as text...");
try {
if (classFileFactory == null) {
System.err.println("Cannot generate text: exception was thrown during generation");
}
else {
System.err.println(classFileFactory.createText());
}
}
catch (Throwable e1) {
System.err.println("Exception thrown while trying to generate text, the actual exception follows:");
e1.printStackTrace();
System.err.println("-----------------------------------------------------------------------------");
}
fail("See exceptions above");
}
}
return classFileFactory;
}
protected boolean verifyWithDex() {
return true;
}
private static boolean verifyAllFilesWithAsm(ClassFileFactory factory, ClassLoader loader) {
boolean noErrors = true;
for (OutputFile file : ClassFileUtilsKt.getClassFiles(factory)) {
noErrors &= verifyWithAsm(file, loader);
}
return noErrors;
}
private static boolean verifyWithAsm(@NotNull OutputFile file, ClassLoader loader) {
ClassNode classNode = new ClassNode();
new ClassReader(file.asByteArray()).accept(classNode, 0);
SimpleVerifier verifier = new SimpleVerifier();
verifier.setClassLoader(loader);
Analyzer<BasicValue> analyzer = new Analyzer<>(verifier);
boolean noErrors = true;
for (MethodNode method : classNode.methods) {
try {
analyzer.analyze(classNode.name, method);
}
catch (Throwable e) {
System.err.println(file.asText());
System.err.println(classNode.name + "::" + method.name + method.desc);
//noinspection InstanceofCatchParameter
if (e instanceof AnalyzerException) {
// Print the erroneous instruction
TraceMethodVisitor tmv = new TraceMethodVisitor(new Textifier());
((AnalyzerException) e).node.accept(tmv);
PrintWriter pw = new PrintWriter(System.err);
tmv.p.print(pw);
pw.flush();
}
e.printStackTrace();
noErrors = false;
}
}
return noErrors;
}
@NotNull
protected Method generateFunction() {
Class<?> aClass = generateFacadeClass();
try {
return findTheOnlyMethod(aClass);
} catch (Error e) {
System.out.println(generateToText());
throw e;
}
}
@NotNull
protected Method generateFunction(@NotNull String name) {
return findDeclaredMethodByName(generateFacadeClass(), name);
}
@NotNull
public Class<? extends Annotation> loadAnnotationClassQuietly(@NotNull String fqName) {
try {
//noinspection unchecked
return (Class<? extends Annotation>) initializedClassLoader.loadClass(fqName);
}
catch (ClassNotFoundException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
protected void updateConfiguration(@NotNull CompilerConfiguration configuration) {
}
protected ClassBuilderFactory getClassBuilderFactory(){
return ClassBuilderFactories.TEST;
}
protected void setupEnvironment(@NotNull KotlinCoreEnvironment environment) {
}
protected void setCustomDefaultJvmTarget(CompilerConfiguration configuration) {
JvmTarget target = configuration.get(JVMConfigurationKeys.JVM_TARGET);
if (target == null && defaultJvmTarget != null) {
JvmTarget value = JvmTarget.fromString(defaultJvmTarget);
assert value != null : "Can't construct JvmTarget for " + defaultJvmTarget;
configuration.put(JVMConfigurationKeys.JVM_TARGET, value);
}
}
protected void compile(
@NotNull List<TestFile> files,
@Nullable File javaSourceDir
) {
configurationKind = extractConfigurationKind(files);
boolean loadAndroidAnnotations = files.stream().anyMatch(it ->
InTextDirectivesUtils.isDirectiveDefined(it.content, "ANDROID_ANNOTATIONS")
);
List<String> javacOptions = extractJavacOptions(files);
List<File> classpath = new ArrayList<>();
classpath.add(getAnnotationsJar());
if (loadAndroidAnnotations) {
classpath.add(ForTestCompileRuntime.androidAnnotationsForTests());
}
CompilerConfiguration configuration = createConfiguration(
configurationKind, getJdkKind(files),
classpath,
ArraysKt.filterNotNull(new File[] {javaSourceDir}),
files
);
myEnvironment = KotlinCoreEnvironment.createForTests(
getTestRootDisposable(), configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES
);
setupEnvironment(myEnvironment);
loadMultiFiles(files);
generateClassesInFile();
if (javaSourceDir != null) {
// If there are Java files, they should be compiled against the class files produced by Kotlin, so we dump them to the disk
File kotlinOut;
try {
kotlinOut = KotlinTestUtils.tmpDir(toString());
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
OutputUtilsKt.writeAllTo(classFileFactory, kotlinOut);
List<String> javaClasspath = new ArrayList<>();
javaClasspath.add(kotlinOut.getPath());
if (loadAndroidAnnotations) {
javaClasspath.add(ForTestCompileRuntime.androidAnnotationsForTests().getPath());
}
javaClassesOutputDirectory = CodegenTestUtil.compileJava(
findJavaSourcesInDirectory(javaSourceDir), javaClasspath, javacOptions
);
}
}
protected ConfigurationKind extractConfigurationKind(@NotNull List<TestFile> files) {
boolean addRuntime = false;
boolean addReflect = false;
for (TestFile file : files) {
if (InTextDirectivesUtils.isDirectiveDefined(file.content, "WITH_RUNTIME")) {
addRuntime = true;
}
if (InTextDirectivesUtils.isDirectiveDefined(file.content, "WITH_REFLECT")) {
addReflect = true;
}
}
return addReflect ? ConfigurationKind.ALL :
addRuntime ? ConfigurationKind.NO_KOTLIN_REFLECT :
ConfigurationKind.JDK_ONLY;
}
@NotNull
protected List<String> extractJavacOptions(@NotNull List<TestFile> files) {
List<String> javacOptions = new ArrayList<>(0);
for (TestFile file : files) {
javacOptions.addAll(InTextDirectivesUtils.findListWithPrefixes(file.content, "// JAVAC_OPTIONS:"));
}
updateJavacOptions(javacOptions);
return javacOptions;
}
protected void updateJavacOptions(List<String> javacOptions) {
if (javaCompilationTarget != null && !javacOptions.contains("-target")) {
javacOptions.add("-source");
javacOptions.add(javaCompilationTarget);
javacOptions.add("-target");
javacOptions.add(javaCompilationTarget);
}
}
public static class TestFile implements Comparable<TestFile> {
public final String name;
public final String content;
public TestFile(@NotNull String name, @NotNull String content) {
this.name = name;
this.content = content;
}
@Override
public int compareTo(@NotNull TestFile o) {
return name.compareTo(o.name);
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj instanceof TestFile && ((TestFile) obj).name.equals(name);
}
@Override
public String toString() {
return name;
}
}
protected void doTest(String filePath) throws Exception {
File file = new File(filePath);
String expectedText = KotlinTestUtils.doLoadFile(file);
Ref<File> javaFilesDir = Ref.create();
List<TestFile> testFiles = createTestFiles(file, expectedText, javaFilesDir);
doMultiFileTest(file, testFiles, javaFilesDir.get());
}
@NotNull
private static List<TestFile> createTestFiles(File file, String expectedText, Ref<File> javaFilesDir) {
return KotlinTestUtils.createTestFiles(file.getName(), expectedText, new KotlinTestUtils.TestFileFactoryNoModules<TestFile>() {
@NotNull
@Override
public TestFile create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives) {
if (fileName.endsWith(".java")) {
if (javaFilesDir.isNull()) {
try {
javaFilesDir.set(KotlinTestUtils.tmpDir("java-files"));
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
writeSourceFile(fileName, text, javaFilesDir.get());
}
return new TestFile(fileName, text);
}
private void writeSourceFile(@NotNull String fileName, @NotNull String content, @NotNull File targetDir) {
File file = new File(targetDir, fileName);
KotlinTestUtils.mkdirs(file.getParentFile());
FilesKt.writeText(file, content, Charsets.UTF_8);
}
});
}
protected void doMultiFileTest(@NotNull File wholeFile, @NotNull List<TestFile> files, @Nullable File javaFilesDir) throws Exception {
throw new UnsupportedOperationException("Multi-file test cases are not supported in this test");
}
protected void callBoxMethodAndCheckResult(URLClassLoader classLoader, String className)
throws IOException, InvocationTargetException, IllegalAccessException {
Class<?> aClass = getGeneratedClass(classLoader, className);
Method method = getBoxMethodOrNull(aClass);
assertTrue("Can't find box method in " + aClass,method != null);
callBoxMethodAndCheckResult(classLoader, aClass, method);
}
protected void callBoxMethodAndCheckResult(URLClassLoader classLoader, Class<?> aClass, Method method)
throws IOException, IllegalAccessException, InvocationTargetException {
String result;
if (boxInSeparateProcessPort != null) {
result = invokeBoxInSeparateProcess(classLoader, aClass);
}
else {
ClassLoader savedClassLoader = Thread.currentThread().getContextClassLoader();
if (savedClassLoader != classLoader) {
// otherwise the test infrastructure used in the test may conflict with the one from the context classloader
Thread.currentThread().setContextClassLoader(classLoader);
}
try {
result = (String) method.invoke(null);
}
finally {
if (savedClassLoader != classLoader) {
Thread.currentThread().setContextClassLoader(savedClassLoader);
}
}
}
assertEquals("OK", result);
}
@NotNull
private String invokeBoxInSeparateProcess(URLClassLoader classLoader, Class<?> aClass) throws IOException {
List<URL> classPath = extractUrls(classLoader);
if (classLoader instanceof GeneratedClassLoader) {
File outDir = KotlinTestUtils.tmpDirForTest(this);
SimpleOutputFileCollection currentOutput = new SimpleOutputFileCollection(((GeneratedClassLoader) classLoader).getAllGeneratedFiles());
writeAllTo(currentOutput, outDir);
classPath.add(0, outDir.toURI().toURL());
}
return new TestProxy(Integer.valueOf(boxInSeparateProcessPort), aClass.getCanonicalName(), classPath).runTest();
}
}
@@ -0,0 +1,142 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.google.common.collect.Lists;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiErrorElement;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.checkers.CheckerTestUtil;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.AnalyzingUtils;
import org.jetbrains.kotlin.script.KotlinScriptDefinitionProvider;
import org.jetbrains.kotlin.script.StandardScriptDefinition;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class CodegenTestFiles {
@NotNull
private final List<KtFile> psiFiles;
@NotNull
private final List<Pair<String, String>> expectedValues;
@NotNull
private final List<Object> scriptParameterValues;
private CodegenTestFiles(
@NotNull List<KtFile> psiFiles,
@NotNull List<Pair<String, String>> expectedValues,
@NotNull List<Object> scriptParameterValues
) {
this.psiFiles = psiFiles;
this.expectedValues = expectedValues;
this.scriptParameterValues = scriptParameterValues;
}
@NotNull
public KtFile getPsiFile() {
assert psiFiles.size() == 1;
return psiFiles.get(0);
}
@NotNull
public List<Pair<String, String>> getExpectedValues() {
return expectedValues;
}
@NotNull
public List<Object> getScriptParameterValues() {
return scriptParameterValues;
}
@NotNull
public List<KtFile> getPsiFiles() {
return psiFiles;
}
@NotNull
public static CodegenTestFiles create(@NotNull List<KtFile> ktFiles) {
assert !ktFiles.isEmpty() : "List should have at least one file";
return new CodegenTestFiles(ktFiles, Collections.emptyList(), Collections.emptyList());
}
public static CodegenTestFiles create(Project project, String[] names) {
return create(project, names, KotlinTestUtils.getTestDataPathBase());
}
public static CodegenTestFiles create(Project project, String[] names, String testDataPath) {
List<KtFile> files = new ArrayList<>(names.length);
for (String name : names) {
try {
String content = KotlinTestUtils.doLoadFile(testDataPath + "/codegen/", name);
KtFile file = KotlinTestUtils.createFile(name, content, project);
files.add(file);
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
return create(files);
}
@NotNull
public static CodegenTestFiles create(@NotNull String fileName, @NotNull String contentWithDiagnosticMarkup, @NotNull Project project) {
String content = CheckerTestUtil.parseDiagnosedRanges(contentWithDiagnosticMarkup, new ArrayList<>());
KtFile file = KotlinTestUtils.createFile(fileName, content, project);
List<PsiErrorElement> ranges = AnalyzingUtils.getSyntaxErrorRanges(file);
assert ranges.isEmpty() : "Syntax errors found in " + file + ": " + ranges;
List<Pair<String, String>> expectedValues = Lists.newArrayList();
Matcher matcher = Pattern.compile("// expected: (\\S+): (.*)").matcher(content);
while (matcher.find()) {
String fieldName = matcher.group(1);
String expectedValue = matcher.group(2);
expectedValues.add(Pair.create(fieldName, expectedValue));
}
List<Object> scriptParameterValues = Lists.newArrayList();
if (file.isScript()) {
Pattern scriptParametersPattern = Pattern.compile("param: (\\S.*)");
Matcher scriptParametersMatcher = scriptParametersPattern.matcher(file.getText());
if (scriptParametersMatcher.find()) {
String valueString = scriptParametersMatcher.group(1);
String[] values = valueString.split(" ");
scriptParameterValues.add(values);
} else {
scriptParameterValues.add(ArrayUtil.EMPTY_STRING_ARRAY);
}
}
KotlinScriptDefinitionProvider scriptDefinitionProvider = KotlinScriptDefinitionProvider.getInstance(project);
assert scriptDefinitionProvider != null;
scriptDefinitionProvider.addScriptDefinition(StandardScriptDefinition.INSTANCE);
return new CodegenTestFiles(Collections.singletonList(file), expectedValues, scriptParameterValues);
}
}
@@ -0,0 +1,164 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.intellij.ide.highlighter.JavaFileType;
import com.intellij.openapi.util.io.FileUtil;
import kotlin.collections.CollectionsKt;
import kotlin.io.FilesKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.jetbrains.kotlin.utils.StringsKt;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertTrue;
public class CodegenTestUtil {
private CodegenTestUtil() {}
@NotNull
public static ClassFileFactory generateFiles(@NotNull KotlinCoreEnvironment environment, @NotNull CodegenTestFiles files) {
return GenerationUtils.compileFiles(files.getPsiFiles(), environment).getFactory();
}
public static void assertThrows(@NotNull Method foo, @NotNull Class<? extends Throwable> exceptionClass,
@Nullable Object instance, @NotNull Object... args) throws IllegalAccessException {
boolean caught = false;
try {
foo.invoke(instance, args);
}
catch (InvocationTargetException ex) {
caught = exceptionClass.isInstance(ex.getTargetException());
}
assertTrue(caught);
}
@NotNull
public static Method findDeclaredMethodByName(@NotNull Class<?> aClass, @NotNull String name) {
Method result = findDeclaredMethodByNameOrNull(aClass, name);
if (result == null) {
throw new AssertionError("Method " + name + " is not found in " + aClass);
}
return result;
}
public static Method findDeclaredMethodByNameOrNull(@NotNull Class<?> aClass, @NotNull String name) {
for (Method method : aClass.getDeclaredMethods()) {
if (method.getName().equals(name)) {
return method;
}
}
return null;
}
@NotNull
public static File compileJava(
@NotNull List<String> fileNames,
@NotNull List<String> additionalClasspath,
@NotNull List<String> additionalOptions
) {
try {
File directory = KotlinTestUtils.tmpDir("java-classes");
compileJava(fileNames, additionalClasspath, additionalOptions, directory);
return directory;
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
public static void compileJava(
@NotNull List<String> fileNames,
@NotNull List<String> additionalClasspath,
@NotNull List<String> additionalOptions,
@NotNull File outDirectory
) {
try {
List<String> classpath = new ArrayList<>();
classpath.add(ForTestCompileRuntime.runtimeJarForTests().getPath());
classpath.add(ForTestCompileRuntime.reflectJarForTests().getPath());
classpath.add(KotlinTestUtils.getAnnotationsJar().getPath());
classpath.addAll(additionalClasspath);
List<String> options = new ArrayList<>(Arrays.asList(
"-classpath", StringsKt.join(classpath, File.pathSeparator),
"-d", outDirectory.getPath()
));
options.addAll(additionalOptions);
KotlinTestUtils.compileJavaFiles(CollectionsKt.map(fileNames, File::new), options);
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
public static Method findTheOnlyMethod(@NotNull Class<?> aClass) {
Method r = null;
for (Method method : aClass.getMethods()) {
if (method.getDeclaringClass().equals(Object.class)) {
continue;
}
if (r != null) {
throw new AssertionError("More than one public method in class " + aClass);
}
r = method;
}
if (r == null) {
throw new AssertionError("No public methods in class " + aClass);
}
return r;
}
@Nullable
public static Object getAnnotationAttribute(@NotNull Object annotation, @NotNull String name) {
try {
return annotation.getClass().getMethod(name).invoke(annotation);
}
catch (Exception e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
@NotNull
public static List<String> findJavaSourcesInDirectory(@NotNull File directory) {
List<String> javaFilePaths = new ArrayList<>(1);
FileUtil.processFilesRecursively(directory, file -> {
if (file.isFile() && FilesKt.getExtension(file).equals(JavaFileType.DEFAULT_EXTENSION)) {
javaFilePaths.add(file.getPath());
}
return true;
});
return javaFilePaths;
}
}
@@ -0,0 +1,101 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen;
import com.android.dx.cf.direct.DirectClassFile;
import com.android.dx.cf.direct.StdAttributeFactory;
import com.android.dx.command.dexer.Main;
import com.android.dx.dex.cf.CfTranslator;
import com.android.dx.dex.file.DexFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.junit.Assert;
import java.io.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class DxChecker {
public static final boolean RUN_DX_CHECKER = true;
private static final Pattern STACK_TRACE_PATTERN = Pattern.compile("[\\s]+at .*");
private DxChecker() {
}
public static void check(ClassFileFactory outputFiles) {
Main.Arguments arguments = new Main.Arguments();
String[] array = new String[1];
array[0] = "testArgs";
arguments.parse(array);
for (OutputFile file : ClassFileUtilsKt.getClassFiles(outputFiles)) {
try {
byte[] bytes = file.asByteArray();
if (isJava6Class(bytes)) {
checkFileWithDx(bytes, file.getRelativePath(), arguments);
}
}
catch (Throwable e) {
Assert.fail(generateExceptionMessage(e));
}
}
}
private static boolean isJava6Class(byte[] bytes) throws IOException {
try (DataInputStream stream = new DataInputStream(new ByteArrayInputStream(bytes))) {
int header = stream.readInt();
if (0xCAFEBABE != header) {
throw new IOException("Invalid header class header: " + header);
}
int minor = stream.readUnsignedShort();
int major = stream.readUnsignedShort();
return major == Opcodes.V1_6;
}
}
public static void checkFileWithDx(byte[] bytes, @NotNull String relativePath) {
Main.Arguments arguments = new Main.Arguments();
String[] array = new String[1];
array[0] = "testArgs";
arguments.parse(array);
checkFileWithDx(bytes, relativePath, arguments);
}
private static void checkFileWithDx(byte[] bytes, @NotNull String relativePath, @NotNull Main.Arguments arguments) {
DirectClassFile cf = new DirectClassFile(bytes, relativePath, true);
cf.setAttributeFactory(StdAttributeFactory.THE_ONE);
CfTranslator.translate(
cf,
bytes,
arguments.cfOptions,
arguments.dexOptions,
new DexFile(arguments.dexOptions)
);
}
private static String generateExceptionMessage(Throwable e) {
StringWriter writer = new StringWriter();
try (PrintWriter printWriter = new PrintWriter(writer)) {
e.printStackTrace(printWriter);
String stackTrace = writer.toString();
Matcher matcher = STACK_TRACE_PATTERN.matcher(stackTrace);
return matcher.replaceAll("");
}
}
}
@@ -0,0 +1,75 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.backend.jvm.JvmIrCodegenFactory
import org.jetbrains.kotlin.cli.common.output.outputUtils.writeAllTo
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.descriptors.PackagePartProvider
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
import java.io.File
object GenerationUtils {
@JvmStatic
fun compileFileTo(ktFile: KtFile, environment: KotlinCoreEnvironment, output: File): ClassFileFactory =
compileFile(ktFile, environment).apply {
writeAllTo(output)
}
@JvmStatic
fun compileFile(ktFile: KtFile, environment: KotlinCoreEnvironment): ClassFileFactory =
compileFiles(listOf(ktFile), environment).factory
@JvmStatic
@JvmOverloads
fun compileFiles(
files: List<KtFile>,
environment: KotlinCoreEnvironment,
classBuilderFactory: ClassBuilderFactory = ClassBuilderFactories.TEST
): GenerationState =
compileFiles(files, environment.configuration, classBuilderFactory, environment::createPackagePartProvider)
@JvmStatic
fun compileFiles(
files: List<KtFile>,
configuration: CompilerConfiguration,
classBuilderFactory: ClassBuilderFactory,
packagePartProvider: (GlobalSearchScope) -> PackagePartProvider
): GenerationState {
val analysisResult = JvmResolveUtil.analyzeAndCheckForErrors(files.first().project, files, configuration, packagePartProvider)
analysisResult.throwIfError()
val state = GenerationState(
files.first().project, classBuilderFactory, analysisResult.moduleDescriptor, analysisResult.bindingContext,
files, configuration,
codegenFactory = if (configuration.getBoolean(JVMConfigurationKeys.IR)) JvmIrCodegenFactory else DefaultCodegenFactory
)
if (analysisResult.shouldGenerateCode) {
KotlinCodegenFacade.compileCorrectFiles(state, CompilationErrorHandler.THROW_EXCEPTION)
}
// For JVM-specific errors
AnalyzingUtils.throwExceptionOnErrors(state.collectedExtraJvmDiagnostics)
return state
}
}
@@ -0,0 +1,254 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen
import org.jetbrains.kotlin.backend.common.output.OutputFile
import org.jetbrains.kotlin.inline.inlineFunctionsJvmNames
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
import org.jetbrains.kotlin.load.kotlin.FileBasedKotlinClass
import org.jetbrains.kotlin.load.kotlin.KotlinJvmBinaryClass
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.tree.MethodNode
import java.util.*
object InlineTestUtil {
fun checkNoCallsToInline(outputFiles: Iterable<OutputFile>, sourceFiles: List<KtFile>) {
val inlineInfo = obtainInlineInfo(outputFiles)
val inlineMethods = inlineInfo.inlineMethods
assert(!inlineMethods.isEmpty()) { "There are no inline methods" }
val notInlinedCalls = checkInlineMethodNotInvoked(outputFiles, inlineMethods)
assert(notInlinedCalls.isEmpty()) { "All inline methods should be inlined but:\n" + notInlinedCalls.joinToString("\n") }
val skipParameterChecking = sourceFiles.any {
InTextDirectivesUtils.isDirectiveDefined(it.text, "NO_CHECK_LAMBDA_INLINING")
} || !doLambdaInliningCheck(outputFiles, inlineInfo)
if (!skipParameterChecking) {
val notInlinedParameters = checkParametersInlined(outputFiles, inlineInfo, sourceFiles)
assert(notInlinedParameters.isEmpty()) {
"All inline parameters should be inlined but:\n${notInlinedParameters.joinToString("\n")}\n" +
"but if you have not inlined lambdas or anonymous objects enable NO_CHECK_LAMBDA_INLINING directive"
}
}
}
private fun obtainInlineInfo(files: Iterable<OutputFile>): InlineInfo {
val inlineMethods = HashSet<MethodInfo>()
val binaryClasses = hashMapOf<String, KotlinJvmBinaryClass>()
for (file in files) {
val binaryClass = loadBinaryClass(file)
val inlineFunctions = inlineFunctionsJvmNames(binaryClass.classHeader)
val classVisitor = object : ClassVisitorWithName() {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
if (name + desc in inlineFunctions) {
inlineMethods.add(MethodInfo(className, name, desc))
}
return null
}
}
ClassReader(file.asByteArray()).accept(classVisitor, 0)
binaryClasses.put(classVisitor.className, binaryClass)
}
return InlineInfo(inlineMethods, binaryClasses)
}
private fun doLambdaInliningCheck(files: Iterable<OutputFile>, inlineInfo: InlineInfo): Boolean {
var doLambdaInliningCheck = true
for (file in files) {
val binaryClass = loadBinaryClass(file)
val inlineFunctions = inlineFunctionsJvmNames(binaryClass.classHeader)
//if inline function creates anonymous object then do not try to check that all lambdas are inlined
val classVisitor = object : ClassVisitorWithName() {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
if (name + desc in inlineFunctions) {
return object: MethodNodeWithAnonymousObjectCheck(inlineInfo, access, name, desc, signature, exceptions) {
override fun onAnonymousConstructorCallOrSingletonAccess(owner: String) {
doLambdaInliningCheck = false
}
}
}
return null
}
}
ClassReader(file.asByteArray()).accept(classVisitor, 0)
if (!doLambdaInliningCheck) break
}
return doLambdaInliningCheck
}
private fun checkInlineMethodNotInvoked(files: Iterable<OutputFile>, inlinedMethods: Set<MethodInfo>): List<NotInlinedCall> {
val notInlined = ArrayList<NotInlinedCall>()
files.forEach { file ->
ClassReader(file.asByteArray()).accept(object : ClassVisitorWithName() {
private var skipMethodsOfThisClass = false
override fun visitAnnotation(desc: String, visible: Boolean): AnnotationVisitor? {
if (desc == JvmAnnotationNames.METADATA_DESC) {
return object : AnnotationVisitor(Opcodes.ASM5) {
override fun visit(name: String?, value: Any) {
if (name == JvmAnnotationNames.KIND_FIELD_NAME && value == KotlinClassHeader.Kind.MULTIFILE_CLASS.id) {
skipMethodsOfThisClass = true
}
}
}
}
return null
}
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
if (skipMethodsOfThisClass) {
return null
}
return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) {
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
val methodCall = MethodInfo(owner, name, desc)
if (inlinedMethods.contains(methodCall)) {
val fromCall = MethodInfo(className, this.name, this.desc)
//skip delegation to interface DefaultImpls from child class
if (methodCall.owner.endsWith(JvmAbi.DEFAULT_IMPLS_SUFFIX) && fromCall.owner != methodCall.owner) {
return
}
notInlined.add(NotInlinedCall(fromCall, methodCall))
}
}
}
}
}, 0)
}
return notInlined
}
private fun checkParametersInlined(outputFiles: Iterable<OutputFile>, inlineInfo: InlineInfo, sourceFiles: List<KtFile>): ArrayList<NotInlinedParameter> {
val skipMethods =
sourceFiles.flatMap {
InTextDirectivesUtils.findLinesWithPrefixesRemoved(it.text, "// SKIP_INLINE_CHECK_IN: ")
}.toSet()
val inlinedMethods = inlineInfo.inlineMethods
val notInlinedParameters = ArrayList<NotInlinedParameter>()
for (file in outputFiles) {
if (!isClassOrPackagePartKind(loadBinaryClass(file))) continue
ClassReader(file.asByteArray()).accept(object : ClassVisitorWithName() {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
val declaration = MethodInfo(className, name, desc)
//do not check anonymous object creation in inline functions and in package facades
if (declaration in inlinedMethods) {
return null
}
if (skipMethods.contains(name)) {
return null
}
return object: MethodNodeWithAnonymousObjectCheck(inlineInfo, access, name, desc, signature, exceptions) {
override fun onAnonymousConstructorCallOrSingletonAccess(owner: String) {
val fromCall = MethodInfo(className, this.name, this.desc)
notInlinedParameters.add(NotInlinedParameter(owner, fromCall))
}
}
}
}, 0)
}
return notInlinedParameters
}
private fun isTopLevelOrInnerOrPackageClass(classInternalName: String, inlineInfo: InlineInfo): Boolean {
if (classInternalName.startsWith("kotlin/jvm/internal/"))
return true
return isClassOrPackagePartKind(inlineInfo.binaryClasses[classInternalName]!!)
}
private fun isClassOrPackagePartKind(klass: KotlinJvmBinaryClass): Boolean {
return klass.classHeader.kind == KotlinClassHeader.Kind.CLASS && !klass.classId.isLocal
|| klass.classHeader.kind == KotlinClassHeader.Kind.FILE_FACADE /*single file facade equals to package part*/
|| klass.classHeader.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART
}
private fun loadBinaryClass(file: OutputFile): KotlinJvmBinaryClass {
val klass = FileBasedKotlinClass.create(file.asByteArray()) {
className, classVersion, classHeader, innerClasses ->
object : FileBasedKotlinClass(className, classVersion, classHeader, innerClasses) {
override val location: String
get() = throw UnsupportedOperationException()
override fun getFileContents(): ByteArray = throw UnsupportedOperationException()
override fun hashCode(): Int = throw UnsupportedOperationException()
override fun equals(other: Any?): Boolean = throw UnsupportedOperationException()
override fun toString(): String = throw UnsupportedOperationException()
}
}!!
return klass
}
private class InlineInfo(val inlineMethods: Set<MethodInfo>, val binaryClasses: Map<String, KotlinJvmBinaryClass>)
private data class NotInlinedCall(val fromCall: MethodInfo, val inlineMethod: MethodInfo)
private data class NotInlinedParameter(val parameterClassName: String, val fromCall: MethodInfo)
private data class MethodInfo(val owner: String, val name: String, val desc: String)
private open class ClassVisitorWithName : ClassVisitor(Opcodes.ASM5) {
lateinit var className: String
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String?, interfaces: Array<String>?) {
className = name
super.visit(version, access, name, signature, superName, interfaces)
}
}
private abstract class MethodNodeWithAnonymousObjectCheck(val inlineInfo: InlineInfo, access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?) : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) {
private fun isInlineParameterLikeOwner(owner: String) =
"$" in owner && !isTopLevelOrInnerOrPackageClass(owner, inlineInfo)
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
if ("<init>" == name && isInlineParameterLikeOwner(owner)) {
onAnonymousConstructorCallOrSingletonAccess(owner)
}
}
abstract fun onAnonymousConstructorCallOrSingletonAccess(owner: String)
override fun visitFieldInsn(opcode: Int, owner: String, name: String, desc: String) {
if (opcode == Opcodes.GETSTATIC && isInlineParameterLikeOwner(owner)) {
onAnonymousConstructorCallOrSingletonAccess(owner)
}
super.visitFieldInsn(opcode, owner, name, desc)
}
}
}
@@ -0,0 +1,125 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen
import com.intellij.openapi.util.io.FileUtil
import com.intellij.openapi.util.text.StringUtil
import org.jetbrains.kotlin.backend.common.output.OutputFile
import org.jetbrains.kotlin.codegen.inline.GENERATE_SMAP
import org.jetbrains.kotlin.codegen.inline.RangeMapping
import org.jetbrains.kotlin.codegen.inline.SMAPParser
import org.jetbrains.kotlin.codegen.inline.toRange
import org.jetbrains.kotlin.utils.keysToMap
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.ClassVisitor
import org.jetbrains.org.objectweb.asm.Opcodes
import org.junit.Assert
import java.io.File
import java.io.StringReader
object SMAPTestUtil {
private fun extractSMAPFromClasses(outputFiles: Iterable<OutputFile>): List<SMAPAndFile> {
return outputFiles.mapNotNull { outputFile ->
var debugInfo: String? = null
ClassReader(outputFile.asByteArray()).accept(object : ClassVisitor(Opcodes.ASM5) {
override fun visitSource(source: String?, debug: String?) {
debugInfo = debug
}
}, 0)
SMAPAndFile.SMAPAndFile(debugInfo, outputFile.sourceFiles.single(), outputFile.relativePath)
}
}
private fun extractSmapFromTestDataFile(file: CodegenTestCase.TestFile): SMAPAndFile? {
if (!file.name.endsWith(".smap")) return null
val content = buildString {
StringReader(file.content).forEachLine { line ->
// Strip comments
if (!line.startsWith("//")) {
appendln(line.trim())
}
}
}.trim()
return SMAPAndFile(if (content.isNotEmpty()) content else null, SMAPAndFile.getPath(file.name), "NOT_SORTED")
}
fun checkSMAP(inputFiles: List<CodegenTestCase.TestFile>, outputFiles: Iterable<OutputFile>) {
if (!GENERATE_SMAP) return
val sourceData = inputFiles.mapNotNull { extractSmapFromTestDataFile(it) }
val compiledSmaps = extractSMAPFromClasses(outputFiles)
val compiledData = compiledSmaps.groupBy {
it.sourceFile
}.map {
val smap = it.value.sortedByDescending { it.outputFile }.mapNotNull { it.smap }.joinToString("\n")
SMAPAndFile(if (smap.isNotEmpty()) smap else null, it.key, "NOT_SORTED")
}.associateBy { it.sourceFile }
for (source in sourceData) {
val ktFileName = "/" + source.sourceFile.replace(".smap", ".kt")
val data = compiledData[ktFileName]
Assert.assertEquals("Smap data differs for $ktFileName", normalize(source.smap), normalize(data?.smap))
}
checkNoConflictMappings(compiledSmaps)
}
private fun checkNoConflictMappings(compiledSmap: List<SMAPAndFile>?) {
if (compiledSmap == null) return
compiledSmap.mapNotNull { it.smap }.forEach {
val smap = SMAPParser.parse(it)
val conflictingLines = smap.fileMappings.flatMap {
fileMapping ->
fileMapping.lineMappings.flatMap {
lineMapping: RangeMapping ->
lineMapping.toRange.keysToMap { lineMapping }.entries
}
}.groupBy { it.key }.entries.filter { it.value.size != 1 }
Assert.assertTrue(
conflictingLines.joinToString(separator = "\n") {
"Conflicting mapping for line ${it.key} in ${it.value.joinToString { it.toString() }} "
},
conflictingLines.isEmpty()
)
}
}
private fun normalize(text: String?) =
text?.let { StringUtil.convertLineSeparators(it.trim()) }
private class SMAPAndFile(val smap: String?, val sourceFile: String, val outputFile: String) {
companion object {
fun SMAPAndFile(smap: String?, sourceFile: File, outputFile: String) =
SMAPAndFile(smap, getPath(sourceFile), outputFile)
fun getPath(file: File): String {
return getPath(file.canonicalPath)
}
fun getPath(canonicalPath: String): String {
//There are some problems with disk name on windows cause LightVirtualFile return it without disk name
return FileUtil.toSystemIndependentName(canonicalPath).substringAfter(":")
}
}
}
}
@@ -0,0 +1,72 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen.defaultConstructor;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.kotlin.codegen.CodegenTestCase;
import org.jetbrains.kotlin.test.ConfigurationKind;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.List;
import static org.jetbrains.kotlin.test.InTextDirectivesUtils.findListWithPrefixes;
public abstract class AbstractDefaultArgumentsReflectionTest extends CodegenTestCase {
@Override
public void setUp() throws Exception {
super.setUp();
createEnvironmentWithMockJdkAndIdeaAnnotations(ConfigurationKind.JDK_ONLY);
}
@Override
protected void doTest(String path) throws IOException {
loadFileByFullPath(path);
String fileText = FileUtil.loadFile(new File(path), true);
String className = loadInstructionValue(fileText, "CLASS");
boolean hasDefaultConstructor = loadInstructionValue(fileText, "HAS_DEFAULT_CONSTRUCTOR").equals("true");
Class<?> aClass = generateClass(className);
assertNotNull("Cannot find class with name " + className, aClass);
try {
Constructor constructor = aClass.getDeclaredConstructor();
if (!hasDefaultConstructor) {
System.out.println(generateToText());
throw new AssertionError("Default constructor was found but it wasn't expected: " + constructor);
}
}
catch (NoSuchMethodException e) {
if (hasDefaultConstructor) {
System.out.println(generateToText());
throw new AssertionError("Cannot find default constructor");
}
}
catch (Throwable e) {
System.out.println(generateToText());
throw new RuntimeException(e);
}
}
private static String loadInstructionValue(String fileContent, String instructionName) {
List<String> testedClass = findListWithPrefixes(fileContent, "// " + instructionName + ": ");
assertTrue("Cannot find " + instructionName + " instruction", !testedClass.isEmpty());
assertTrue(instructionName + " instruction must have only one element", testedClass.size() == 1);
return testedClass.get(0);
}
}
@@ -0,0 +1,275 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen.flags;
import com.intellij.openapi.util.io.FileUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.backend.common.output.OutputFile;
import org.jetbrains.kotlin.codegen.CodegenTestCase;
import org.jetbrains.org.objectweb.asm.*;
import java.io.File;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import static org.jetbrains.kotlin.test.InTextDirectivesUtils.findListWithPrefixes;
import static org.jetbrains.kotlin.test.InTextDirectivesUtils.findStringWithPrefixes;
/*
* Test correctness of written flags in class file
*
* TESTED_OBJECT_KIND - maybe class, function or property
* TESTED_OBJECTS - className, [function/property name]
* FLAGS - only flags which must be true (could be skipped if ABSENT is TRUE)
* ABSENT - true or false, optional (false by default)
*
* There is could be specified several tested objects separated by empty line, e.g:
* TESTED_OBJECT_KIND: property
* TESTED_OBJECTS: Test$object, prop
* ABSENT: TRUE
*
* TESTED_OBJECT_KIND: property
* TESTED_OBJECTS: Test, prop$delegate
* FLAGS: ACC_STATIC, ACC_FINAL, ACC_PRIVATE
*/
public abstract class AbstractWriteFlagsTest extends CodegenTestCase {
@Override
protected void doMultiFileTest(
@NotNull File wholeFile, @NotNull List<TestFile> files, @Nullable File javaFilesDir
) throws Exception {
compile(files, null);
String fileText = FileUtil.loadFile(wholeFile, true);
List<TestedObject> testedObjects = parseExpectedTestedObject(fileText);
for (TestedObject testedObject : testedObjects) {
String className = null;
for (OutputFile outputFile : classFileFactory.asList()) {
String filePath = outputFile.getRelativePath();
if (testedObject.isFullContainingClassName && filePath.equals(testedObject.containingClass + ".class") ||
!testedObject.isFullContainingClassName && filePath.startsWith(testedObject.containingClass)) {
className = filePath;
}
}
assertNotNull("Couldn't find a class file with name " + testedObject.containingClass, className);
OutputFile outputFile = classFileFactory.get(className);
assertNotNull(outputFile);
ClassReader cr = new ClassReader(outputFile.asByteArray());
TestClassVisitor classVisitor = getClassVisitor(testedObject.kind, testedObject.name, false);
cr.accept(classVisitor, ClassReader.SKIP_CODE);
if (!classVisitor.isExists()) {
classVisitor = getClassVisitor(testedObject.kind, testedObject.name, true);
cr.accept(classVisitor, ClassReader.SKIP_CODE);
}
boolean isObjectExists = !Boolean.valueOf(findStringWithPrefixes(testedObject.textData, "// ABSENT: "));
assertEquals("Wrong object existence state: " + testedObject, isObjectExists, classVisitor.isExists());
if (isObjectExists) {
assertEquals("Wrong access flag for " + testedObject + " \n" + outputFile.asText(),
getExpectedFlags(testedObject.textData), classVisitor.getAccess());
}
}
}
private static List<TestedObject> parseExpectedTestedObject(String testDescription) {
String[] testObjectData = testDescription.substring(testDescription.indexOf("// TESTED_OBJECT_KIND")).split("\n\n");
List<TestedObject> objects = new ArrayList<>();
for (String testData : testObjectData) {
if (testData.isEmpty()) continue;
TestedObject testObject = new TestedObject();
testObject.textData = testData;
List<String> testedObjects = findListWithPrefixes(testData, "// TESTED_OBJECTS: ");
assertTrue("Cannot find TESTED_OBJECTS instruction", !testedObjects.isEmpty());
testObject.containingClass = testedObjects.get(0);
if (testedObjects.size() == 1) {
testObject.name = testedObjects.get(0);
}
else if (testedObjects.size() == 2) {
testObject.name = testedObjects.get(1);
}
else {
throw new IllegalArgumentException(
"TESTED_OBJECTS instruction must contain one (for class) or two (for function and property) values");
}
testObject.kind = findStringWithPrefixes(testData, "// TESTED_OBJECT_KIND: ");
List<String> isFullName = findListWithPrefixes(testData, "// IS_FULL_CONTAINING_CLASS_NAME: ");
if (isFullName.size() == 1) {
testObject.isFullContainingClassName = Boolean.parseBoolean(isFullName.get(0));
}
objects.add(testObject);
}
assertTrue("Test description not present!", !objects.isEmpty());
return objects;
}
private static class TestedObject {
public String name;
public String containingClass = "";
public boolean isFullContainingClassName = true;
public String kind;
public String textData;
@Override
public String toString() {
return "Class = " + containingClass + ", name = " + name + ", kind = " + kind;
}
}
private static TestClassVisitor getClassVisitor(String visitorKind, String testedObjectName, boolean allowSynthetic) {
switch (visitorKind) {
case "class":
return new ClassFlagsVisitor();
case "function":
return new FunctionFlagsVisitor(testedObjectName, allowSynthetic);
case "property":
return new PropertyFlagsVisitor(testedObjectName);
case "innerClass":
return new InnerClassFlagsVisitor(testedObjectName);
default:
}
throw new IllegalArgumentException("Value of TESTED_OBJECT_KIND is incorrect: " + visitorKind);
}
protected static abstract class TestClassVisitor extends ClassVisitor {
protected boolean isExists;
public TestClassVisitor() {
super(Opcodes.ASM5);
}
abstract public int getAccess();
public boolean isExists() {
return isExists;
}
}
private static int getExpectedFlags(String text) {
int expectedAccess = 0;
Class klass = Opcodes.class;
List<String> flags = findListWithPrefixes(text, "// FLAGS: ");
for (String flag : flags) {
try {
Field field = klass.getDeclaredField(flag);
expectedAccess |= field.getInt(klass);
}
catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalArgumentException("Cannot find " + flag + " field in Opcodes class", e);
}
}
return expectedAccess;
}
private static class ClassFlagsVisitor extends TestClassVisitor {
private int access = 0;
@Override
public void visit(int version, int access, @NotNull String name, String signature, String superName, String[] interfaces) {
this.access = access;
isExists = true;
}
@Override
public int getAccess() {
return access;
}
}
private static class FunctionFlagsVisitor extends TestClassVisitor {
private int access = 0;
private final String funName;
private final boolean allowSynthetic;
public FunctionFlagsVisitor(String name, boolean allowSynthetic) {
funName = name;
this.allowSynthetic = allowSynthetic;
}
@Override
public MethodVisitor visitMethod(int access, @NotNull String name, @NotNull String desc, String signature, String[] exceptions) {
if (name.equals(funName)) {
if (!allowSynthetic && (access & Opcodes.ACC_SYNTHETIC) != 0) return null;
this.access = access;
isExists = true;
}
return null;
}
@Override
public int getAccess() {
return access;
}
}
private static class PropertyFlagsVisitor extends TestClassVisitor {
private int access = 0;
private final String propertyName;
public PropertyFlagsVisitor(String name) {
propertyName = name;
}
@Override
public FieldVisitor visitField(int access, @NotNull String name, @NotNull String desc, String signature, Object value) {
if (name.equals(propertyName)) {
this.access = access;
isExists = true;
}
return null;
}
@Override
public int getAccess() {
return access;
}
}
private static class InnerClassFlagsVisitor extends TestClassVisitor {
private int access = 0;
private final String innerClassName;
public InnerClassFlagsVisitor(String name) {
innerClassName = name;
}
@Override
public void visitInnerClass(@NotNull String innerClassInternalName, String outerClassInternalName, String name, int access) {
if (innerClassName.equals(name)) {
this.access = access;
isExists = true;
}
}
@Override
public int getAccess() {
return access;
}
}
}
@@ -0,0 +1,137 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen.forTestCompile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.io.File;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
public class ForTestCompileRuntime {
private static volatile SoftReference<ClassLoader> reflectJarClassLoader = new SoftReference<>(null);
private static volatile SoftReference<ClassLoader> runtimeJarClassLoader = new SoftReference<>(null);
@NotNull
public static File runtimeJarForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-stdlib.jar"));
}
@NotNull
public static File mockRuntimeJarForTests() {
return assertExists(new File("dist/kotlin-mock-runtime-for-test.jar"));
}
@NotNull
public static File kotlinTestJarForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-test.jar"));
}
@NotNull
public static File kotlinTestJunitJarForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-test-junit.jar"));
}
@NotNull
public static File reflectJarForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-reflect.jar"));
}
@NotNull
public static File scriptRuntimeJarForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-script-runtime.jar"));
}
@NotNull
public static File runtimeSourcesJarForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-runtime-sources.jar"));
}
@NotNull
public static File stdlibCommonForTests() {
return assertExists(new File("dist/common/kotlin-stdlib-common.jar"));
}
@NotNull
public static File stdlibJsForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-stdlib-js.jar"));
}
@NotNull
public static File jvmAnnotationsForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-annotations-jvm.jar"));
}
@NotNull
public static File androidAnnotationsForTests() {
return assertExists(new File("dist/kotlinc/lib/kotlin-annotations-android.jar"));
}
// TODO: Do not use these classes, remove them after stdlib tests are merged in the same build as the compiler
@NotNull
@Deprecated
public static File[] runtimeClassesForTests() {
// TODO: replace hardcoded path with something flexible
return new File[] { assertExists(new File("dist/builtins")), assertExists(new File("build/kotlin-stdlib/classes/java/builtins")), assertExists(new File("build/kotlin-stdlib/classes/java/main")) };
}
@NotNull
private static File assertExists(@NotNull File file) {
if (!file.exists()) {
throw new IllegalStateException(file + " does not exist. Run 'ant dist'");
}
return file;
}
@NotNull
public static synchronized ClassLoader runtimeAndReflectJarClassLoader() {
ClassLoader loader = reflectJarClassLoader.get();
if (loader == null) {
loader = createClassLoader(runtimeJarForTests(), reflectJarForTests(), scriptRuntimeJarForTests(), kotlinTestJarForTests());
reflectJarClassLoader = new SoftReference<>(loader);
}
return loader;
}
@NotNull
public static synchronized ClassLoader runtimeJarClassLoader() {
ClassLoader loader = runtimeJarClassLoader.get();
if (loader == null) {
loader = createClassLoader(runtimeJarForTests(), scriptRuntimeJarForTests(), kotlinTestJarForTests());
runtimeJarClassLoader = new SoftReference<>(loader);
}
return loader;
}
@NotNull
private static ClassLoader createClassLoader(@NotNull File... files) {
try {
List<URL> urls = new ArrayList<>(2);
for (File file : files) {
urls.add(file.toURI().toURL());
}
return new URLClassLoader(urls.toArray(new URL[urls.size()]), null);
}
catch (MalformedURLException e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
}
@@ -0,0 +1,29 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen.ir;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.codegen.AbstractBlackBoxCodegenTest;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.config.JVMConfigurationKeys;
public abstract class AbstractIrBlackBoxCodegenTest extends AbstractBlackBoxCodegenTest {
@Override
protected void updateConfiguration(@NotNull CompilerConfiguration configuration) {
configuration.put(JVMConfigurationKeys.IR, true);
}
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen.ir
import org.jetbrains.kotlin.codegen.AbstractBlackBoxCodegenTest
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
abstract class AbstractIrBlackBoxInlineCodegenTest: AbstractBlackBoxCodegenTest() {
override fun updateConfiguration(configuration: CompilerConfiguration) = configuration.put(JVMConfigurationKeys.IR, true)
}
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen.ir
import org.jetbrains.kotlin.codegen.AbstractCompileKotlinAgainstInlineKotlinTest
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.config.JVMConfigurationKeys
abstract class AbstractIrCompileKotlinAgainstInlineKotlinTest : AbstractCompileKotlinAgainstInlineKotlinTest() {
override fun updateConfiguration(configuration: CompilerConfiguration) = configuration.put(JVMConfigurationKeys.IR, true)
}
@@ -0,0 +1,44 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.codegen
import junit.framework.TestCase
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.utils.rethrow
import java.lang.reflect.Method
import java.net.URL
import java.net.URLClassLoader
fun clearReflectionCache(classLoader: ClassLoader) {
try {
val klass = classLoader.loadClass(JvmAbi.REFLECTION_FACTORY_IMPL.asSingleFqName().asString())
val method = klass.getDeclaredMethod("clearCaches")
method.invoke(null)
}
catch (e: ClassNotFoundException) {
// This is OK for a test without kotlin-reflect in the dependencies
}
}
fun ClassLoader?.extractUrls(): List<URL> {
return (this as? URLClassLoader)?.let {
it.urLs.toList() + it.parent.extractUrls()
} ?: emptyList()
}
@@ -0,0 +1,88 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.daemon
/**
* holder of a [regex] and optional [matchCheck] for additional checks on match result
*/
class LinePattern(val regex: Regex, val matchCheck: (MatchResult) -> Boolean = { true })
fun LinePattern(regex: String, matchCheck: (MatchResult) -> Boolean = { true }) = LinePattern(regex.toRegex(), matchCheck)
/**
* calls [body] if receiver does not contain complete sequence of lines matched by [patternsIter], separated by any number of other lines
* [body] receives first unmatched pattern and index of last matched line in the sequence
*/
fun Sequence<String>.ifNotContainsSequence(patternsIter: Iterator<LinePattern>,
body: (LinePattern, Int) -> Unit) : Unit {
class Accumulator(it: Iterator<LinePattern>) {
val iter = EndBoundIteratorWithValue(it)
var lineNo = 1
var lastMatchedLineNo = 0
fun nextLineAndPattern(): Accumulator { iter.traverseNext(); lastMatchedLineNo = lineNo; return nextLine() }
fun nextLine(): Accumulator { lineNo++; return this }
}
val res = fold(Accumulator(patternsIter))
{ acc, line ->
when {
!acc.iter.isValid() -> return@fold acc
acc.iter.value.regex.find(line)?.let { acc.iter.value.matchCheck(it) } ?: false -> acc.nextLineAndPattern()
else -> acc.nextLine()
}
}
if (res.iter.isValid()) {
body(res.iter.value, res.lastMatchedLineNo)
}
}
/**
* calls [body] if receiver does not contain complete sequence of lines matched by [patterns], separated by any number of other lines
* [body] receives first unmatched pattern and index of last matched line in the sequence
*/
fun Sequence<String>.ifNotContainsSequence(patterns: List<LinePattern>,
body: (LinePattern, Int) -> Unit): Unit {
ifNotContainsSequence(patterns.iterator(), body)
}
/**
* calls [body] if receiver does not contain complete sequence of lines matched by [patterns], separated by any number of other lines
* [body] receives first unmatched pattern and index of last matched line in the sequence
*/
fun Sequence<String>.ifNotContainsSequence(vararg patterns: LinePattern,
body: (LinePattern, Int) -> Unit): Unit {
ifNotContainsSequence(patterns.iterator(), body)
}
// emulates Stepanov's / STL iterator, but with "embedded" end check via isValid:
// iterator points to a current value and upon init points to the first element or is invalid
// allows to express some algorithms more concisely
private class EndBoundIteratorWithValue<T: Any, Iter: Iterator<T>>(val base: Iter) {
private var _value: T? = base.nextOrNull()
val value: T get() = _value ?: throw Exception("Dereferencing invalid iterator")
fun isValid(): Boolean = _value != null
fun traverseNext(): EndBoundIteratorWithValue<T, Iter> {
_value = base.nextOrNull()
return this
}
}
private fun<T: Any> Iterator<T>.nextOrNull(): T? = if (hasNext()) next() else null
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.integration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import java.io.File;
public abstract class AbstractAntTaskTest extends KotlinIntegrationTestBase {
protected void doTest(String testFile) throws Exception {
String testDataDir = new File(testFile).getAbsolutePath();
runJava(
testDataDir,
"build.log",
"-Xmx192m",
"-jar", getAntHome() + File.separator + "lib" + File.separator + "ant-launcher.jar",
"-Dkotlin.lib=" + KotlinIntegrationTestBase.getCompilerLib(),
"-Dkotlin.runtime.jar=" + ForTestCompileRuntime.runtimeJarForTests().getAbsolutePath(),
"-Dkotlin.reflect.jar=" + ForTestCompileRuntime.reflectJarForTests().getAbsolutePath(),
"-Dkotlin.stdlib.jre7.jar=" + new File("dist/kotlinc/lib/kotlin-stdlib-jre7.jar").getAbsolutePath(),
"-Dkotlin.stdlib.jre8.jar=" + new File("dist/kotlinc/lib/kotlin-stdlib-jre8.jar").getAbsolutePath(),
"-Dkotlin.stdlib.jdk7.jar=" + new File("dist/kotlinc/lib/kotlin-stdlib-jdk7.jar").getAbsolutePath(),
"-Dkotlin.stdlib.jdk8.jar=" + new File("dist/kotlinc/lib/kotlin-stdlib-jdk8.jar").getAbsolutePath(),
"-Dtest.data=" + testDataDir,
"-Dtemp=" + tmpdir,
"-f", "build.xml"
);
}
@Override
@NotNull
protected String normalizeOutput(@NotNull File testDataDir, @NotNull String content) {
return super.normalizeOutput(testDataDir, content)
.replaceAll("Total time: .+\n", "Total time: [time]\n");
}
@NotNull
private static String getAntHome() {
return KotlinIntegrationTestBase.getKotlinProjectHome().getAbsolutePath() + File.separator + "dependencies" + File.separator + "ant-1.8";
}
}
@@ -0,0 +1,169 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.integration;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessOutputTypes;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import kotlin.text.Regex;
import org.intellij.lang.annotations.Language;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.config.KotlinCompilerVersion;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestCaseWithTmpdir;
import org.jetbrains.kotlin.utils.PathUtil;
import java.io.File;
import java.io.IOException;
import java.util.regex.Pattern;
public abstract class KotlinIntegrationTestBase extends TestCaseWithTmpdir {
static {
System.setProperty("java.awt.headless", "true");
}
protected int runJava(@NotNull String testDataDir, @Nullable String logName, @NotNull String... arguments) throws Exception {
GeneralCommandLine commandLine = new GeneralCommandLine().withWorkDirectory(testDataDir);
commandLine.setExePath(getJavaRuntime().getAbsolutePath());
commandLine.addParameters(arguments);
StringBuilder executionLog = new StringBuilder();
int exitCode = runProcess(commandLine, executionLog);
if (logName == null) {
assertEquals("Non-zero exit code", 0, exitCode);
}
else {
check(testDataDir, logName, executionLog.toString());
}
return exitCode;
}
private static String normalizePath(String content, File baseDir, String pathId) {
String contentWithRelativePaths = content.replace(baseDir.getAbsolutePath(), pathId);
@Language("RegExp")
String RELATIVE_PATH_WITH_MIXED_SEPARATOR = Regex.Companion.escape(pathId) + "[-.\\w/\\\\]*";
return new Regex(RELATIVE_PATH_WITH_MIXED_SEPARATOR).replace(
contentWithRelativePaths, mr -> FileUtil.toSystemIndependentName(mr.getValue())
);
}
@NotNull
protected String normalizeOutput(@NotNull File testDataDir, @NotNull String content) {
content = normalizePath(content, testDataDir, "[TestData]");
content = normalizePath(content, tmpdir, "[Temp]");
content = normalizePath(content, getCompilerLib(), "[CompilerLib]");
content = normalizePath(content, getKotlinProjectHome(), "[KotlinProjectHome]");
content = content.replaceAll(Pattern.quote(KotlinCompilerVersion.VERSION), "[KotlinVersion]");
content = content.replaceAll("\\(JRE .+\\)", "(JRE [JREVersion])");
content = StringUtil.convertLineSeparators(content);
return content;
}
private void check(String testDataDir, String baseName, String content) throws IOException {
File expectedFile = new File(testDataDir, baseName + ".expected");
String normalizedContent = normalizeOutput(new File(testDataDir), content);
KotlinTestUtils.assertEqualsToFile(expectedFile, normalizedContent);
}
private static int runProcess(GeneralCommandLine commandLine, StringBuilder executionLog) throws ExecutionException {
OSProcessHandler handler =
new OSProcessHandler(commandLine.createProcess(), commandLine.getCommandLineString(), commandLine.getCharset());
StringBuilder outContent = new StringBuilder();
StringBuilder errContent = new StringBuilder();
handler.addProcessListener(new OutputListener(outContent, errContent));
handler.startNotify();
handler.waitFor();
int exitCode = handler.getProcess().exitValue();
appendIfNotEmpty(executionLog, "OUT:\n", outContent.toString());
appendIfNotEmpty(executionLog, "\nERR:\n", errContent.toString());
executionLog.append("\nReturn code: ").append(exitCode).append("\n");
return exitCode;
}
private static void appendIfNotEmpty(StringBuilder executionLog, String prefix, String content) {
if (content.length() > 0) {
executionLog.append(prefix);
executionLog.append(content);
}
}
private static File getJavaRuntime() {
File javaHome = new File(System.getProperty("java.home"));
String javaExe = SystemInfo.isWindows ? "java.exe" : "java";
File runtime = new File(javaHome, "bin" + File.separator + javaExe);
assertTrue("No java runtime at " + runtime, runtime.isFile());
return runtime;
}
public static File getCompilerLib() {
File file = PathUtil.getKotlinPathsForDistDirectory().getLibPath().getAbsoluteFile();
assertTrue("Lib directory doesn't exist. Run 'ant dist'", file.isDirectory());
return file;
}
protected static File getKotlinProjectHome() {
return new File(PathManager.getHomePath()).getParentFile();
}
private static class OutputListener extends ProcessAdapter {
private final StringBuilder out;
private final StringBuilder err;
public OutputListener(@NotNull StringBuilder out, @NotNull StringBuilder err) {
this.out = out;
this.err = err;
}
@Override
public void onTextAvailable(ProcessEvent event, Key outputType) {
if (outputType == ProcessOutputTypes.STDERR) {
err.append(event.getText());
}
else if (outputType == ProcessOutputTypes.SYSTEM) {
// skip
}
else {
out.append(event.getText());
}
}
@Override
public void processTerminated(ProcessEvent event) {}
}
}
@@ -0,0 +1,340 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.jvm.compiler;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import junit.framework.ComparisonFailure;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM;
import org.jetbrains.kotlin.cli.jvm.config.JvmContentRootsKt;
import org.jetbrains.kotlin.config.*;
import org.jetbrains.kotlin.descriptors.ClassDescriptor;
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil;
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor;
import org.jetbrains.kotlin.test.*;
import org.jetbrains.kotlin.test.util.DescriptorValidator;
import org.junit.Assert;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;
import static org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil.*;
import static org.jetbrains.kotlin.test.KotlinTestUtils.*;
import static org.jetbrains.kotlin.test.util.DescriptorValidator.ValidationVisitor.errorTypesAllowed;
import static org.jetbrains.kotlin.test.util.DescriptorValidator.ValidationVisitor.errorTypesForbidden;
import static org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.*;
/*
The generated test compares package descriptors loaded from kotlin sources and read from compiled java.
*/
public abstract class AbstractLoadJavaTest extends TestCaseWithTmpdir {
// There are two modules in each test case (sources and dependencies), so we should render declarations from both of them
public static final Configuration COMPARATOR_CONFIGURATION = DONT_INCLUDE_METHODS_OF_OBJECT.renderDeclarationsFromOtherModules(true);
protected void doTestCompiledJava(@NotNull String javaFileName) throws Exception {
doTestCompiledJava(javaFileName, COMPARATOR_CONFIGURATION);
}
// Java-Kotlin dependencies are not supported in this method for simplicity
protected void doTestCompiledJavaAndKotlin(@NotNull String expectedFileName) throws Exception {
File expectedFile = new File(expectedFileName);
File sourcesDir = new File(expectedFileName.replaceFirst("\\.txt$", ""));
if (useJavacWrapper()) return;
List<File> kotlinSources = FileUtil.findFilesByMask(Pattern.compile(".+\\.kt"), sourcesDir);
KotlinCoreEnvironment environment =
KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(myTestRootDisposable, ConfigurationKind.JDK_ONLY);
registerJavacIfNeeded(environment);
compileKotlinToDirAndGetModule(kotlinSources, tmpdir, environment);
List<File> javaSources = FileUtil.findFilesByMask(Pattern.compile(".+\\.java"), sourcesDir);
Pair<PackageViewDescriptor, BindingContext> binaryPackageAndContext = compileJavaAndLoadTestPackageAndBindingContextFromBinary(
javaSources, tmpdir, ConfigurationKind.JDK_ONLY
);
checkJavaPackage(expectedFile, binaryPackageAndContext.first, binaryPackageAndContext.second, COMPARATOR_CONFIGURATION);
}
@NotNull
protected File getExpectedFile(@NotNull String expectedFileName) {
return new File(expectedFileName);
}
protected void doTestCompiledJavaIncludeObjectMethods(@NotNull String javaFileName) throws Exception {
doTestCompiledJava(javaFileName, RECURSIVE.renderDeclarationsFromOtherModules(true));
}
protected void doTestCompiledKotlin(@NotNull String ktFileName) throws Exception {
doTestCompiledKotlin(ktFileName, ConfigurationKind.JDK_ONLY, false);
}
protected void doTestCompiledKotlinWithTypeTable(@NotNull String ktFileName) throws Exception {
doTestCompiledKotlin(ktFileName, ConfigurationKind.JDK_ONLY, true);
}
protected void doTestCompiledKotlinWithStdlib(@NotNull String ktFileName) throws Exception {
doTestCompiledKotlin(ktFileName, ConfigurationKind.ALL, false);
}
private void doTestCompiledKotlin(
@NotNull String ktFileName, @NotNull ConfigurationKind configurationKind, boolean useTypeTableInSerializer
) throws Exception {
File ktFile = new File(ktFileName);
File txtFile = getTxtFileFromKtFile(ktFileName);
CompilerConfiguration configuration = newConfiguration(configurationKind, TestJdkKind.MOCK_JDK, getAnnotationsJar());
if (useTypeTableInSerializer) {
configuration.put(JVMConfigurationKeys.USE_TYPE_TABLE, true);
}
updateConfigurationWithLanguageVersionDirective(ktFile, configuration);
KotlinCoreEnvironment environment =
KotlinCoreEnvironment.createForTests(getTestRootDisposable(), configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
registerJavacIfNeeded(environment);
ModuleDescriptor module = compileKotlinToDirAndGetModule(Collections.singletonList(ktFile), tmpdir, environment);
PackageViewDescriptor packageFromSource = module.getPackage(TEST_PACKAGE_FQNAME);
Assert.assertEquals("test", packageFromSource.getName().asString());
PackageViewDescriptor packageFromBinary = LoadDescriptorUtil.loadTestPackageAndBindingContextFromJavaRoot(
tmpdir, getTestRootDisposable(), getJdkKind(), configurationKind, true, false, useJavacWrapper(),
configuration.get(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS)
).first;
for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(packageFromBinary.getMemberScope())) {
if (descriptor instanceof ClassDescriptor) {
assert descriptor instanceof DeserializedClassDescriptor : DescriptorUtils.getFqName(descriptor) + " is loaded as " + descriptor.getClass();
}
}
DescriptorValidator.validate(errorTypesForbidden(), packageFromSource);
DescriptorValidator.validate(new DeserializedScopeValidationVisitor(), packageFromBinary);
Configuration comparatorConfiguration = COMPARATOR_CONFIGURATION.checkPrimaryConstructors(true).checkPropertyAccessors(true).checkFunctionContracts(true);
compareDescriptors(packageFromSource, packageFromBinary, comparatorConfiguration, txtFile);
}
private static void updateConfigurationWithLanguageVersionDirective(File file, CompilerConfiguration configuration) throws IOException {
String version = InTextDirectivesUtils.findStringWithPrefixes(FileUtil.loadFile(file, true), "// LANGUAGE_VERSION:");
if (version != null) {
LanguageVersion explicitVersion = LanguageVersion.fromVersionString(version);
CommonConfigurationKeysKt.setLanguageVersionSettings(
configuration,
new LanguageVersionSettingsImpl(explicitVersion, ApiVersion.createByLanguageVersion(explicitVersion))
);
}
}
protected boolean useFastClassFilesReading() {
return false;
}
protected boolean useJavacWrapper() { return false; }
protected void registerJavacIfNeeded(KotlinCoreEnvironment environment) {}
protected void doTestJavaAgainstKotlin(String expectedFileName) throws Exception {
File expectedFile = new File(expectedFileName);
File sourcesDir = new File(expectedFileName.replaceFirst("\\.txt$", ""));
FileUtil.copyDir(sourcesDir, new File(tmpdir, "test"), pathname -> pathname.getName().endsWith(".java"));
CompilerConfiguration configuration = KotlinTestUtils.newConfiguration(ConfigurationKind.JDK_ONLY, getJdkKind());
ContentRootsKt.addKotlinSourceRoot(configuration, sourcesDir.getAbsolutePath());
JvmContentRootsKt.addJavaSourceRoot(configuration, new File("compiler/testData/loadJava/include"));
JvmContentRootsKt.addJavaSourceRoot(configuration, tmpdir);
KotlinCoreEnvironment environment =
KotlinCoreEnvironment.createForTests(getTestRootDisposable(), configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
registerJavacIfNeeded(environment);
AnalysisResult result = TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
environment.getProject(), environment.getSourceFiles(), new CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(),
configuration, environment::createPackagePartProvider
);
PackageViewDescriptor packageView = result.getModuleDescriptor().getPackage(TEST_PACKAGE_FQNAME);
checkJavaPackage(expectedFile, packageView, result.getBindingContext(), COMPARATOR_CONFIGURATION);
}
// TODO: add more tests on inherited parameter names, but currently impossible because of KT-4509
protected void doTestKotlinAgainstCompiledJavaWithKotlin(@NotNull String expectedFileName) throws Exception {
File kotlinSrc = new File(expectedFileName);
File librarySrc = new File(expectedFileName.replaceFirst("\\.kt$", ""));
File expectedFile = new File(expectedFileName.replaceFirst("\\.kt$", ".txt"));
File libraryOut = new File(tmpdir, "libraryOut");
compileKotlinWithJava(
FileUtil.findFilesByMask(Pattern.compile(".+\\.java$"), librarySrc),
FileUtil.findFilesByMask(Pattern.compile(".+\\.kt$"), librarySrc),
libraryOut,
getTestRootDisposable(),
null
);
KotlinCoreEnvironment environment = KotlinCoreEnvironment.createForTests(
getTestRootDisposable(),
newConfiguration(ConfigurationKind.JDK_ONLY, getJdkKind(), getAnnotationsJar(), libraryOut),
EnvironmentConfigFiles.JVM_CONFIG_FILES
);
registerJavacIfNeeded(environment);
KtFile ktFile = KotlinTestUtils.createFile(kotlinSrc.getPath(), FileUtil.loadFile(kotlinSrc, true), environment.getProject());
ModuleDescriptor module = JvmResolveUtil.analyzeAndCheckForErrors(Collections.singleton(ktFile), environment).getModuleDescriptor();
PackageViewDescriptor packageView = module.getPackage(TEST_PACKAGE_FQNAME);
assertFalse(packageView.isEmpty());
validateAndCompareDescriptorWithFile(packageView, COMPARATOR_CONFIGURATION.withValidationStrategy(
new DeserializedScopeValidationVisitor()
), expectedFile);
}
@NotNull
protected TestJdkKind getJdkKind() {
return TestJdkKind.MOCK_JDK;
}
protected void doTestSourceJava(@NotNull String javaFileName) throws Exception {
File originalJavaFile = new File(javaFileName);
File expectedFile = getTxtFile(javaFileName);
File testPackageDir = new File(tmpdir, "test");
assertTrue(testPackageDir.mkdir());
FileUtil.copy(originalJavaFile, new File(testPackageDir, originalJavaFile.getName()));
Pair<PackageViewDescriptor, BindingContext> javaPackageAndContext = loadTestPackageAndBindingContextFromJavaRoot(
tmpdir, getTestRootDisposable(), getJdkKind(), ConfigurationKind.JDK_ONLY, false,
false, useJavacWrapper(), null);
checkJavaPackage(
expectedFile, javaPackageAndContext.first, javaPackageAndContext.second,
COMPARATOR_CONFIGURATION.withValidationStrategy(errorTypesAllowed())
);
}
private void doTestCompiledJava(@NotNull String javaFileName, Configuration configuration) throws Exception {
File srcDir = new File(tmpdir, "src");
File compiledDir = new File(tmpdir, "compiled");
assertTrue(srcDir.mkdir());
assertTrue(compiledDir.mkdir());
List<File> srcFiles = KotlinTestUtils.createTestFiles(
new File(javaFileName).getName(), FileUtil.loadFile(new File(javaFileName), true),
new TestFileFactoryNoModules<File>() {
@NotNull
@Override
public File create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives) {
File targetFile = new File(srcDir, fileName);
try {
FileUtil.writeToFile(targetFile, text);
}
catch (IOException e) {
throw new AssertionError(e);
}
return targetFile;
}
});
Pair<PackageViewDescriptor, BindingContext> javaPackageAndContext = compileJavaAndLoadTestPackageAndBindingContextFromBinary(
srcFiles, compiledDir, ConfigurationKind.ALL
);
checkJavaPackage(getExpectedFile(javaFileName.replaceFirst("\\.java$", ".txt")), javaPackageAndContext.first, javaPackageAndContext.second, configuration);
}
@NotNull
private Pair<PackageViewDescriptor, BindingContext> compileJavaAndLoadTestPackageAndBindingContextFromBinary(
@NotNull Collection<File> javaFiles,
@NotNull File outDir,
@NotNull ConfigurationKind configurationKind
) throws IOException {
compileJavaWithAnnotationsJar(javaFiles, outDir);
return loadTestPackageAndBindingContextFromJavaRoot(outDir, myTestRootDisposable, getJdkKind(), configurationKind, true,
useFastClassFilesReading(), useJavacWrapper(), null);
}
private static void checkJavaPackage(
File txtFile,
PackageViewDescriptor javaPackage,
BindingContext bindingContext,
Configuration configuration
) {
boolean fail = false;
try {
ExpectedLoadErrorsUtil.checkForLoadErrors(javaPackage, bindingContext);
}
catch (ComparisonFailure e) {
// to let the next check run even if this one failed
System.err.println("Expected: " + e.getExpected());
System.err.println("Actual : " + e.getActual());
e.printStackTrace();
fail = true;
}
catch (AssertionError e) {
e.printStackTrace();
fail = true;
}
validateAndCompareDescriptorWithFile(javaPackage, configuration, txtFile);
if (fail) {
fail("See error above");
}
}
private File getTxtFile(String javaFileName) {
try {
String fileText = FileUtil.loadFile(new File(javaFileName));
if (useJavacWrapper() && InTextDirectivesUtils.isDirectiveDefined(fileText, "// JAVAC_EXPECTED_FILE")) {
return new File(javaFileName.replaceFirst("\\.java$", ".javac.txt"));
}
else return new File(javaFileName.replaceFirst("\\.java$", ".txt"));
}
catch (IOException e) {
return new File(javaFileName.replaceFirst("\\.java$", ".txt"));
}
}
private File getTxtFileFromKtFile(String ktFileName) {
try {
String fileText = FileUtil.loadFile(new File(ktFileName));
if (useJavacWrapper() && InTextDirectivesUtils.isDirectiveDefined(fileText, "// JAVAC_EXPECTED_FILE")) {
return new File(ktFileName.replaceFirst("\\.kt$", ".javac.txt"));
}
else return new File(ktFileName.replaceFirst("\\.kt$", ".txt"));
}
catch (IOException e) {
return new File(ktFileName.replaceFirst("\\.kt$", ".txt"));
}
}
}
@@ -0,0 +1,30 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.jvm.compiler
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractLoadJavaWithFastClassReadingTest : AbstractLoadJavaTest() {
override fun useFastClassFilesReading() = true
override fun getExpectedFile(expectedFileName: String): File {
val differentResultFile = KotlinTestUtils.replaceExtension(File(expectedFileName), "fast.txt")
if (differentResultFile.exists()) return differentResultFile
return super.getExpectedFile(expectedFileName)
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.jvm.compiler
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
import org.jetbrains.kotlin.resolve.MemberComparator
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedMemberScope
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import org.jetbrains.kotlin.test.util.DescriptorValidator
import org.jetbrains.kotlin.test.util.DescriptorValidator.ValidationVisitor
class DeserializedScopeValidationVisitor : ValidationVisitor() {
override fun validateScope(scopeOwner: DeclarationDescriptor, scope: MemberScope, collector: DescriptorValidator.DiagnosticCollector) {
super.validateScope(scopeOwner, scope, collector)
validateDeserializedScope(scopeOwner, scope)
}
}
private fun validateDeserializedScope(scopeOwner: DeclarationDescriptor, scope: MemberScope) {
val isPackageViewScope = scopeOwner is PackageViewDescriptor
if (scope is DeserializedMemberScope || isPackageViewScope) {
val relevantDescriptors = scope.getContributedDescriptors().filter { member ->
member is CallableMemberDescriptor && member.kind.isReal || (!isPackageViewScope && member is ClassDescriptor)
}
checkSorted(relevantDescriptors, scopeOwner)
}
}
private fun checkSorted(descriptors: Collection<DeclarationDescriptor>, declaration: DeclarationDescriptor) {
val serializedOnly = descriptors.filterNot { it is JavaCallableMemberDescriptor }
KtUsefulTestCase.assertOrderedEquals(
"Members of $declaration should be sorted by serialization.",
serializedOnly,
serializedOnly.sortedWith(MemberComparator.INSTANCE)
)
}
@@ -0,0 +1,135 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.jvm.compiler;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.constants.ConstantValue;
import org.jetbrains.kotlin.resolve.jvm.JvmBindingContextSlices;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import java.util.*;
import static org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase.assertNotNull;
import static org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase.assertSameElements;
public class ExpectedLoadErrorsUtil {
public static final String ANNOTATION_CLASS_NAME = "org.jetbrains.kotlin.jvm.compiler.annotation.ExpectLoadError";
public static void checkForLoadErrors(
@NotNull PackageViewDescriptor packageFromJava,
@NotNull BindingContext bindingContext
) {
Map<SourceElement, List<String>> expectedErrors = getExpectedLoadErrors(packageFromJava);
Map<SourceElement, List<String>> actualErrors = getActualLoadErrors(bindingContext);
for (SourceElement source : ContainerUtil.union(expectedErrors.keySet(), actualErrors.keySet())) {
List<String> actual = actualErrors.get(source);
List<String> expected = expectedErrors.get(source);
assertNotNull("Unexpected load error(s):\n" + actual + "\ncontainer:" + source, expected);
assertNotNull("Missing load error(s):\n" + expected + "\ncontainer:" + source, actual);
assertSameElements("Unexpected/missing load error(s)\ncontainer:" + source, actual, expected);
}
}
private static Map<SourceElement, List<String>> getExpectedLoadErrors(@NotNull PackageViewDescriptor packageFromJava) {
Map<SourceElement, List<String>> map = new HashMap<>();
packageFromJava.acceptVoid(new DeclarationDescriptorVisitorEmptyBodies<Void, Void>() {
@Override
public Void visitPackageViewDescriptor(PackageViewDescriptor descriptor, Void data) {
return visitDeclarationRecursively(descriptor, descriptor.getMemberScope());
}
@Override
public Void visitClassDescriptor(ClassDescriptor descriptor, Void data) {
return visitDeclarationRecursively(descriptor, descriptor.getDefaultType().getMemberScope());
}
@Override
public Void visitFunctionDescriptor(FunctionDescriptor descriptor, Void data) {
return visitDeclaration(descriptor);
}
@Override
public Void visitPropertyDescriptor(PropertyDescriptor descriptor, Void data) {
return visitDeclaration(descriptor);
}
private Void visitDeclaration(@NotNull DeclarationDescriptor descriptor) {
AnnotationDescriptor annotation = descriptor.getAnnotations().findAnnotation(new FqName(ANNOTATION_CLASS_NAME));
if (annotation == null) return null;
// we expect exactly one annotation argument
ConstantValue<?> argument = annotation.getAllValueArguments().values().iterator().next();
String error = (String) argument.getValue();
//noinspection ConstantConditions
List<String> errors = Arrays.asList(error.split("\\|"));
putError(map, descriptor, errors);
return null;
}
private Void visitDeclarationRecursively(@NotNull DeclarationDescriptor descriptor, @NotNull MemberScope memberScope) {
for (DeclarationDescriptor member : DescriptorUtils.getAllDescriptors(memberScope)) {
member.acceptVoid(this);
}
return visitDeclaration(descriptor);
}
});
return map;
}
private static Map<SourceElement, List<String>> getActualLoadErrors(@NotNull BindingContext bindingContext) {
Map<SourceElement, List<String>> result = new HashMap<>();
Collection<DeclarationDescriptor> descriptors = bindingContext.getKeys(JvmBindingContextSlices.LOAD_FROM_JAVA_SIGNATURE_ERRORS);
for (DeclarationDescriptor descriptor : descriptors) {
List<String> errors = bindingContext.get(JvmBindingContextSlices.LOAD_FROM_JAVA_SIGNATURE_ERRORS, descriptor);
if (errors == null) continue;
putError(result, descriptor, errors);
}
return result;
}
private static void putError(
@NotNull Map<SourceElement, List<String>> result,
@NotNull DeclarationDescriptor descriptor,
@NotNull List<String> errors
) {
assert descriptor.getOriginal() instanceof DeclarationDescriptorWithSource
: "Signature errors should be reported only on declarations with source, but " + descriptor + " found";
result.put(((DeclarationDescriptorWithSource) descriptor.getOriginal()).getSource(), errors);
}
private ExpectedLoadErrorsUtil() {
}
}
@@ -0,0 +1,150 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.jvm.compiler;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.text.StringKt;
import kotlin.collections.CollectionsKt;
import kotlin.io.FilesKt;
import kotlin.sequences.SequencesKt;
import kotlin.text.Charsets;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.common.output.outputUtils.OutputUtilsKt;
import org.jetbrains.kotlin.cli.jvm.compiler.EnvironmentConfigFiles;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.codegen.GenerationUtils;
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.config.CommonConfigurationKeys;
import org.jetbrains.kotlin.config.CompilerConfiguration;
import org.jetbrains.kotlin.config.JVMConfigurationKeys;
import org.jetbrains.kotlin.config.LanguageVersionSettings;
import org.jetbrains.kotlin.descriptors.ModuleDescriptor;
import org.jetbrains.kotlin.descriptors.PackageViewDescriptor;
import org.jetbrains.kotlin.jvm.compiler.javac.JavacRegistrarForTests;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.TestJdkKind;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class LoadDescriptorUtil {
@NotNull
public static final FqName TEST_PACKAGE_FQNAME = FqName.topLevel(Name.identifier("test"));
private LoadDescriptorUtil() {
}
@NotNull
public static ModuleDescriptor compileKotlinToDirAndGetModule(
@NotNull List<File> kotlinFiles, @NotNull File outDir, @NotNull KotlinCoreEnvironment environment
) {
GenerationState state = GenerationUtils.compileFiles(createKtFiles(kotlinFiles, environment), environment);
OutputUtilsKt.writeAllTo(state.getFactory(), outDir);
return state.getModule();
}
@NotNull
public static Pair<PackageViewDescriptor, BindingContext> loadTestPackageAndBindingContextFromJavaRoot(
@NotNull File javaRoot,
@NotNull Disposable disposable,
@NotNull TestJdkKind testJdkKind,
@NotNull ConfigurationKind configurationKind,
boolean isBinaryRoot,
boolean useFastClassReading,
boolean useJavacWrapper,
@Nullable LanguageVersionSettings explicitLanguageVersionSettings
) {
List<File> javaBinaryRoots = new ArrayList<>();
javaBinaryRoots.add(KotlinTestUtils.getAnnotationsJar());
List<File> javaSourceRoots = new ArrayList<>();
javaSourceRoots.add(new File("compiler/testData/loadJava/include"));
if (isBinaryRoot) {
javaBinaryRoots.add(javaRoot);
}
else {
javaSourceRoots.add(javaRoot);
}
CompilerConfiguration configuration =
KotlinTestUtils.newConfiguration(configurationKind, testJdkKind, javaBinaryRoots, javaSourceRoots);
configuration.put(JVMConfigurationKeys.USE_FAST_CLASS_FILES_READING, useFastClassReading);
configuration.put(JVMConfigurationKeys.USE_JAVAC, useJavacWrapper);
if (explicitLanguageVersionSettings != null) {
configuration.put(CommonConfigurationKeys.LANGUAGE_VERSION_SETTINGS, explicitLanguageVersionSettings);
}
KotlinCoreEnvironment environment =
KotlinCoreEnvironment.createForTests(disposable, configuration, EnvironmentConfigFiles.JVM_CONFIG_FILES);
if (useJavacWrapper) {
JavacRegistrarForTests.INSTANCE.registerJavac(environment);
}
AnalysisResult analysisResult = JvmResolveUtil.analyze(environment);
PackageViewDescriptor packageView = analysisResult.getModuleDescriptor().getPackage(TEST_PACKAGE_FQNAME);
return Pair.create(packageView, analysisResult.getBindingContext());
}
public static void compileJavaWithAnnotationsJar(@NotNull Collection<File> javaFiles, @NotNull File outDir) throws IOException {
List<File> classpath = new ArrayList<>();
classpath.add(ForTestCompileRuntime.runtimeJarForTests());
classpath.add(KotlinTestUtils.getAnnotationsJar());
for (File test: javaFiles) {
String content = FilesKt.readText(test, Charsets.UTF_8);
if (InTextDirectivesUtils.isDirectiveDefined(content, "ANDROID_ANNOTATIONS")) {
classpath.add(ForTestCompileRuntime.androidAnnotationsForTests());
}
}
KotlinTestUtils.compileJavaFiles(javaFiles, Arrays.asList(
"-classpath", classpath.stream().map(File::getPath).collect(Collectors.joining(File.pathSeparator)),
"-sourcepath", "compiler/testData/loadJava/include",
"-d", outDir.getPath()
));
}
@NotNull
private static List<KtFile> createKtFiles(@NotNull List<File> kotlinFiles, @NotNull KotlinCoreEnvironment environment) {
return CollectionsKt.map(kotlinFiles, kotlinFile -> {
try {
return KotlinTestUtils.createFile(kotlinFile.getName(), FileUtil.loadFile(kotlinFile, true), environment.getProject());
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
});
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.jvm.compiler.javac
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.jvm.compiler.AbstractLoadJavaTest
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractLoadJavaUsingJavacTest : AbstractLoadJavaTest() {
override fun registerJavacIfNeeded(environment: KotlinCoreEnvironment) {
environment.registerJavac()
environment.configuration.put(JVMConfigurationKeys.USE_JAVAC, true)
}
override fun useJavacWrapper() = true
override fun getExpectedFile(expectedFileName: String): File {
val differentResultFile = KotlinTestUtils.replaceExtension(File(expectedFileName), "javac.txt")
if (differentResultFile.exists()) return differentResultFile
return super.getExpectedFile(expectedFileName)
}
}
object JavacRegistrarForTests {
fun registerJavac(environment: KotlinCoreEnvironment) {
environment.registerJavac()
}
}
@@ -0,0 +1,259 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.jvm.runtime
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.checkers.KotlinMultiFileTestWithJava
import org.jetbrains.kotlin.codegen.GenerationUtils
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.config.ContentRoot
import org.jetbrains.kotlin.config.JVMConfigurationKeys
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.descriptors.impl.PackageFragmentDescriptorImpl
import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.jvm.compiler.ExpectedLoadErrorsUtil
import org.jetbrains.kotlin.jvm.compiler.LoadDescriptorUtil
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
import org.jetbrains.kotlin.load.java.structure.reflect.classId
import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader
import org.jetbrains.kotlin.load.kotlin.reflect.ReflectKotlinClass
import org.jetbrains.kotlin.load.kotlin.reflect.RuntimeModuleData
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.renderer.*
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.scopes.ChainedMemberScope
import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.resolve.scopes.MemberScopeImpl
import org.jetbrains.kotlin.test.*
import org.jetbrains.kotlin.test.KotlinTestUtils.TestFileFactoryNoModules
import org.jetbrains.kotlin.test.util.DescriptorValidator.ValidationVisitor.errorTypesForbidden
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator
import org.jetbrains.kotlin.test.util.RecursiveDescriptorComparator.Configuration
import org.jetbrains.kotlin.utils.Printer
import org.jetbrains.kotlin.utils.sure
import java.io.File
import java.net.URLClassLoader
import java.util.*
import java.util.regex.Pattern
abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdir() {
companion object {
private val renderer = DescriptorRenderer.withOptions {
withDefinedIn = false
excludedAnnotationClasses = setOf(
FqName(ExpectedLoadErrorsUtil.ANNOTATION_CLASS_NAME)
)
overrideRenderingPolicy = OverrideRenderingPolicy.RENDER_OPEN_OVERRIDE
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
includePropertyConstant = false
verbose = true
annotationArgumentsRenderingPolicy = AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY
renderDefaultAnnotationArguments = true
modifiers = DescriptorRendererModifier.ALL
}
}
protected open val defaultJdkKind: TestJdkKind = TestJdkKind.MOCK_JDK
// NOTE: this test does a dirty hack of text substitution to make all annotations defined in source code retain at runtime.
// Specifically each @interface in Java sources is extended by @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME)
// Also type related annotations are removed from Java because they are invisible at runtime
protected fun doTest(fileName: String) {
val file = File(fileName)
val text = FileUtil.loadFile(file, true)
if (InTextDirectivesUtils.isDirectiveDefined(text, "SKIP_IN_RUNTIME_TEST")) return
val jdkKind =
if (InTextDirectivesUtils.isDirectiveDefined(text, "FULL_JDK")) TestJdkKind.FULL_JDK
else defaultJdkKind
compileFile(file, text, jdkKind)
val classLoader = URLClassLoader(arrayOf(tmpdir.toURI().toURL()), ForTestCompileRuntime.runtimeAndReflectJarClassLoader())
val actual = createReflectedPackageView(classLoader, KotlinTestUtils.TEST_MODULE_NAME)
val comparatorConfiguration = Configuration(
/* checkPrimaryConstructors = */ fileName.endsWith(".kt"),
/* checkPropertyAccessors = */ true,
/* includeMethodsOfKotlinAny = */ false,
/* renderDeclarationsFromOtherModules = */ true,
/* checkFunctionContract = */ false,
// Skip Java annotation constructors because order of their parameters is not retained at runtime
{ descriptor -> !descriptor!!.isJavaAnnotationConstructor() },
errorTypesForbidden(), renderer
)
val differentResultFile = KotlinTestUtils.replaceExtension(file, "runtime.txt")
if (differentResultFile.exists()) {
RecursiveDescriptorComparator.validateAndCompareDescriptorWithFile(actual, comparatorConfiguration, differentResultFile)
return
}
val expected = LoadDescriptorUtil.loadTestPackageAndBindingContextFromJavaRoot(
tmpdir, testRootDisposable, jdkKind, ConfigurationKind.ALL, true, false, false, null
).first
RecursiveDescriptorComparator.validateAndCompareDescriptors(expected, actual, comparatorConfiguration, null)
}
private fun DeclarationDescriptor.isJavaAnnotationConstructor() =
this is ClassConstructorDescriptor &&
containingDeclaration is JavaClassDescriptor &&
containingDeclaration.kind == ClassKind.ANNOTATION_CLASS
private fun compileFile(file: File, text: String, jdkKind: TestJdkKind) {
val fileName = file.name
when {
fileName.endsWith(".java") -> {
val sources = KotlinTestUtils.createTestFiles(fileName, text, object : TestFileFactoryNoModules<File>() {
override fun create(fileName: String, text: String, directives: Map<String, String>): File {
val targetFile = File(tmpdir, fileName)
targetFile.writeText(adaptJavaSource(text))
return targetFile
}
})
LoadDescriptorUtil.compileJavaWithAnnotationsJar(sources, tmpdir)
}
fileName.endsWith(".kt") -> {
val environment = KotlinTestUtils.createEnvironmentWithJdkAndNullabilityAnnotationsFromIdea(
myTestRootDisposable, ConfigurationKind.ALL, jdkKind
)
for (root in environment.configuration.getList(JVMConfigurationKeys.CONTENT_ROOTS)) {
LOG.info("root: " + root.toString())
}
val ktFile = KotlinTestUtils.createFile(file.path, text, environment.project)
GenerationUtils.compileFileTo(ktFile, environment, tmpdir)
}
}
}
private fun createReflectedPackageView(classLoader: URLClassLoader, moduleName: String): SyntheticPackageViewForTest {
val moduleData = RuntimeModuleData.create(classLoader)
moduleData.packagePartProvider.registerModule(moduleName)
val module = moduleData.module
val generatedPackageDir = File(tmpdir, LoadDescriptorUtil.TEST_PACKAGE_FQNAME.pathSegments().single().asString())
val allClassFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.class"), generatedPackageDir)
val packageScopes = arrayListOf<MemberScope>()
val classes = arrayListOf<ClassDescriptor>()
for (classFile in allClassFiles) {
val className = classFile.toRelativeString(tmpdir).substringBeforeLast(".class").replace('/', '.').replace('\\', '.')
val klass = classLoader.loadClass(className).sure { "Couldn't load class $className" }
val binaryClass = ReflectKotlinClass.create(klass)
val header = binaryClass?.classHeader
if (header?.kind == KotlinClassHeader.Kind.FILE_FACADE || header?.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS) {
val packageView = module.getPackage(LoadDescriptorUtil.TEST_PACKAGE_FQNAME)
if (!packageScopes.contains(packageView.memberScope)) {
packageScopes.add(packageView.memberScope)
}
}
else if (header == null || header.kind == KotlinClassHeader.Kind.CLASS) {
// Either a normal Kotlin class or a Java class
val classId = klass.classId
if (!classId.isLocal) {
val classDescriptor = module.findClassAcrossModuleDependencies(classId).sure { "Couldn't resolve class $className" }
if (DescriptorUtils.isTopLevelDeclaration(classDescriptor)) {
classes.add(classDescriptor)
}
}
}
}
// Since runtime package view descriptor doesn't support getAllDescriptors(), we construct a synthetic package view here.
// It has in its scope descriptors for all the classes and top level members generated by the compiler
return SyntheticPackageViewForTest(module, packageScopes, classes)
}
private fun adaptJavaSource(text: String): String {
val typeAnnotations = arrayOf("NotNull", "Nullable", "ReadOnly", "Mutable")
val adaptedSource = typeAnnotations.fold(text) { text, annotation -> text.replace("@$annotation", "") }
if ("@Retention" !in adaptedSource) {
return adaptedSource.replace(
"@interface",
"@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @interface"
)
}
return adaptedSource
}
private class SyntheticPackageViewForTest(override val module: ModuleDescriptor,
packageScopes: List<MemberScope>,
classes: List<ClassifierDescriptor>) : PackageViewDescriptor {
private val scope: MemberScope
init {
val list = ArrayList<MemberScope>(packageScopes.size + 1)
list.add(ScopeWithClassifiers(classes))
list.addAll(packageScopes)
scope = ChainedMemberScope("synthetic package view for test", list)
}
override val fqName: FqName
get() = LoadDescriptorUtil.TEST_PACKAGE_FQNAME
override val memberScope: MemberScope
get() = scope
override val fragments: List<PackageFragmentDescriptor> = listOf(
object : PackageFragmentDescriptorImpl(module, fqName) {
override fun getMemberScope(): MemberScope = scope
}
)
override fun <R, D> accept(visitor: DeclarationDescriptorVisitor<R, D>, data: D): R =
visitor.visitPackageViewDescriptor(this, data)
override fun getContainingDeclaration() = null
override fun getOriginal() = throw UnsupportedOperationException()
override fun acceptVoid(visitor: DeclarationDescriptorVisitor<Void, Void>?) = throw UnsupportedOperationException()
override fun getName() = throw UnsupportedOperationException()
override val annotations: Annotations
get() = throw UnsupportedOperationException()
}
private class ScopeWithClassifiers(classifiers: List<ClassifierDescriptor>) : MemberScopeImpl() {
private val classifierMap = HashMap<Name, ClassifierDescriptor>()
init {
for (classifier in classifiers) {
classifierMap.put(classifier.name, classifier)?.let {
throw IllegalStateException(String.format("Redeclaration: %s (%s) and %s (%s) (no line info available)",
DescriptorUtils.getFqName(it), it,
DescriptorUtils.getFqName(classifier), classifier))
}
}
}
override fun getContributedClassifier(name: Name, location: LookupLocation): ClassifierDescriptor? = classifierMap[name]
override fun getContributedDescriptors(kindFilter: DescriptorKindFilter, nameFilter: (Name) -> Boolean): Collection<DeclarationDescriptor> = classifierMap.values
override fun printScopeStructure(p: Printer) {
p.println("runtime descriptor loader test")
}
}
}
private val LOG = Logger.getInstance(KotlinMultiFileTestWithJava::class.java)
@@ -0,0 +1,159 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.renderer
import com.intellij.openapi.editor.impl.DocumentImpl
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.frontend.di.createContainerForLazyResolve
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.CompilerEnvironment
import org.jetbrains.kotlin.resolve.TargetEnvironment
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.resolve.lazy.ResolveSession
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
import java.io.File
import java.util.*
abstract class AbstractDescriptorRendererTest : KotlinTestWithEnvironment() {
protected open fun getDescriptor(declaration: KtDeclaration, container: ComponentProvider): DeclarationDescriptor {
return container.get<ResolveSession>().resolveToDescriptor(declaration)
}
protected open val targetEnvironment: TargetEnvironment
get() = CompilerEnvironment
fun doTest(path: String) {
val fileText = FileUtil.loadFile(File(path), true)
val psiFile = KtPsiFactory(project).createFile(fileText)
val context = TopDownAnalyzerFacadeForJVM.createContextWithSealedModule(project, environment.configuration)
val container = createContainerForLazyResolve(
context,
FileBasedDeclarationProviderFactory(context.storageManager, listOf(psiFile)),
CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(),
JvmPlatform,
JvmTarget.JVM_1_6,
targetEnvironment,
LanguageVersionSettingsImpl.DEFAULT
)
val resolveSession = container.get<ResolveSession>()
context.initializeModuleContents(resolveSession.packageFragmentProvider)
val descriptors = ArrayList<DeclarationDescriptor>()
psiFile.accept(object : KtVisitorVoid() {
override fun visitKtFile(file: KtFile) {
val fqName = file.packageFqName
if (!fqName.isRoot) {
val packageDescriptor = context.module.getPackage(fqName)
descriptors.add(packageDescriptor)
}
file.acceptChildren(this)
}
override fun visitParameter(parameter: KtParameter) {
val declaringElement = parameter.parent.parent
when (declaringElement) {
is KtFunctionType -> return
is KtNamedFunction ->
addCorrespondingParameterDescriptor(getDescriptor(declaringElement, container) as FunctionDescriptor, parameter)
is KtPrimaryConstructor -> {
val ktClassOrObject: KtClassOrObject = declaringElement.getContainingClassOrObject()
val classDescriptor = getDescriptor(ktClassOrObject, container) as ClassDescriptor
addCorrespondingParameterDescriptor(classDescriptor.unsubstitutedPrimaryConstructor!!, parameter)
}
else -> super.visitParameter(parameter)
}
}
override fun visitPropertyAccessor(accessor: KtPropertyAccessor) {
val property = accessor.property
val propertyDescriptor = getDescriptor(property, container) as PropertyDescriptor
if (accessor.isGetter) {
descriptors.add(propertyDescriptor.getter!!)
}
else {
descriptors.add(propertyDescriptor.setter!!)
}
accessor.acceptChildren(this)
}
override fun visitAnonymousInitializer(initializer: KtAnonymousInitializer) {
initializer.acceptChildren(this)
}
override fun visitDeclaration(element: KtDeclaration) {
val descriptor = getDescriptor(element, container)
descriptors.add(descriptor)
if (descriptor is ClassDescriptor) {
// if class has primary constructor then we visit it later, otherwise add it artificially
if (element !is KtClassOrObject || !element.hasExplicitPrimaryConstructor()) {
if (descriptor.unsubstitutedPrimaryConstructor != null) {
descriptors.add(descriptor.unsubstitutedPrimaryConstructor!!)
}
}
}
element.acceptChildren(this)
}
override fun visitKtElement(element: KtElement) {
element.acceptChildren(this)
}
private fun addCorrespondingParameterDescriptor(functionDescriptor: FunctionDescriptor, parameter: KtParameter) {
for (valueParameterDescriptor in functionDescriptor.valueParameters) {
if (valueParameterDescriptor.name == parameter.nameAsName) {
descriptors.add(valueParameterDescriptor)
}
}
parameter.acceptChildren(this)
}
})
val renderer = DescriptorRenderer.withOptions {
classifierNamePolicy = ClassifierNamePolicy.FULLY_QUALIFIED
modifiers = DescriptorRendererModifier.ALL
annotationArgumentsRenderingPolicy = AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY
}
val renderedDescriptors = descriptors.map { renderer.render(it) }.joinToString(separator = "\n")
val document = DocumentImpl(psiFile.text)
KtUsefulTestCase.assertSameLines(KotlinTestUtils.getLastCommentedLines(document), renderedDescriptors)
}
override fun createEnvironment(): KotlinCoreEnvironment {
return createEnvironmentWithMockJdk(ConfigurationKind.JDK_ONLY)
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve;
import org.jetbrains.kotlin.checkers.TestCheckerUtil;
import org.jetbrains.kotlin.psi.KtFile;
public abstract class AbstractResolveTest extends ExtensibleResolveTestCase {
@Override
protected ExpectedResolveData getExpectedResolveData() {
return new ExpectedResolveData(
ExpectedResolveDataUtil.prepareDefaultNameToDescriptors(getEnvironment()),
ExpectedResolveDataUtil.prepareDefaultNameToDeclaration(getEnvironment())
) {
@Override
protected KtFile createKtFile(String fileName, String text) {
return TestCheckerUtil.createCheckAndReturnPsiFile(fileName, text, getProject());
}
};
}
}
@@ -0,0 +1,390 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiQualifiedNamedElement;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.DefaultBuiltIns;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.diagnostics.Diagnostic;
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils;
import org.jetbrains.kotlin.diagnostics.Errors;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil;
import org.jetbrains.kotlin.types.ErrorUtils;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.TypeConstructor;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.jetbrains.kotlin.resolve.BindingContext.AMBIGUOUS_REFERENCE_TARGET;
import static org.jetbrains.kotlin.resolve.BindingContext.REFERENCE_TARGET;
import static org.junit.Assert.*;
public abstract class ExpectedResolveData {
protected static final String STANDARD_PREFIX = "kotlin::";
private static class Position {
private final PsiElement element;
private Position(KtFile file, int offset) {
this.element = file.findElementAt(offset);
}
public PsiElement getElement() {
return element;
}
@Override
public String toString() {
return DiagnosticUtils.atLocation(element);
}
}
private final Map<String, Position> declarationToPosition = Maps.newHashMap();
private final Map<Position, String> positionToReference = Maps.newHashMap();
private final Map<Position, String> positionToType = Maps.newHashMap();
private final Map<String, DeclarationDescriptor> nameToDescriptor;
private final Map<String, PsiElement> nameToPsiElement;
public ExpectedResolveData(Map<String, DeclarationDescriptor> nameToDescriptor, Map<String, PsiElement> nameToPsiElement) {
this.nameToDescriptor = nameToDescriptor;
this.nameToPsiElement = nameToPsiElement;
}
public final KtFile createFileFromMarkedUpText(String fileName, String text) {
Map<String, Integer> declarationToIntPosition = Maps.newHashMap();
Map<Integer, String> intPositionToReference = Maps.newHashMap();
Map<Integer, String> intPositionToType = Maps.newHashMap();
Pattern pattern = Pattern.compile("(~[^~]+~)|(`[^`]+`)");
while (true) {
Matcher matcher = pattern.matcher(text);
if (!matcher.find()) break;
String group = matcher.group();
String name = group.substring(1, group.length() - 1);
int start = matcher.start();
if (group.startsWith("~")) {
if (declarationToIntPosition.put(name, start) != null) {
throw new IllegalArgumentException("Redeclaration: " + name);
}
}
else if (group.startsWith("`")) {
if (name.startsWith(":")) {
intPositionToType.put(start - 1, name.substring(1));
}
else {
intPositionToReference.put(start, name);
}
}
else {
throw new IllegalStateException();
}
text = text.substring(0, start) + text.substring(matcher.end());
}
KtFile ktFile = createKtFile(fileName, text);
for (Map.Entry<Integer, String> entry : intPositionToType.entrySet()) {
positionToType.put(new Position(ktFile, entry.getKey()), entry.getValue());
}
for (Map.Entry<String, Integer> entry : declarationToIntPosition.entrySet()) {
declarationToPosition.put(entry.getKey(), new Position(ktFile, entry.getValue()));
}
for (Map.Entry<Integer, String> entry : intPositionToReference.entrySet()) {
positionToReference.put(new Position(ktFile, entry.getKey()), entry.getValue());
}
return ktFile;
}
protected abstract KtFile createKtFile(String fileName, String text);
protected static BindingContext analyze(List<KtFile> files, KotlinCoreEnvironment environment) {
if (files.isEmpty()) {
System.err.println("Suspicious: no files");
return BindingContext.EMPTY;
}
return JvmResolveUtil.analyze(files, environment).getBindingContext();
}
public final void checkResult(BindingContext bindingContext) {
Set<PsiElement> unresolvedReferences = Sets.newHashSet();
for (Diagnostic diagnostic : bindingContext.getDiagnostics()) {
if (Errors.UNRESOLVED_REFERENCE_DIAGNOSTICS.contains(diagnostic.getFactory())) {
unresolvedReferences.add(diagnostic.getPsiElement());
}
}
Map<String, PsiElement> nameToDeclaration = Maps.newHashMap();
Map<PsiElement, String> declarationToName = Maps.newHashMap();
for (Map.Entry<String, Position> entry : declarationToPosition.entrySet()) {
String name = entry.getKey();
Position position = entry.getValue();
PsiElement element = position.getElement();
PsiElement ancestorOfType;
if (name.equals("file")) {
ancestorOfType = element.getContainingFile();
}
else {
ancestorOfType = getAncestorOfType(KtDeclaration.class, element);
if (ancestorOfType == null) {
KtPackageDirective directive = getAncestorOfType(KtPackageDirective.class, element);
assert directive != null : "Not a declaration: " + name;
ancestorOfType = element;
}
}
nameToDeclaration.put(name, ancestorOfType);
declarationToName.put(ancestorOfType, name);
}
for (Map.Entry<Position, String> entry : positionToReference.entrySet()) {
Position position = entry.getKey();
String name = entry.getValue();
PsiElement element = position.getElement();
KtReferenceExpression referenceExpression = PsiTreeUtil.getParentOfType(element, KtReferenceExpression.class);
DeclarationDescriptor referenceTarget = bindingContext.get(REFERENCE_TARGET, referenceExpression);
if ("!".equals(name)) {
assertTrue(
"Must have been unresolved: " +
renderReferenceInContext(referenceExpression) +
" but was resolved to " + renderNullableDescriptor(referenceTarget),
unresolvedReferences.contains(referenceExpression));
assertTrue(
String.format("Reference =%s= has a reference target =%s= but expected to be unresolved",
renderReferenceInContext(referenceExpression), renderNullableDescriptor(referenceTarget)),
referenceTarget == null);
continue;
}
if ("!!".equals(name)) {
assertTrue(
"Must have been resolved to multiple descriptors: " +
renderReferenceInContext(referenceExpression) +
" but was resolved to " + renderNullableDescriptor(referenceTarget),
bindingContext.get(AMBIGUOUS_REFERENCE_TARGET, referenceExpression) != null);
continue;
}
else if ("!null".equals(name)) {
assertTrue(
"Must have been resolved to null: " +
renderReferenceInContext(referenceExpression) +
" but was resolved to " + renderNullableDescriptor(referenceTarget),
referenceTarget == null
);
continue;
}
else if ("!error".equals(name)) {
assertTrue(
"Must have been resolved to error: " +
renderReferenceInContext(referenceExpression) +
" but was resolved to " + renderNullableDescriptor(referenceTarget),
ErrorUtils.isError(referenceTarget)
);
continue;
}
PsiElement expected = nameToDeclaration.get(name);
if (expected == null) {
expected = nameToPsiElement.get(name);
}
KtReferenceExpression reference = getAncestorOfType(KtReferenceExpression.class, element);
if (expected == null && name.startsWith(STANDARD_PREFIX)) {
DeclarationDescriptor expectedDescriptor = nameToDescriptor.get(name);
KtTypeReference typeReference = getAncestorOfType(KtTypeReference.class, element);
if (expectedDescriptor != null) {
DeclarationDescriptor actual = bindingContext.get(REFERENCE_TARGET, reference);
assertDescriptorsEqual("Expected: " + name, expectedDescriptor.getOriginal(), actual == null
? null
: actual.getOriginal());
continue;
}
KotlinType actualType = bindingContext.get(BindingContext.TYPE, typeReference);
assertNotNull("Type " + name + " not resolved for reference " + name, actualType);
ClassifierDescriptor expectedClass = getBuiltinClass(name.substring(STANDARD_PREFIX.length()));
assertTypeConstructorEquals("Type resolution mismatch: ", expectedClass.getTypeConstructor(), actualType.getConstructor());
continue;
}
assert expected != null : "No declaration for " + name;
if (referenceTarget instanceof PackageViewDescriptor) {
KtPackageDirective expectedDirective = PsiTreeUtil.getParentOfType(expected, KtPackageDirective.class);
FqName expectedFqName;
if (expectedDirective != null) {
expectedFqName = expectedDirective.getFqName();
}
else if (expected instanceof PsiQualifiedNamedElement) {
String qualifiedName = ((PsiQualifiedNamedElement) expected).getQualifiedName();
assert qualifiedName != null : "No qualified name for " + name;
expectedFqName = new FqName(qualifiedName);
}
else {
throw new IllegalStateException(expected.getClass().getName() + " name=" + name);
}
assertEquals(expectedFqName, ((PackageViewDescriptor) referenceTarget).getFqName());
continue;
}
PsiElement actual = referenceTarget == null
? bindingContext.get(BindingContext.LABEL_TARGET, referenceExpression)
: DescriptorToSourceUtils.descriptorToDeclaration(referenceTarget);
if (actual instanceof KtSimpleNameExpression) {
actual = ((KtSimpleNameExpression)actual).getIdentifier();
}
String actualName = null;
if (actual != null) {
actualName = declarationToName.get(actual);
if (actualName == null) {
actualName = actual.toString();
}
}
assertNotNull(element.getText(), reference);
assertEquals(
"Reference `" + name + "`" + renderReferenceInContext(reference) + " is resolved into " + actualName + ".",
expected, actual);
}
for (Map.Entry<Position, String> entry : positionToType.entrySet()) {
Position position = entry.getKey();
String typeName = entry.getValue();
PsiElement element = position.getElement();
KtExpression expression = getAncestorOfType(KtExpression.class, element);
KotlinType expressionType = bindingContext.getType(expression);
TypeConstructor expectedTypeConstructor;
if (typeName.startsWith(STANDARD_PREFIX)) {
String name = typeName.substring(STANDARD_PREFIX.length());
ClassifierDescriptor expectedClass = getBuiltinClass(name);
expectedTypeConstructor = expectedClass.getTypeConstructor();
}
else {
Position declarationPosition = declarationToPosition.get(typeName);
assertNotNull("Undeclared: " + typeName, declarationPosition);
PsiElement declElement = declarationPosition.getElement();
assertNotNull(declarationPosition);
KtDeclaration declaration = getAncestorOfType(KtDeclaration.class, declElement);
assertNotNull(declaration);
if (declaration instanceof KtClass) {
ClassDescriptor classDescriptor = bindingContext.get(BindingContext.CLASS, declaration);
expectedTypeConstructor = classDescriptor.getTypeConstructor();
}
else if (declaration instanceof KtTypeParameter) {
TypeParameterDescriptor typeParameterDescriptor = bindingContext.get(BindingContext.TYPE_PARAMETER, (KtTypeParameter) declaration);
expectedTypeConstructor = typeParameterDescriptor.getTypeConstructor();
}
else {
fail("Unsupported declaration: " + declaration);
return;
}
}
assertNotNull(expression.getText() + " type is null", expressionType);
assertTypeConstructorEquals("At " + position + ": ", expectedTypeConstructor, expressionType.getConstructor());
}
}
private static void assertTypeConstructorEquals(String message, TypeConstructor expected, TypeConstructor actual) {
assertDescriptorsEqual(message, expected.getDeclarationDescriptor(), actual.getDeclarationDescriptor());
}
private static void assertDescriptorsEqual(String message, DeclarationDescriptor expected, DeclarationDescriptor actual) {
if (DescriptorEquivalenceForOverrides.INSTANCE.areEquivalent(expected, actual)) {
return;
}
String formatted = "";
if (message != null) {
formatted = message + " ";
}
fail(formatted + "expected same:<" + expected + "> was not:<" + actual
+ ">");
}
@NotNull
public static ClassifierDescriptor getBuiltinClass(String nameOrFqName) {
ClassifierDescriptor expectedClass;
if (nameOrFqName.indexOf('.') >= 0) {
expectedClass = DefaultBuiltIns.getInstance().getBuiltInClassByFqNameNullable(FqName.fromSegments(Arrays.asList(nameOrFqName.split("\\."))));
}
else {
expectedClass = DefaultBuiltIns.getInstance().getBuiltInClassByNameNullable(Name.identifier(nameOrFqName));
}
assertNotNull("Expected class not found: " + nameOrFqName, expectedClass);
return expectedClass;
}
private static String renderReferenceInContext(KtReferenceExpression referenceExpression) {
KtExpression statement = referenceExpression;
while (true) {
PsiElement parent = statement.getParent();
if (!(parent instanceof KtExpression)) break;
if (parent instanceof KtBlockExpression) break;
statement = (KtExpression) parent;
}
KtDeclaration declaration = PsiTreeUtil.getParentOfType(referenceExpression, KtDeclaration.class);
return referenceExpression.getText() + " at " + DiagnosticUtils.atLocation(referenceExpression) +
" in " + statement.getText() + (declaration == null ? "" : " in " + declaration.getText());
}
private static <T> T getAncestorOfType(Class<T> type, PsiElement element) {
while (element != null && !type.isInstance(element)) {
element = element.getParent();
}
@SuppressWarnings({"unchecked", "UnnecessaryLocalVariable"})
T result = (T) element;
return result;
}
@NotNull
private static String renderNullableDescriptor(@Nullable DeclarationDescriptor d) {
return d == null ? "<null>" : DescriptorRenderer.FQ_NAMES_IN_TYPES.render(d);
}
}
@@ -0,0 +1,186 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.builtins.DefaultBuiltIns;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.config.CommonConfigurationKeysKt;
import org.jetbrains.kotlin.config.LanguageVersionSettings;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.impl.ModuleDescriptorImpl;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall;
import org.jetbrains.kotlin.resolve.calls.results.OverloadResolutionResults;
import org.jetbrains.kotlin.resolve.calls.smartcasts.DataFlowInfoFactory;
import org.jetbrains.kotlin.resolve.descriptorUtil.DescriptorUtilsKt;
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil;
import org.jetbrains.kotlin.resolve.scopes.ImportingScope;
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeImpl;
import org.jetbrains.kotlin.resolve.scopes.LexicalScopeKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.tests.di.ContainerForTests;
import org.jetbrains.kotlin.tests.di.InjectionKt;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.kotlin.types.expressions.ExpressionTypingContext;
import org.jetbrains.kotlin.types.expressions.ExpressionTypingUtils;
import org.jetbrains.kotlin.types.expressions.FakeCallKind;
import java.util.*;
import static org.jetbrains.kotlin.psi.KtPsiFactoryKt.KtPsiFactory;
import static org.junit.Assert.assertNotNull;
public class ExpectedResolveDataUtil {
private ExpectedResolveDataUtil() {
}
@NotNull
public static Map<String, DeclarationDescriptor> prepareDefaultNameToDescriptors(@NotNull KotlinCoreEnvironment environment) {
Project project = environment.getProject();
KotlinBuiltIns builtIns = DefaultBuiltIns.getInstance();
Map<String, DeclarationDescriptor> nameToDescriptor = new HashMap<>();
nameToDescriptor.put("kotlin::Int.plus(Int)", standardFunction(builtIns.getInt(), "plus", project, environment, builtIns.getIntType()));
FunctionDescriptor descriptorForGet = standardFunction(builtIns.getArray(), "get", project, environment, builtIns.getIntType());
nameToDescriptor.put("kotlin::Array.get(Int)", descriptorForGet.getOriginal());
nameToDescriptor.put("kotlin::Int.compareTo(Double)",
standardFunction(builtIns.getInt(), "compareTo", project, environment, builtIns.getDoubleType()));
@NotNull
FunctionDescriptor descriptorForSet = standardFunction(builtIns.getArray(), "set", project, environment,
builtIns.getIntType(), builtIns.getIntType());
nameToDescriptor.put("kotlin::Array.set(Int, Int)", descriptorForSet.getOriginal());
return nameToDescriptor;
}
@NotNull
public static Map<String, PsiElement> prepareDefaultNameToDeclaration(@NotNull KotlinCoreEnvironment environment) {
Project project = environment.getProject();
Map<String, PsiElement> nameToDeclaration = new HashMap<>();
PsiClass java_util_Collections = findClass("java.util.Collections", environment);
nameToDeclaration.put("java::java.util.Collections.emptyList()", findMethod(java_util_Collections, "emptyList"));
nameToDeclaration.put("java::java.util.Collections", java_util_Collections);
PsiClass java_util_List = findClass("java.util.ArrayList", environment);
nameToDeclaration.put("java::java.util.List", findClass("java.util.List", environment));
nameToDeclaration.put("java::java.util.ArrayList", java_util_List);
nameToDeclaration.put("java::java.util.ArrayList.set()", java_util_List.findMethodsByName("set", true)[0]);
nameToDeclaration.put("java::java.util.ArrayList.get()", java_util_List.findMethodsByName("get", true)[0]);
nameToDeclaration.put("java::java", findPackage("java", project));
nameToDeclaration.put("java::java.util", findPackage("java.util", project));
nameToDeclaration.put("java::java.lang", findPackage("java.lang", project));
nameToDeclaration.put("java::java.lang.Object", findClass("java.lang.Object", environment));
nameToDeclaration.put("java::java.lang.Comparable", findClass("java.lang.Comparable", environment));
PsiClass java_lang_System = findClass("java.lang.System", environment);
nameToDeclaration.put("java::java.lang.System", java_lang_System);
PsiMethod[] methods = findClass("java.io.PrintStream", environment).findMethodsByName("print", true);
nameToDeclaration.put("java::java.io.PrintStream.print(Object)", methods[8]);
nameToDeclaration.put("java::java.io.PrintStream.print(Int)", methods[2]);
nameToDeclaration.put("java::java.io.PrintStream.print(char[])", methods[6]);
nameToDeclaration.put("java::java.io.PrintStream.print(Double)", methods[5]);
PsiField outField = java_lang_System.findFieldByName("out", true);
assertNotNull("'out' property wasn't found", outField);
nameToDeclaration.put("java::java.lang.System.out", outField);
PsiClass java_lang_Number = findClass("java.lang.Number", environment);
nameToDeclaration.put("java::java.lang.Number", java_lang_Number);
nameToDeclaration.put("java::java.lang.Number.intValue()", java_lang_Number.findMethodsByName("intValue", true)[0]);
return nameToDeclaration;
}
@NotNull
private static PsiElement findPackage(String qualifiedName, Project project) {
JavaPsiFacade javaFacade = JavaPsiFacade.getInstance(project);
PsiPackage javaFacadePackage = javaFacade.findPackage(qualifiedName);
assertNotNull("Package wasn't found: " + qualifiedName, javaFacadePackage);
return javaFacadePackage;
}
@NotNull
private static PsiMethod findMethod(PsiClass psiClass, String name) {
PsiMethod[] emptyLists = psiClass.findMethodsByName(name, true);
return emptyLists[0];
}
@NotNull
private static PsiClass findClass(String qualifiedName, KotlinCoreEnvironment environment) {
ModuleDescriptor module = JvmResolveUtil.analyze(environment).getModuleDescriptor();
ClassDescriptor classDescriptor = DescriptorUtilsKt.resolveTopLevelClass(module, new FqName(qualifiedName), NoLookupLocation.FROM_TEST);
assertNotNull("Class descriptor wasn't resolved: " + qualifiedName, classDescriptor);
PsiClass psiClass = (PsiClass) DescriptorToSourceUtils.getSourceFromDescriptor(classDescriptor);
assertNotNull("Class declaration wasn't found: " + classDescriptor, psiClass);
return psiClass;
}
@NotNull
private static FunctionDescriptor standardFunction(
ClassDescriptor classDescriptor,
String name,
Project project,
KotlinCoreEnvironment environment,
KotlinType... parameterTypes
) {
ModuleDescriptorImpl emptyModule = KotlinTestUtils.createEmptyModule();
ContainerForTests container = InjectionKt.createContainerForTests(project, emptyModule);
emptyModule.setDependencies(emptyModule);
emptyModule.initialize(PackageFragmentProvider.Empty.INSTANCE);
LexicalScopeImpl lexicalScope = new LexicalScopeImpl(ImportingScope.Empty.INSTANCE, classDescriptor, false,
classDescriptor.getThisAsReceiverParameter(),
LexicalScopeKind.SYNTHETIC);
LanguageVersionSettings languageVersionSettings = CommonConfigurationKeysKt.getLanguageVersionSettings(environment.getConfiguration());
ExpressionTypingContext context = ExpressionTypingContext.newContext(
new BindingTraceContext(), lexicalScope,
DataFlowInfoFactory.EMPTY, TypeUtils.NO_EXPECTED_TYPE, languageVersionSettings);
KtExpression callElement = KtPsiFactory(project).createExpression(name);
TemporaryBindingTrace traceWithFakeArgumentInfo =
TemporaryBindingTrace.create(context.trace, "trace to store fake argument for", name);
List<KtExpression> fakeArguments = new ArrayList<>(parameterTypes.length);
for (KotlinType type : parameterTypes) {
fakeArguments.add(ExpressionTypingUtils.createFakeExpressionOfType(
project, traceWithFakeArgumentInfo, "fakeArgument" + fakeArguments.size(), type
));
}
OverloadResolutionResults<FunctionDescriptor> functions = container.getFakeCallResolver().resolveFakeCall(
context, null, Name.identifier(name), callElement, callElement, FakeCallKind.OTHER, fakeArguments
);
for (ResolvedCall<? extends FunctionDescriptor> resolvedCall : functions.getResultingCalls()) {
List<ValueParameterDescriptor> unsubstitutedValueParameters = resolvedCall.getResultingDescriptor().getValueParameters();
for (int i = 0, unsubstitutedValueParametersSize = unsubstitutedValueParameters.size(); i < unsubstitutedValueParametersSize; i++) {
ValueParameterDescriptor unsubstitutedValueParameter = unsubstitutedValueParameters.get(i);
if (unsubstitutedValueParameter.getType().equals(parameterTypes[i])) {
return resolvedCall.getResultingDescriptor();
}
}
}
throw new IllegalArgumentException("Not found: kotlin::" + classDescriptor.getName() + "." + name + "(" +
Arrays.toString(parameterTypes) + ")");
}
}
@@ -0,0 +1,65 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.test.ConfigurationKind;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment;
import java.io.File;
import java.util.List;
import java.util.Map;
public abstract class ExtensibleResolveTestCase extends KotlinTestWithEnvironment {
private ExpectedResolveData expectedResolveData;
@Override
protected KotlinCoreEnvironment createEnvironment() {
return createEnvironmentWithMockJdk(ConfigurationKind.JDK_ONLY);
}
@Override
protected void setUp() throws Exception {
super.setUp();
expectedResolveData = getExpectedResolveData();
}
@Override
protected void tearDown() throws Exception {
expectedResolveData = null;
super.tearDown();
}
protected abstract ExpectedResolveData getExpectedResolveData();
protected void doTest(@NonNls String filePath) throws Exception {
File file = new File(filePath);
String text = KotlinTestUtils.doLoadFile(file);
List<KtFile> files = KotlinTestUtils.createTestFiles("file.kt", text, new KotlinTestUtils.TestFileFactoryNoModules<KtFile>() {
@NotNull
@Override
public KtFile create(@NotNull String fileName, @NotNull String text, @NotNull Map<String, String> directives) {
return expectedResolveData.createFileFromMarkedUpText(fileName, text);
}
});
expectedResolveData.checkResult(ExpectedResolveData.analyze(files, getEnvironment()));
}
}
@@ -0,0 +1,390 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.annotation;
import com.intellij.openapi.util.text.StringUtil;
import kotlin.Unit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.analyzer.AnalysisResult;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor;
import org.jetbrains.kotlin.descriptors.annotations.AnnotationUseSiteTarget;
import org.jetbrains.kotlin.descriptors.annotations.Annotations;
import org.jetbrains.kotlin.descriptors.impl.AnonymousFunctionDescriptor;
import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.psi.KtAnnotationUseSiteTarget;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.renderer.AnnotationArgumentsRenderingPolicy;
import org.jetbrains.kotlin.renderer.ClassifierNamePolicy;
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isNonCompanionObject;
public abstract class AbstractAnnotationDescriptorResolveTest extends KotlinTestWithEnvironment {
private static final DescriptorRenderer WITH_ANNOTATION_ARGUMENT_TYPES = DescriptorRenderer.Companion.withOptions(
options -> {
options.setVerbose(true);
options.setAnnotationArgumentsRenderingPolicy(AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY);
options.setClassifierNamePolicy(ClassifierNamePolicy.SHORT.INSTANCE);
options.setModifiers(DescriptorRendererModifier.ALL);
return Unit.INSTANCE;
}
);
private static final String PATH = "compiler/testData/resolveAnnotations/testFile.kt";
private static final FqName PACKAGE = new FqName("test");
protected BindingContext context;
@Override
protected KotlinCoreEnvironment createEnvironment() {
return KotlinTestUtils.createEnvironmentWithMockJdkAndIdeaAnnotations(getTestRootDisposable());
}
protected void doTest(@NotNull String content, @NotNull String expectedAnnotation) {
checkAnnotationOnAllExceptLocalDeclarations(content, expectedAnnotation);
checkAnnotationOnLocalDeclarations(expectedAnnotation);
}
protected void checkAnnotationOnAllExceptLocalDeclarations(String content, String expectedAnnotation) {
KtFile testFile = getFile(content);
PackageFragmentDescriptor testPackage = getPackage(testFile);
checkAnnotationsOnFile(expectedAnnotation, testFile);
ClassDescriptor myClass = getClassDescriptor(testPackage, "MyClass");
checkDescriptor(expectedAnnotation, myClass);
ClassDescriptor companionObjectDescriptor = myClass.getCompanionObjectDescriptor();
assert companionObjectDescriptor != null : "Cannot find companion object for class " + myClass.getName();
checkDescriptor(expectedAnnotation, companionObjectDescriptor);
checkDescriptor(expectedAnnotation, getInnerClassDescriptor(myClass, "InnerClass"));
FunctionDescriptor foo = getFunctionDescriptor(myClass, "foo");
checkAnnotationsOnFunction(expectedAnnotation, foo);
SimpleFunctionDescriptor anonymousFun = getAnonymousFunDescriptor();
if (anonymousFun instanceof AnonymousFunctionDescriptor) {
for (ValueParameterDescriptor descriptor : anonymousFun.getValueParameters()) {
List<VariableDescriptor> destructuringVariables = ValueParameterDescriptorImpl.getDestructuringVariablesOrNull(descriptor);
if (destructuringVariables == null) continue;
for (VariableDescriptor entry : destructuringVariables) {
checkDescriptor(expectedAnnotation, entry);
}
}
}
PropertyDescriptor prop = getPropertyDescriptor(myClass, "prop");
checkAnnotationsOnProperty(expectedAnnotation, prop);
FunctionDescriptor topFoo = getFunctionDescriptor(testPackage, "topFoo");
checkAnnotationsOnFunction(expectedAnnotation, topFoo);
PropertyDescriptor topProp = getPropertyDescriptor(testPackage, "topProp", true);
checkAnnotationsOnProperty(expectedAnnotation, topProp);
checkDescriptor(expectedAnnotation, getClassDescriptor(testPackage, "MyObject"));
checkDescriptor(expectedAnnotation, getConstructorParameterDescriptor(myClass, "consProp"));
checkDescriptor(expectedAnnotation, getConstructorParameterDescriptor(myClass, "param"));
}
private void checkAnnotationsOnFile(String expectedAnnotation, KtFile file) {
String actualAnnotation = StringUtil.join(file.getAnnotationEntries(), annotationEntry -> {
AnnotationDescriptor annotationDescriptor = context.get(BindingContext.ANNOTATION, annotationEntry);
assertNotNull(annotationDescriptor);
KtAnnotationUseSiteTarget target = annotationEntry.getUseSiteTarget();
if (target != null) {
return WITH_ANNOTATION_ARGUMENT_TYPES.renderAnnotation(
annotationDescriptor, target.getAnnotationUseSiteTarget());
}
return WITH_ANNOTATION_ARGUMENT_TYPES.renderAnnotation(annotationDescriptor, null);
}, " ");
String expectedAnnotationWithTarget = "@" + AnnotationUseSiteTarget.FILE.getRenderName() + ":" + expectedAnnotation.substring(1);
assertEquals(expectedAnnotationWithTarget, actualAnnotation);
}
private void checkAnnotationOnLocalDeclarations(String expectedAnnotation) {
checkDescriptor(expectedAnnotation, getLocalClassDescriptor("LocalClass"));
checkDescriptor(expectedAnnotation, getLocalObjectDescriptor("LocalObject"));
checkDescriptor(expectedAnnotation, getLocalFunDescriptor("localFun"));
checkDescriptor(expectedAnnotation, getLocalVarDescriptor(context, "localVar"));
}
private static void checkAnnotationsOnProperty(String expectedAnnotation, PropertyDescriptor prop) {
checkDescriptorWithTarget(expectedAnnotation, prop, AnnotationUseSiteTarget.FIELD);
checkDescriptor(expectedAnnotation, prop.getGetter());
PropertySetterDescriptor propSetter = prop.getSetter();
assertNotNull(propSetter);
checkAnnotationsOnFunction(expectedAnnotation, propSetter);
}
private static void checkAnnotationsOnFunction(String expectedAnnotation, FunctionDescriptor foo) {
checkDescriptor(expectedAnnotation, foo);
checkDescriptor(expectedAnnotation, getFunctionParameterDescriptor(foo, "param"));
}
@NotNull
protected static FunctionDescriptor getFunctionDescriptor(@NotNull PackageFragmentDescriptor packageView, @NotNull String name) {
Name functionName = Name.identifier(name);
MemberScope memberScope = packageView.getMemberScope();
Collection<SimpleFunctionDescriptor> functions = memberScope.getContributedFunctions(functionName, NoLookupLocation.FROM_TEST);
assert functions.size() == 1 : "Failed to find function " + functionName + " in class" + "." + packageView.getName();
return functions.iterator().next();
}
@NotNull
private static FunctionDescriptor getFunctionDescriptor(@NotNull ClassDescriptor classDescriptor, @NotNull String name) {
Name functionName = Name.identifier(name);
MemberScope memberScope = classDescriptor.getMemberScope(Collections.emptyList());
Collection<SimpleFunctionDescriptor> functions = memberScope.getContributedFunctions(functionName, NoLookupLocation.FROM_TEST);
assert functions.size() == 1 : "Failed to find function " + functionName + " in class" + "." + classDescriptor.getName();
return functions.iterator().next();
}
@Nullable
protected static PropertyDescriptor getPropertyDescriptor(@NotNull PackageFragmentDescriptor packageView, @NotNull String name, boolean failOnMissing) {
Name propertyName = Name.identifier(name);
MemberScope memberScope = packageView.getMemberScope();
Collection<PropertyDescriptor> properties = memberScope.getContributedVariables(propertyName, NoLookupLocation.FROM_TEST);
if (properties.isEmpty()) {
for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(memberScope)) {
if (descriptor instanceof ClassDescriptor) {
Collection<PropertyDescriptor> classProperties = ((ClassDescriptor) descriptor).getMemberScope(Collections.emptyList())
.getContributedVariables(propertyName, NoLookupLocation.FROM_TEST);
if (!classProperties.isEmpty()) {
properties = classProperties;
break;
}
}
}
}
if (failOnMissing) {
assert properties.size() == 1 : "Failed to find property " + propertyName + " in class " + packageView.getName();
}
else if (properties.size() != 1) {
return null;
}
return properties.iterator().next();
}
@NotNull
private static PropertyDescriptor getPropertyDescriptor(@NotNull ClassDescriptor classDescriptor, @NotNull String name) {
Name propertyName = Name.identifier(name);
MemberScope memberScope = classDescriptor.getMemberScope(Collections.emptyList());
Collection<PropertyDescriptor> properties = memberScope.getContributedVariables(propertyName, NoLookupLocation.FROM_TEST);
assert properties.size() == 1 : "Failed to find property " + propertyName + " in class " + classDescriptor.getName();
return properties.iterator().next();
}
@NotNull
protected static ClassDescriptor getClassDescriptor(@NotNull PackageFragmentDescriptor packageView, @NotNull String name) {
Name className = Name.identifier(name);
ClassifierDescriptor aClass = packageView.getMemberScope().getContributedClassifier(className, NoLookupLocation.FROM_TEST);
assertNotNull("Failed to find class: " + packageView.getName() + "." + className, aClass);
assert aClass instanceof ClassDescriptor : "Not a class: " + aClass;
return (ClassDescriptor) aClass;
}
@NotNull
private static ClassDescriptor getInnerClassDescriptor(@NotNull ClassDescriptor classDescriptor, @NotNull String name) {
Name propertyName = Name.identifier(name);
MemberScope memberScope = classDescriptor.getMemberScope(Collections.emptyList());
ClassifierDescriptor innerClass = memberScope.getContributedClassifier(propertyName, NoLookupLocation.FROM_TEST);
assert innerClass instanceof ClassDescriptor : "Failed to find inner class " +
propertyName +
" in class " +
classDescriptor.getName();
return (ClassDescriptor) innerClass;
}
@NotNull
private ClassDescriptor getLocalClassDescriptor(@NotNull String name) {
for (ClassDescriptor descriptor : context.getSliceContents(BindingContext.CLASS).values()) {
if (descriptor.getName().asString().equals(name)) {
return descriptor;
}
}
fail("Failed to find local class " + name);
return null;
}
@NotNull
private ClassDescriptor getLocalObjectDescriptor(@NotNull String name) {
ClassDescriptor localClassDescriptor = getLocalClassDescriptor(name);
if (isNonCompanionObject(localClassDescriptor)) {
return localClassDescriptor;
}
fail("Failed to find local object " + name);
return null;
}
@NotNull
private SimpleFunctionDescriptor getLocalFunDescriptor(@NotNull String name) {
for (SimpleFunctionDescriptor descriptor : context.getSliceContents(BindingContext.FUNCTION).values()) {
if (descriptor.getName().asString().equals(name)) {
return descriptor;
}
}
fail("Failed to find local fun " + name);
return null;
}
@NotNull
protected static VariableDescriptor getLocalVarDescriptor(@NotNull BindingContext context, @NotNull String name) {
for (VariableDescriptor descriptor : context.getSliceContents(BindingContext.VARIABLE).values()) {
if (descriptor.getName().asString().equals(name)) {
return descriptor;
}
}
fail("Failed to find local variable " + name);
return null;
}
@NotNull
private SimpleFunctionDescriptor getAnonymousFunDescriptor() {
for (SimpleFunctionDescriptor descriptor : context.getSliceContents(BindingContext.FUNCTION).values()) {
if (descriptor instanceof AnonymousFunctionDescriptor) {
return descriptor;
}
}
fail("Failed to find anonymous fun");
return null;
}
@NotNull
private static ValueParameterDescriptor getConstructorParameterDescriptor(
@NotNull ClassDescriptor classDescriptor,
@NotNull String name
) {
ConstructorDescriptor constructorDescriptor = getConstructorDescriptor(classDescriptor);
ValueParameterDescriptor parameter = findValueParameter(constructorDescriptor.getValueParameters(), name);
assertNotNull("Cannot find constructor parameter with name " + name, parameter);
return parameter;
}
@NotNull
private static ConstructorDescriptor getConstructorDescriptor(@NotNull ClassDescriptor classDescriptor) {
Collection<ClassConstructorDescriptor> constructors = classDescriptor.getConstructors();
assert constructors.size() == 1;
return constructors.iterator().next();
}
private static ValueParameterDescriptor findValueParameter(List<ValueParameterDescriptor> parameters, String name) {
for (ValueParameterDescriptor parameter : parameters) {
if (parameter.getName().asString().equals(name)) {
return parameter;
}
}
return null;
}
@NotNull
private static ValueParameterDescriptor getFunctionParameterDescriptor(
@NotNull FunctionDescriptor functionDescriptor,
@NotNull String name
) {
ValueParameterDescriptor parameter = findValueParameter(functionDescriptor.getValueParameters(), name);
assertNotNull("Cannot find function parameter with name " + name, parameter);
return parameter;
}
@NotNull
protected KtFile getFile(@NotNull String content) {
KtFile ktFile = KotlinTestUtils.createFile("dummy.kt", content, getProject());
AnalysisResult analysisResult = KotlinTestUtils.analyzeFile(ktFile, getEnvironment());
context = analysisResult.getBindingContext();
return ktFile;
}
@NotNull
protected PackageFragmentDescriptor getPackage(@NotNull KtFile ktFile) {
PackageFragmentDescriptor packageFragment = context.get(BindingContext.FILE_TO_PACKAGE_FRAGMENT, ktFile);
assertNotNull("Failed to find package: " + PACKAGE, packageFragment);
return packageFragment;
}
@NotNull
protected PackageFragmentDescriptor getPackage(@NotNull String content) {
return getPackage(getFile(content));
}
protected static String getContent(@NotNull String annotationText) throws IOException {
File file = new File(PATH);
return KotlinTestUtils.doLoadFile(file).replaceAll("ANNOTATION", annotationText);
}
private static String renderAnnotations(Annotations annotations, @Nullable AnnotationUseSiteTarget defaultTarget) {
return StringUtil.join(annotations.getAllAnnotations(), annotationWithTarget -> {
AnnotationUseSiteTarget targetToRender = annotationWithTarget.getTarget();
if (targetToRender == defaultTarget) {
targetToRender = null;
}
return WITH_ANNOTATION_ARGUMENT_TYPES.renderAnnotation(annotationWithTarget.getAnnotation(), targetToRender);
}, " ");
}
protected static void checkDescriptor(String expectedAnnotation, DeclarationDescriptor member) {
String actual = getAnnotations(member);
assertEquals("Failed to resolve annotation descriptor for " + member.toString(), expectedAnnotation, actual);
}
private static void checkDescriptorWithTarget(String expectedAnnotation, DeclarationDescriptor member, AnnotationUseSiteTarget target) {
String actual = renderAnnotations(member.getAnnotations(), target);
assertEquals("Failed to resolve annotation descriptor for " + member.toString(), expectedAnnotation, actual);
}
@NotNull
protected static String getAnnotations(DeclarationDescriptor member) {
return renderAnnotations(member.getAnnotations(), null);
}
@Override
public void tearDown() throws Exception {
context = null;
super.tearDown();
}
}
@@ -0,0 +1,35 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.annotation
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
abstract class AbstractAnnotationParameterTest : AbstractAnnotationDescriptorResolveTest() {
fun doTest(path: String) {
val fileText = FileUtil.loadFile(File(path), true)
val packageView = getPackage(fileText)
val classDescriptor = AbstractAnnotationDescriptorResolveTest.getClassDescriptor(packageView, "MyClass")
val expected = InTextDirectivesUtils.findListWithPrefixes(fileText, "// EXPECTED: ").joinToString(", ")
val actual = AbstractAnnotationDescriptorResolveTest.getAnnotations(classDescriptor)
KotlinTestUtils.assertEqualsToFile(File(path), fileText.replace(expected, actual))
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.calls
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.TestJdkKind
abstract class AbstractEnhancedSignaturesResolvedCallsTest : AbstractResolvedCallsTest() {
// requires full JDK with various Java API: java.util.Optional, java.util.Map
override fun createEnvironment(): KotlinCoreEnvironment = createEnvironmentWithJdk(ConfigurationKind.ALL, TestJdkKind.FULL_JDK)
override fun renderOutput(originalText: String, text: String, resolvedCallsAt: List<Pair<Int, ResolvedCall<*>?>>): String {
val lines = text.lines()
val lineOffsets = run {
var offset = 0
lines.map { offset.apply { offset += it.length + 1 /* new-line delimiter */ } }
}
fun lineIndexAt(caret: Int): Int =
lineOffsets.binarySearch(caret).let { result ->
if (result < 0) result.inv() - 1 else result }
val callsByLine = resolvedCallsAt.groupBy ({ (caret) -> lineIndexAt(caret) }, { (_, resolvedCall) -> resolvedCall })
return buildString {
lines.forEachIndexed { lineIndex, line ->
appendln(line)
callsByLine[lineIndex]?.let { calls ->
val indent = line.takeWhile(Char::isWhitespace) + " "
calls.forEach { resolvedCall ->
appendln("$indent// ${resolvedCall?.status}")
appendln("$indent// ORIGINAL: ${resolvedCall?.run { resultingDescriptor!!.original.getText() }}")
appendln("$indent// SUBSTITUTED: ${resolvedCall?.run { resultingDescriptor!!.getText() }}")
}
}
}
}
}
}
@@ -0,0 +1,158 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.calls
import com.intellij.openapi.util.io.FileUtil
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.ReceiverParameterDescriptor
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.psi.ValueArgument
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMapping
import org.jetbrains.kotlin.resolve.calls.model.ArgumentMatch
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.VariableAsFunctionResolvedCall
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
import org.jetbrains.kotlin.resolve.scopes.receivers.ExpressionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ExtensionReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.ImplicitClassReceiver
import org.jetbrains.kotlin.resolve.scopes.receivers.Receiver
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import java.io.File
abstract class AbstractResolvedCallsTest : KotlinTestWithEnvironment() {
override fun createEnvironment(): KotlinCoreEnvironment = createEnvironmentWithMockJdk(ConfigurationKind.ALL)
fun doTest(filePath: String) {
val originalText = KotlinTestUtils.doLoadFile(File(filePath))!!
val (text, carets) = extractCarets(originalText)
val ktFile = KtPsiFactory(project).createFile(text)
val bindingContext = JvmResolveUtil.analyze(ktFile, environment).bindingContext
val resolvedCallsAt = carets.map { caret -> caret to run {
val (element, cachedCall) = buildCachedCallAtIndex(bindingContext, ktFile, caret)
val resolvedCall = when {
cachedCall !is VariableAsFunctionResolvedCall -> cachedCall
"(" == element?.text -> cachedCall.functionCall
else -> cachedCall.variableCall
}
resolvedCall
}}
val output = renderOutput(originalText, text, resolvedCallsAt)
val resolvedCallInfoFileName = FileUtil.getNameWithoutExtension(filePath) + ".txt"
KotlinTestUtils.assertEqualsToFile(File(resolvedCallInfoFileName), output)
}
protected open fun renderOutput(originalText: String, text: String, resolvedCallsAt: List<Pair<Int, ResolvedCall<*>?>>): String =
resolvedCallsAt.joinToString("\n\n", prefix = "$originalText\n\n\n") { (_, resolvedCall) ->
resolvedCall?.renderToText().toString()
}
protected fun extractCarets(text: String): Pair<String, List<Int>> {
val parts = text.split("<caret>")
if (parts.size < 2) return text to emptyList()
// possible to rewrite using 'scan' function to get partial sums of parts lengths
val indices = mutableListOf<Int>()
val resultText = buildString {
parts.dropLast(1).forEach { part ->
append(part)
indices.add(this.length)
}
append(parts.last())
}
return resultText to indices
}
protected open fun buildCachedCallAtIndex(
bindingContext: BindingContext, jetFile: KtFile, index: Int
): Pair<PsiElement?, ResolvedCall<out CallableDescriptor>?> {
val element = jetFile.findElementAt(index)!!
val expression = element.getStrictParentOfType<KtExpression>()
val cachedCall = expression?.getParentResolvedCall(bindingContext, strict = false)
return Pair(element, cachedCall)
}
}
internal fun Receiver?.getText() = when (this) {
is ExpressionReceiver -> "${expression.text} {${type}}"
is ImplicitClassReceiver -> "Class{${type}}"
is ExtensionReceiver -> "${type}Ext{${declarationDescriptor.getText()}}"
null -> "NO_RECEIVER"
else -> toString()
}
internal fun ValueArgument.getText() = this.getArgumentExpression()?.text?.replace("\n", " ") ?: ""
internal fun ArgumentMapping.getText() = when (this) {
is ArgumentMatch -> {
val parameterType = DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(valueParameter.type)
"${status.name} ${valueParameter.name} : ${parameterType} ="
}
else -> "ARGUMENT UNMAPPED: "
}
internal fun DeclarationDescriptor.getText(): String = when (this) {
is ReceiverParameterDescriptor -> "${value.getText()}::this"
else -> DescriptorRenderer.COMPACT_WITH_SHORT_TYPES.render(this)
}
internal fun ResolvedCall<*>.renderToText(): String {
return buildString {
appendln("Resolved call:")
appendln()
if (candidateDescriptor != resultingDescriptor) {
appendln("Candidate descriptor: ${candidateDescriptor!!.getText()}")
}
appendln("Resulting descriptor: ${resultingDescriptor!!.getText()}")
appendln()
appendln("Explicit receiver kind = ${explicitReceiverKind}")
appendln("Dispatch receiver = ${dispatchReceiver.getText()}")
appendln("Extension receiver = ${extensionReceiver.getText()}")
val valueArguments = call.valueArguments
if (!valueArguments.isEmpty()) {
appendln()
appendln("Value arguments mapping:")
appendln()
for (valueArgument in valueArguments) {
val argumentText = valueArgument!!.getText()
val argumentMappingText = getArgumentMapping(valueArgument).getText()
appendln("$argumentMappingText $argumentText")
}
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.calls
import com.intellij.psi.PsiElement
import org.jetbrains.kotlin.descriptors.CallableDescriptor
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtSecondaryConstructor
import org.jetbrains.kotlin.psi.psiUtil.getNonStrictParentOfType
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.calls.callUtil.getParentResolvedCall
import org.jetbrains.kotlin.resolve.calls.model.ResolvedCall
abstract class AbstractResolvedConstructorDelegationCallsTests : AbstractResolvedCallsTest() {
override fun buildCachedCallAtIndex(
bindingContext: BindingContext, jetFile: KtFile, index: Int
): Pair<PsiElement?, ResolvedCall<out CallableDescriptor>?> {
val element = jetFile.findElementAt(index)
val constructor = element?.getNonStrictParentOfType<KtSecondaryConstructor>()!!
val delegationCall = constructor.getDelegationCall()
val cachedCall = delegationCall.getParentResolvedCall(bindingContext, strict = false)
return Pair(delegationCall, cachedCall)
}
}
@@ -0,0 +1,118 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.constants.evaluate
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.descriptors.VariableDescriptor
import org.jetbrains.kotlin.psi.KtProperty
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DelegatingBindingTrace
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.annotation.AbstractAnnotationDescriptorResolveTest
import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant
import org.jetbrains.kotlin.resolve.constants.StringValue
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.test.InTextDirectivesUtils
import org.jetbrains.kotlin.test.KotlinTestUtils
import java.io.File
import java.util.regex.Pattern
abstract class AbstractCompileTimeConstantEvaluatorTest : AbstractAnnotationDescriptorResolveTest() {
// Test directives should look like [// val testedPropertyName: expectedValue]
fun doConstantTest(path: String) {
doTest(path) {
property, _ ->
val compileTimeConstant = property.compileTimeInitializer
if (compileTimeConstant is StringValue) {
"\\\"${compileTimeConstant.value}\\\""
} else {
"$compileTimeConstant"
}
}
}
// Test directives should look like [// val testedPropertyName: expectedValue]
fun doIsPureTest(path: String) {
doTest(path) {
property, context ->
evaluateInitializer(context, property)?.isPure.toString()
}
}
// Test directives should look like [// val testedPropertyName: expectedValue]
fun doUsesVariableAsConstantTest(path: String) {
doTest(path) {
property, context ->
evaluateInitializer(context, property)?.usesVariableAsConstant.toString()
}
}
private fun evaluateInitializer(context: BindingContext, property: VariableDescriptor): CompileTimeConstant<*>? {
val propertyDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(property) as KtProperty
val compileTimeConstant = ConstantExpressionEvaluator(property.builtIns, LanguageVersionSettingsImpl.DEFAULT).evaluateExpression(
propertyDeclaration.initializer!!,
DelegatingBindingTrace(context, "trace for evaluating compile time constant"),
property.type
)
return compileTimeConstant
}
private fun doTest(path: String, getValueToTest: (VariableDescriptor, BindingContext) -> String) {
val myFile = File(path)
val fileText = FileUtil.loadFile(myFile, true)
val packageView = getPackage(fileText)
val propertiesForTest = getObjectsToTest(fileText)
val expectedActual = arrayListOf<Pair<String, String>>()
for (propertyName in propertiesForTest) {
val expectedPropertyPrefix = "// val ${propertyName}: "
val expected = InTextDirectivesUtils.findStringWithPrefixes(fileText, expectedPropertyPrefix)
assertNotNull(expected, "Failed to find expected directive: $expectedPropertyPrefix")
val property = AbstractAnnotationDescriptorResolveTest.getPropertyDescriptor(packageView, propertyName, false)
?: AbstractAnnotationDescriptorResolveTest.getLocalVarDescriptor(context!!, propertyName)
val testedObject = getValueToTest(property, context!!)
expectedActual.add(expectedPropertyPrefix + expected!! to expectedPropertyPrefix + testedObject)
}
var actualFileText = fileText
for ((expected, actual) in expectedActual) {
assert(actualFileText.contains(expected)) { "File text should contain $expected" }
actualFileText = actualFileText.replace(expected, actual)
}
KotlinTestUtils.assertEqualsToFile(myFile, actualFileText)
}
fun getObjectsToTest(fileText: String): List<String> {
return InTextDirectivesUtils.findListWithPrefixes(fileText, "// val").map {
val matcher = pattern.matcher(it)
if (matcher.find()) {
matcher.group(0) ?: "Couldn't match tested object $it"
} else "Couldn't match tested object $it"
}
}
companion object {
val pattern = Pattern.compile(".+(?=:)")
}
}
@@ -0,0 +1,149 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.constraintSystem
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.diagnostics.rendering.Renderers
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.TypeResolver
import org.jetbrains.kotlin.resolve.calls.inference.CallHandle
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintContext
import org.jetbrains.kotlin.resolve.calls.inference.ConstraintSystemBuilderImpl
import org.jetbrains.kotlin.resolve.calls.inference.constraintPosition.ConstraintPositionKind.SPECIAL
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
import org.jetbrains.kotlin.test.ConfigurationKind
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.test.KotlinTestWithEnvironment
import org.jetbrains.kotlin.tests.di.createContainerForTests
import org.jetbrains.kotlin.types.ErrorUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.Variance
import java.io.File
abstract class AbstractConstraintSystemTest : KotlinTestWithEnvironment() {
private var _typeResolver: TypeResolver? = null
private val typeResolver: TypeResolver
get() = _typeResolver!!
private var _testDeclarations: ConstraintSystemTestData? = null
private val testDeclarations: ConstraintSystemTestData
get() = _testDeclarations!!
override fun createEnvironment(): KotlinCoreEnvironment {
return createEnvironmentWithMockJdk(ConfigurationKind.ALL)
}
override fun setUp() {
super.setUp()
_typeResolver = createContainerForTests(project, KotlinTestUtils.createEmptyModule()).typeResolver
_testDeclarations = analyzeDeclarations()
}
override fun tearDown() {
_typeResolver = null
_testDeclarations = null
super.tearDown()
}
private val testDataPath: String
get() = KotlinTestUtils.getTestDataPathBase() + "/constraintSystem/"
private fun analyzeDeclarations(): ConstraintSystemTestData {
val fileName = "declarations.kt"
val psiFile = KotlinTestUtils.createFile(fileName, KotlinTestUtils.doLoadFile(testDataPath, fileName), project)
val bindingContext = JvmResolveUtil.analyzeAndCheckForErrors(psiFile, environment).bindingContext
return ConstraintSystemTestData(bindingContext, project, typeResolver)
}
fun doTest(filePath: String) {
val constraintsFile = File(filePath)
val constraintsFileText = constraintsFile.readLines()
val builder = ConstraintSystemBuilderImpl()
val variables = parseVariables(constraintsFileText)
val fixVariables = constraintsFileText.contains("FIX_VARIABLES")
val typeParameterDescriptors = variables.map { testDeclarations.getParameterDescriptor(it) }
val substitutor = builder.registerTypeVariables(CallHandle.NONE, typeParameterDescriptors)
val constraints = parseConstraints(constraintsFileText)
fun getType(typeString: String): KotlinType {
val type = testDeclarations.getType(typeString).apply {
assert(!ErrorUtils.containsErrorType(this)) { "Type $this is resolved to or contains error type" }
}
return substitutor.substitute(type, Variance.INVARIANT) ?: error("Failed to substitute $type")
}
for (constraint in constraints) {
val firstType = getType(constraint.firstType)
val secondType = getType(constraint.secondType)
val context = ConstraintContext(SPECIAL.position(), initial = true, initialReduction = true)
when (constraint.kind) {
MyConstraintKind.SUBTYPE -> builder.addSubtypeConstraint(firstType, secondType, context.position)
MyConstraintKind.SUPERTYPE -> builder.addSubtypeConstraint(secondType, firstType, context.position)
MyConstraintKind.EQUAL -> builder.addConstraint(
ConstraintSystemBuilderImpl.ConstraintKind.EQUAL, firstType, secondType, context
)
}
}
if (fixVariables) builder.fixVariables()
val system = builder.build()
val resultingStatus = Renderers.renderConstraintSystem(system, shortTypeBounds = true)
val resultingSubstitutor = system.resultingSubstitutor
val result = typeParameterDescriptors.map {
val parameterType = testDeclarations.getType(it.name.asString())
val resultType = resultingSubstitutor.substitute(parameterType, Variance.INVARIANT)
"${it.name}=${resultType?.let { DescriptorRenderer.SHORT_NAMES_IN_TYPES.renderType(it) }}"
}.joinToString("\n", prefix = "result:\n")
val boundsFile = File(filePath.replace("constraints", "bounds"))
KotlinTestUtils.assertEqualsToFile(boundsFile, "${constraintsFileText.joinToString("\n")}\n\n$resultingStatus\n\n$result")
}
class MyConstraint(val kind: MyConstraintKind, val firstType: String, val secondType: String)
enum class MyConstraintKind(val token: String) {
SUBTYPE("<:"), SUPERTYPE(">:"), EQUAL(":=")
}
private fun parseVariables(lines: List<String>): List<String> {
val first = lines.first()
val variablesString = "VARIABLES "
assert (first.startsWith(variablesString)) { "The first line should contain variables: $first"}
val variables = first.substringAfter(variablesString).split(' ')
return variables.toList()
}
private fun parseConstraints(lines: List<String>): List<MyConstraint> {
val kindsMap = MyConstraintKind.values().map { it.token to it }.toMap()
val kinds = kindsMap.keys
val linesWithConstraints = lines.filter { line -> kinds.any { kind -> line.contains(kind) } }
return linesWithConstraints.map {
line ->
val kind = kinds.first { line.contains(it) }
val firstType = line.substringBefore(kind).trim()
val secondType = line.substringAfter(kind).trim()
MyConstraint(kindsMap[kind]!!, firstType, secondType)
}
}
}
@@ -0,0 +1,77 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.constraintSystem
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.psi.KtFunction
import org.jetbrains.kotlin.psi.KtPsiFactory
import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.TypeResolver
import org.jetbrains.kotlin.resolve.constants.IntegerValueTypeConstructor
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.resolve.scopes.LexicalScope
import org.jetbrains.kotlin.resolve.scopes.MemberScope
import org.jetbrains.kotlin.test.KotlinTestUtils
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.KotlinTypeFactory
import java.util.regex.Pattern
class ConstraintSystemTestData(
context: BindingContext,
private val project: Project,
private val typeResolver: TypeResolver
) {
private val functionFoo: FunctionDescriptor
private val scopeToResolveTypeParameters: LexicalScope
init {
val functions = context.getSliceContents(BindingContext.FUNCTION)
functionFoo = findFunctionByName(functions.values, "foo")
val function = DescriptorToSourceUtils.descriptorToDeclaration(functionFoo) as KtFunction
val fooBody = function.bodyExpression
scopeToResolveTypeParameters = context.get(BindingContext.LEXICAL_SCOPE, fooBody)!!
}
private fun findFunctionByName(functions: Collection<FunctionDescriptor>, name: String): FunctionDescriptor {
return functions.firstOrNull { it.name.asString() == name } ?:
throw AssertionError("Function ${name} is not declared")
}
fun getParameterDescriptor(name: String): TypeParameterDescriptor {
return functionFoo.typeParameters.firstOrNull { it.name.asString() == name } ?:
throw AssertionError("Unsupported type parameter name: $name. You may add it to constraintSystem/declarations.kt")
}
fun getType(name: String): KotlinType {
val matcher = INTEGER_VALUE_TYPE_PATTERN.matcher(name)
if (matcher.find()) {
val number = matcher.group(1)!!
return KotlinTypeFactory.simpleTypeWithNonTrivialMemberScope(Annotations.EMPTY, IntegerValueTypeConstructor(number.toLong(), functionFoo.builtIns),
listOf(), false, MemberScope.Empty
)
}
return typeResolver.resolveType(
scopeToResolveTypeParameters, KtPsiFactory(project).createType(name),
KotlinTestUtils.DUMMY_TRACE, true)
}
}
private val INTEGER_VALUE_TYPE_PATTERN = Pattern.compile("""IntegerValueType\((\d*)\)""")
@@ -0,0 +1,91 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.lazy
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analyzer.AnalysisResult
import org.jetbrains.kotlin.cli.jvm.compiler.CliLightClassGenerationSupport
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM
import org.jetbrains.kotlin.config.CompilerConfiguration
import org.jetbrains.kotlin.container.ComponentProvider
import org.jetbrains.kotlin.descriptors.PackagePartProvider
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.AnalyzingUtils
import org.jetbrains.kotlin.resolve.lazy.declarations.FileBasedDeclarationProviderFactory
object JvmResolveUtil {
@JvmStatic
@JvmOverloads
fun createContainer(environment: KotlinCoreEnvironment, files: Collection<KtFile> = emptyList()): ComponentProvider =
TopDownAnalyzerFacadeForJVM.createContainer(
environment.project, files, CliLightClassGenerationSupport.NoScopeRecordCliBindingTrace(),
environment.configuration, { PackagePartProvider.Empty }, ::FileBasedDeclarationProviderFactory
)
@JvmStatic
fun analyzeAndCheckForErrors(file: KtFile, environment: KotlinCoreEnvironment): AnalysisResult =
analyzeAndCheckForErrors(setOf(file), environment)
@JvmStatic
fun analyzeAndCheckForErrors(files: Collection<KtFile>, environment: KotlinCoreEnvironment): AnalysisResult =
analyzeAndCheckForErrors(environment.project, files, environment.configuration, environment::createPackagePartProvider)
@JvmStatic
fun analyzeAndCheckForErrors(
project: Project,
files: Collection<KtFile>,
configuration: CompilerConfiguration,
packagePartProvider: (GlobalSearchScope) -> PackagePartProvider
): AnalysisResult {
for (file in files) {
AnalyzingUtils.checkForSyntacticErrors(file)
}
return analyze(project, files, configuration, packagePartProvider).apply {
AnalyzingUtils.throwExceptionOnErrors(bindingContext)
}
}
@JvmStatic
fun analyze(environment: KotlinCoreEnvironment): AnalysisResult =
analyze(emptySet(), environment)
@JvmStatic
fun analyze(file: KtFile, environment: KotlinCoreEnvironment): AnalysisResult =
analyze(setOf(file), environment)
@JvmStatic
fun analyze(files: Collection<KtFile>, environment: KotlinCoreEnvironment): AnalysisResult =
analyze(files, environment, environment.configuration)
@JvmStatic
fun analyze(files: Collection<KtFile>, environment: KotlinCoreEnvironment, configuration: CompilerConfiguration): AnalysisResult =
analyze(environment.project, files, configuration, environment::createPackagePartProvider)
private fun analyze(
project: Project,
files: Collection<KtFile>,
configuration: CompilerConfiguration,
packagePartProviderFactory: (GlobalSearchScope) -> PackagePartProvider
): AnalysisResult {
return TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(
project, files, CliLightClassGenerationSupport.CliBindingTrace(), configuration, packagePartProviderFactory
)
}
}
@@ -0,0 +1,57 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.resolve.lazy
import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.analyzer.ModuleContent
import org.jetbrains.kotlin.analyzer.ModuleInfo
import org.jetbrains.kotlin.analyzer.ResolverForProjectImpl
import org.jetbrains.kotlin.container.get
import org.jetbrains.kotlin.context.ProjectContext
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.resolve.MultiTargetPlatform
import org.jetbrains.kotlin.resolve.jvm.JvmAnalyzerFacade
import org.jetbrains.kotlin.resolve.jvm.JvmPlatformParameters
fun createResolveSessionForFiles(
project: Project,
syntheticFiles: Collection<KtFile>,
addBuiltIns: Boolean
): ResolveSession {
val projectContext = ProjectContext(project)
val testModule = TestModule(addBuiltIns)
val resolverForProject = ResolverForProjectImpl(
"test",
projectContext, listOf(testModule), { JvmAnalyzerFacade },
{ ModuleContent(syntheticFiles, GlobalSearchScope.allScope(project)) },
JvmPlatformParameters { testModule },
modulePlatforms = { MultiTargetPlatform.Specific("JVM") }
)
return resolverForProject.resolverForModule(testModule).componentProvider.get<ResolveSession>()
}
private class TestModule(val dependsOnBuiltIns: Boolean) : ModuleInfo {
override val name: Name = Name.special("<Test module for lazy resolve>")
override fun dependencies() = listOf(this)
override fun dependencyOnBuiltIns() =
if (dependsOnBuiltIns)
ModuleInfo.DependencyOnBuiltIns.LAST
else
ModuleInfo.DependencyOnBuiltIns.NONE
}
@@ -0,0 +1,32 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test
enum class ConfigurationKind(
val withRuntime: Boolean = false,
val withMockRuntime: Boolean = false,
val withReflection: Boolean = false
) {
/** JDK without any kotlin runtime */
JDK_NO_RUNTIME(),
/** JDK + light mock kotlin runtime */
JDK_ONLY(withMockRuntime = true),
/** JDK + kotlin runtime but without reflection */
NO_KOTLIN_REFLECT(withRuntime = true),
/** JDK + kotlin runtime + kotlin reflection */
ALL(withRuntime = true, withReflection = true),
}
@@ -0,0 +1,254 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ArrayUtil;
import kotlin.text.StringsKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.junit.Assert;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import java.util.*;
public final class InTextDirectivesUtils {
private static final String DIRECTIVES_FILE_NAME = "directives.txt";
private InTextDirectivesUtils() {
}
@Nullable
public static Integer getPrefixedInt(String fileText, String prefix) {
String[] strings = findArrayWithPrefixes(fileText, prefix);
if (strings.length > 0) {
assert strings.length == 1;
return Integer.parseInt(strings[0]);
}
return null;
}
@Nullable
public static Boolean getPrefixedBoolean(String fileText, String prefix) {
String[] strings = findArrayWithPrefixes(fileText, prefix);
if (strings.length > 0) {
assert strings.length == 1;
return Boolean.parseBoolean(strings[0]);
}
return null;
}
@NotNull
public static String[] findArrayWithPrefixes(@NotNull String fileText, @NotNull String... prefixes) {
return ArrayUtil.toStringArray(findListWithPrefixes(fileText, prefixes));
}
@NotNull
public static List<String> findListWithPrefixes(@NotNull String fileText, @NotNull String... prefixes) {
List<String> result = new ArrayList<>();
for (String line : findLinesWithPrefixesRemoved(fileText, prefixes)) {
String unquoted = StringUtil.unquoteString(line);
if (!unquoted.equals(line)) {
result.add(unquoted);
}
else{
String[] variants = line.split(",");
for (String variant : variants) {
result.add(variant.trim());
}
}
}
return result;
}
public static boolean isDirectiveDefined(String fileText, String directive) {
return !findListWithPrefixes(fileText, directive).isEmpty();
}
@Nullable
public static String findStringWithPrefixes(String fileText, String... prefixes) {
List<String> strings = findListWithPrefixes(fileText, prefixes);
if (strings.isEmpty()) {
return null;
}
if (strings.size() != 1) {
throw new IllegalStateException("There is more than one string with given prefixes " +
Arrays.toString(prefixes) + ":\n" +
StringUtil.join(strings, "\n") + "\n" +
"Use findListWithPrefixes() instead.");
}
return strings.get(0);
}
@NotNull
public static List<String> findLinesWithPrefixesRemoved(String fileText, String... prefixes) {
return findLinesWithPrefixesRemoved(fileText, true, prefixes);
}
@NotNull
public static List<String> findLinesWithPrefixesRemoved(String fileText, boolean trim, String... prefixes) {
List<String> result = new ArrayList<>();
List<String> cleanedPrefixes = cleanDirectivesFromComments(Arrays.asList(prefixes));
for (String line : fileNonEmptyCommentedLines(fileText)) {
for (String prefix : cleanedPrefixes) {
if (line.startsWith(prefix)) {
String noPrefixLine = line.substring(prefix.length());
if (noPrefixLine.isEmpty() ||
Character.isWhitespace(noPrefixLine.charAt(0)) ||
Character.isWhitespace(prefix.charAt(prefix.length() - 1))) {
result.add(trim ? noPrefixLine.trim() : StringUtil.trimTrailing(StringsKt.drop(noPrefixLine, 1)));
break;
} else {
throw new AssertionError(
"Line starts with prefix \"" + prefix + "\", but doesn't have space symbol after it: " + line);
}
}
}
}
return result;
}
public static void assertHasUnknownPrefixes(String fileText, Collection<String> knownPrefixes) {
Set<String> prefixes = Sets.newHashSet();
for (String line : fileNonEmptyCommentedLines(fileText)) {
String prefix = probableDirective(line);
if (prefix != null) {
prefixes.add(prefix);
}
}
prefixes.removeAll(cleanDirectivesFromComments(knownPrefixes));
Assert.assertTrue("File contains some unexpected directives" + prefixes, prefixes.isEmpty());
}
private static String probableDirective(String line) {
String[] arr = line.split(" ", 2);
String firstWord = arr[0];
if (firstWord.length() > 1 && StringUtil.toUpperCase(firstWord).equals(firstWord)) {
return firstWord;
}
return null;
}
private static List<String> cleanDirectivesFromComments(Collection<String> prefixes) {
List<String> resultPrefixes = Lists.newArrayList();
for (String prefix : prefixes) {
if (prefix.startsWith("//") || prefix.startsWith("##")) {
resultPrefixes.add(StringUtil.trimLeading(prefix.substring(2)));
}
else {
resultPrefixes.add(prefix);
}
}
return resultPrefixes;
}
@NotNull
private static List<String> fileNonEmptyCommentedLines(String fileText) {
List<String> result = new ArrayList<>();
try {
try (BufferedReader reader = new BufferedReader(new StringReader(fileText))) {
String line;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.startsWith("//") || line.startsWith("##")) {
String uncommentedLine = line.substring(2).trim();
if (!uncommentedLine.isEmpty()) {
result.add(uncommentedLine);
}
}
}
}
}
catch (IOException e) {
throw ExceptionUtilsKt.rethrow(e);
}
return result;
}
private static String textWithDirectives(File file) {
try {
String fileText;
if (file.isDirectory()) {
File directivesFile = new File(file, DIRECTIVES_FILE_NAME);
if (!directivesFile.exists()) return "";
fileText = FileUtil.loadFile(directivesFile);
}
else {
fileText = FileUtil.loadFile(file);
}
return fileText;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static boolean isCompatibleTarget(TargetBackend targetBackend, File file) {
if (targetBackend == TargetBackend.ANY) return true;
List<String> backends = findLinesWithPrefixesRemoved(textWithDirectives(file), "// TARGET_BACKEND: ");
return backends.isEmpty() || backends.contains(targetBackend.name());
}
private static boolean isIgnoredTargetByPrefix(TargetBackend targetBackend, File file, String prefix) {
if (targetBackend == TargetBackend.ANY) return false;
List<String> ignoredBackends = findListWithPrefixes(textWithDirectives(file), prefix);
return ignoredBackends.contains(targetBackend.name());
}
public static boolean isIgnoredTarget(TargetBackend targetBackend, File file) {
return isIgnoredTargetByPrefix(targetBackend, file, "// IGNORE_BACKEND: ");
}
public static boolean isIgnoredTargetWithoutCheck(TargetBackend targetBackend, File file) {
return isIgnoredTargetByPrefix(targetBackend, file, "// IGNORE_BACKEND_WITHOUT_CHECK: ");
}
// Whether the target test is supposed to pass successfully on targetBackend
public static boolean isPassingTarget(TargetBackend targetBackend, File file) {
return isCompatibleTarget(targetBackend, file) && !isIgnoredTarget(targetBackend, file) && !isIgnoredTargetWithoutCheck(targetBackend, file);
}
}
@@ -0,0 +1,236 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test;
import junit.framework.Test;
import junit.framework.TestResult;
import junit.framework.TestSuite;
import org.junit.internal.MethodSorter;
import org.junit.internal.runners.JUnit38ClassRunner;
import org.junit.runner.Description;
import org.junit.runner.Runner;
import org.junit.runner.manipulation.*;
import org.junit.runner.notification.RunNotifier;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.regex.Pattern;
/**
* This runner runs class with all inners test classes, but monitors situation when
* inner classes are already processed.
*/
public class JUnit3RunnerWithInners extends Runner implements Filterable, Sortable {
private static final Set<Class> processedClasses = new HashSet<>();
private JUnit38ClassRunner delegateRunner;
private final Class<?> klass;
private boolean isFakeTest = false;
private static class FakeEmptyClassTest implements Test, Filterable {
private final String klassName;
FakeEmptyClassTest(Class<?> klass) {
this.klassName = klass.getName();
}
@Override
public int countTestCases() {
return 0;
}
@Override
public void run(TestResult result) {
result.startTest(this);
result.endTest(this);
}
@Override
public String toString() {
return "Empty class with inners for " + klassName;
}
@Override
public void filter(Filter filter) throws NoTestsRemainException {
throw new NoTestsRemainException();
}
}
public JUnit3RunnerWithInners(Class<?> klass) {
this.klass = klass;
}
@Override
public void run(RunNotifier notifier) {
initialize();
delegateRunner.run(notifier);
}
@Override
public Description getDescription() {
initialize();
return isFakeTest ? Description.EMPTY : delegateRunner.getDescription();
}
@Override
public void filter(Filter filter) throws NoTestsRemainException {
initialize();
delegateRunner.filter(filter);
}
@Override
public void sort(Sorter sorter) {
initialize();
delegateRunner.sort(sorter);
}
private void initialize() {
if (delegateRunner != null) return;
Test collectedTests = getCollectedTests();
delegateRunner = new JUnit38ClassRunner(collectedTests) {
@Override public void filter(Filter filter) throws NoTestsRemainException {
String classDescription = collectedTests.toString();
String classPatternString = getGradleClassPattern(filter);
if (classPatternString != null) {
if (Pattern.compile(classPatternString + "\\$.*").matcher(classDescription).matches()) {
return;
}
}
super.filter(filter);
}
};
}
private Test getCollectedTests() {
if (processedClasses.contains(klass)) {
isFakeTest = true;
return new FakeEmptyClassTest(klass);
}
Set<Class> classes = collectDeclaredClasses(klass, true);
Set<Class> unprocessedClasses = unprocessedClasses(classes);
processedClasses.addAll(unprocessedClasses);
return createTreeTestSuite(klass, unprocessedClasses);
}
private static Test createTreeTestSuite(Class root, Set<Class> classes) {
Map<Class, TestSuite> classSuites = new HashMap<>();
for (Class aClass : classes) {
classSuites.put(aClass, hasTestMethods(aClass) ? new TestSuite(aClass) : new TestSuite(aClass.getCanonicalName()));
}
for (Class aClass : classes) {
if (aClass.getEnclosingClass() != null && classes.contains(aClass.getEnclosingClass())) {
classSuites.get(aClass.getEnclosingClass()).addTest(classSuites.get(aClass));
}
}
return classSuites.get(root);
}
private static Set<Class> unprocessedClasses(Collection<Class> classes) {
Set<Class> result = new LinkedHashSet<>();
for (Class aClass : classes) {
if (!processedClasses.contains(aClass)) {
result.add(aClass);
}
}
return result;
}
private static Set<Class> collectDeclaredClasses(Class klass, boolean withItself) {
Set<Class> result = new HashSet<>();
if (withItself) {
result.add(klass);
}
for (Class aClass : klass.getDeclaredClasses()) {
result.addAll(collectDeclaredClasses(aClass, true));
}
return result;
}
private static boolean hasTestMethods(Class klass) {
for (Class currentClass = klass; Test.class.isAssignableFrom(currentClass); currentClass = currentClass.getSuperclass()) {
for (Method each : MethodSorter.getDeclaredMethods(currentClass)) {
if (isTestMethod(each)) return true;
}
}
return false;
}
private static boolean isTestMethod(Method method) {
return method.getParameterTypes().length == 0 &&
method.getName().startsWith("test") &&
method.getReturnType().equals(Void.TYPE) &&
Modifier.isPublic(method.getModifiers());
}
private static String getGradleClassPattern(Filter filter) {
try {
Class<? extends Filter> filterClass = filter.getClass();
if (!"org.gradle.api.internal.tasks.testing.junit.JUnitTestClassExecuter$MethodNameFilter".equals(filterClass.getName())) {
return null;
}
Field matcherField = filterClass.getDeclaredField("matcher");
matcherField.setAccessible(true);
Object testSelectionMatcher = matcherField.get(filter);
Class<?> testSelectionMatcherClass = testSelectionMatcher.getClass();
if (!"org.gradle.api.internal.tasks.testing.filter.TestSelectionMatcher".equals(testSelectionMatcherClass.getName())) {
return null;
}
Field includePatternsField;
try {
includePatternsField = testSelectionMatcherClass.getDeclaredField("includePatterns");
}
catch (NoSuchFieldException exception) {
includePatternsField = testSelectionMatcherClass.getDeclaredField("buildScriptIncludePatterns");
}
includePatternsField.setAccessible(true);
@SuppressWarnings("unchecked") ArrayList<Pattern> includePatterns =
(ArrayList<Pattern>) includePatternsField.get(testSelectionMatcher);
if (includePatterns.size() != 1) {
return null;
}
Pattern pattern = includePatterns.get(0);
String patternStr = pattern.pattern();
if (patternStr.endsWith("*")) {
return null;
}
return patternStr;
} catch (ReflectiveOperationException e) {
throw new IllegalStateException(e);
}
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
public abstract class KotlinTestWithEnvironment extends KotlinTestWithEnvironmentManagement {
private KotlinCoreEnvironment environment;
@Override
protected void setUp() throws Exception {
super.setUp();
environment = createEnvironment();
}
@Override
protected void tearDown() throws Exception {
environment = null;
super.tearDown();
}
protected abstract KotlinCoreEnvironment createEnvironment() throws Exception;
@NotNull
public KotlinCoreEnvironment getEnvironment() {
return environment;
}
@NotNull
public Project getProject() {
return getEnvironment().getProject();
}
}
@@ -0,0 +1,37 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment;
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase;
public abstract class KotlinTestWithEnvironmentManagement extends KtUsefulTestCase {
static {
System.setProperty("java.awt.headless", "true");
}
@NotNull
protected KotlinCoreEnvironment createEnvironmentWithMockJdk(@NotNull ConfigurationKind configurationKind) {
return createEnvironmentWithJdk(configurationKind, TestJdkKind.MOCK_JDK);
}
@NotNull
protected KotlinCoreEnvironment createEnvironmentWithJdk(@NotNull ConfigurationKind configurationKind, @NotNull TestJdkKind jdkKind) {
return KotlinTestUtils.createEnvironmentWithJdkAndNullabilityAnnotationsFromIdea(getTestRootDisposable(), configurationKind, jdkKind);
}
}
@@ -0,0 +1,221 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test
import com.intellij.openapi.util.io.FileUtil
import com.intellij.util.io.ZipUtil
import org.jetbrains.kotlin.cli.common.CLICompiler
import org.jetbrains.kotlin.cli.common.ExitCode
import org.jetbrains.kotlin.cli.js.K2JSCompiler
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.preloading.ClassPreloadingUtils
import org.jetbrains.kotlin.preloading.Preloader
import org.jetbrains.kotlin.utils.PathUtil
import org.junit.Assert
import org.junit.Assert.assertEquals
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileOutputStream
import java.io.PrintStream
import java.lang.ref.SoftReference
import java.util.regex.Pattern
import java.util.zip.ZipOutputStream
import kotlin.reflect.KClass
object MockLibraryUtil {
private var compilerClassLoader = SoftReference<ClassLoader>(null)
@JvmStatic
@JvmOverloads
fun compileJvmLibraryToJar(
sourcesPath: String,
jarName: String,
addSources: Boolean = false,
allowKotlinSources: Boolean = true,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList(),
useJava9: Boolean = false
): File {
return compileLibraryToJar(
sourcesPath, KotlinTestUtils.tmpDir("testLibrary-" + jarName), jarName, addSources,allowKotlinSources, extraOptions, extraClasspath
, useJava9)}
@JvmStatic
@JvmOverloads
fun compileJavaFilesLibraryToJar(
sourcesPath: String,
jarName: String,
addSources: Boolean = false,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList()
): File {
return compileJvmLibraryToJar(
sourcesPath, jarName, addSources,
allowKotlinSources = false,
extraClasspath = extraClasspath, extraOptions = extraOptions
)
}
@JvmStatic
@JvmOverloads
fun compileLibraryToJar(
sourcesPath: String,
contentDir: File,
jarName: String,
addSources: Boolean = false,
allowKotlinSources: Boolean = true,
extraOptions: List<String> = emptyList(),
extraClasspath: List<String> = emptyList(),
useJava9: Boolean = false
): File {
val classesDir = File(contentDir, "classes")
val srcFile = File(sourcesPath)
val kotlinFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.kt"), srcFile)
if (srcFile.isFile || kotlinFiles.isNotEmpty()) {
Assert.assertTrue("Only java files are expected", allowKotlinSources)
compileKotlin(sourcesPath, classesDir, extraOptions, *extraClasspath.toTypedArray())
}
val javaFiles = FileUtil.findFilesByMask(Pattern.compile(".*\\.java"), srcFile)
if (javaFiles.isNotEmpty()) {
val classpath = mutableListOf<String>()
classpath += ForTestCompileRuntime.runtimeJarForTests().path
classpath += KotlinTestUtils.getAnnotationsJar().path
classpath += extraClasspath
// Probably no kotlin files were present, so dir might not have been created after kotlin compiler
if (classesDir.exists()) {
classpath += classesDir.path
}
else {
FileUtil.createDirectory(classesDir)
}
val options = listOf(
"-classpath", classpath.joinToString(File.pathSeparator),
"-d", classesDir.path
)
val compile =
if (useJava9) KotlinTestUtils::compileJavaFilesExternallyWithJava9
else KotlinTestUtils::compileJavaFiles
val success = compile(javaFiles, options)
if (!success) {
throw AssertionError("Java files are not compiled successfully")
}
}
return createJarFile(contentDir, classesDir, jarName, sourcesPath.takeIf { addSources })
}
@JvmStatic
fun compileJsLibraryToJar(sourcesPath: String, jarName: String, addSources: Boolean): File {
val contentDir = KotlinTestUtils.tmpDir("testLibrary-" + jarName)
val outDir = File(contentDir, "out")
val outputFile = File(outDir, jarName + ".js")
compileKotlin2JS(sourcesPath, outputFile)
return createJarFile(contentDir, outDir, jarName, sourcesPath.takeIf { addSources })
}
fun createJarFile(contentDir: File, dirToAdd: File, jarName: String, sourcesPath: String? = null): File {
val jarFile = File(contentDir, jarName + ".jar")
ZipOutputStream(FileOutputStream(jarFile)).use { zip ->
ZipUtil.addDirToZipRecursively(zip, jarFile, dirToAdd, "", null, null)
if (sourcesPath != null) {
ZipUtil.addDirToZipRecursively(zip, jarFile, File(sourcesPath), "src", null, null)
}
}
return jarFile
}
private fun runJvmCompiler(args: List<String>) {
runCompiler(compiler2JVMClass, args)
}
private fun runJsCompiler(args: List<String>) {
runCompiler(compiler2JSClass, args)
}
// Runs compiler in custom class loader to avoid effects caused by replacing Application with another one created in compiler.
private fun runCompiler(compilerClass: Class<*>, args: List<String>) {
val outStream = ByteArrayOutputStream()
val compiler = compilerClass.newInstance()
val execMethod = compilerClass.getMethod("exec", PrintStream::class.java, Array<String>::class.java)
val invocationResult = execMethod.invoke(compiler, PrintStream(outStream), args.toTypedArray()) as Enum<*>
assertEquals(String(outStream.toByteArray()), ExitCode.OK.name, invocationResult.name)
}
@JvmStatic
@JvmOverloads
fun compileKotlin(
sourcesPath: String,
outDir: File,
extraOptions: List<String> = emptyList(),
vararg extraClasspath: String
) {
val classpath = mutableListOf<String>()
if (File(sourcesPath).isDirectory) {
classpath += sourcesPath
}
classpath += extraClasspath
val args = mutableListOf(
sourcesPath,
"-d", outDir.absolutePath,
"-classpath", classpath.joinToString(File.pathSeparator)
) + extraOptions
runJvmCompiler(args)
}
private fun compileKotlin2JS(sourcesPath: String, outputFile: File) {
runJsCompiler(listOf("-meta-info", "-output", outputFile.absolutePath, sourcesPath))
}
fun compileKotlinModule(buildFilePath: String) {
runJvmCompiler(listOf("-no-stdlib", "-Xbuild-file", buildFilePath))
}
private val compiler2JVMClass: Class<*>
@Synchronized get() = loadCompilerClass(K2JVMCompiler::class)
private val compiler2JSClass: Class<*>
@Synchronized get() = loadCompilerClass(K2JSCompiler::class)
@Synchronized
private fun loadCompilerClass(compilerClass: KClass<out CLICompiler<*>>): Class<*> {
val classLoader = compilerClassLoader.get() ?: createCompilerClassLoader().also { classLoader ->
compilerClassLoader = SoftReference<ClassLoader>(classLoader)
}
return classLoader.loadClass(compilerClass.java.name)
}
@Synchronized
private fun createCompilerClassLoader(): ClassLoader {
return ClassPreloadingUtils.preloadClasses(
listOf(PathUtil.kotlinPathsForDistDirectory.compilerPath),
Preloader.DEFAULT_CLASS_NUMBER_ESTIMATE, null, null
)
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test;
public enum TargetBackend {
ANY,
JVM,
JS
}
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test;
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase;
import java.io.File;
public abstract class TestCaseWithTmpdir extends KtUsefulTestCase {
protected File tmpdir;
@Override
protected void setUp() throws Exception {
super.setUp();
tmpdir = KotlinTestUtils.tmpDirForTest(this);
}
}
@@ -0,0 +1,31 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test;
public enum TestJdkKind {
MOCK_JDK,
// Differs from common mock JDK only by one additional 'nonExistingMethod' in Collection and constructor from Double in Throwable
// It's needed to test the way we load additional built-ins members that neither in black nor white lists
MODIFIED_MOCK_JDK,
// JDK found at $JDK_16
FULL_JDK_6,
// JDK found at $JDK_19
FULL_JDK_9,
// JDK found at java.home
FULL_JDK,
ANDROID_API,
}
@@ -0,0 +1,28 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface TestMetadata {
String value();
}
@@ -0,0 +1,48 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework
import org.jetbrains.annotations.TestOnly
import java.lang.reflect.InvocationTargetException
import javax.swing.SwingUtilities
class EdtTestUtil {
companion object {
@TestOnly @JvmStatic fun runInEdtAndWait(runnable: Runnable) {
runInEdtAndWait { runnable.run() }
}
}
}
// Test only because in production you must use Application.invokeAndWait(Runnable, ModalityState).
// The problem is - Application logs errors, but not throws. But in tests must be thrown.
// In any case name "runInEdtAndWait" is better than "invokeAndWait".
@TestOnly
fun runInEdtAndWait(runnable: () -> Unit) {
if (SwingUtilities.isEventDispatchThread()) {
runnable()
}
else {
try {
SwingUtilities.invokeAndWait(runnable)
}
catch (e: InvocationTargetException) {
throw e.cause ?: e
}
}
}
@@ -0,0 +1,340 @@
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework;
import com.intellij.core.CoreASTFactory;
import com.intellij.lang.*;
import com.intellij.lang.impl.PsiBuilderFactoryImpl;
import com.intellij.mock.MockFileDocumentManagerImpl;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.PathManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileEditor.impl.LoadTextUtil;
import com.intellij.openapi.fileTypes.FileTypeFactory;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.progress.EmptyProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.impl.CoreProgressManager;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.pom.PomModel;
import com.intellij.pom.core.impl.PomModelImpl;
import com.intellij.pom.tree.TreeAspect;
import com.intellij.psi.*;
import com.intellij.psi.impl.DebugUtil;
import com.intellij.psi.impl.PsiCachedValuesFactory;
import com.intellij.psi.impl.PsiFileFactoryImpl;
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl;
import com.intellij.psi.impl.source.text.BlockSupportImpl;
import com.intellij.psi.impl.source.text.DiffLog;
import com.intellij.psi.util.CachedValuesManager;
import com.intellij.testFramework.LightVirtualFile;
import com.intellij.testFramework.TestDataFile;
import com.intellij.util.CachedValuesManagerImpl;
import com.intellij.util.Function;
import com.intellij.util.messages.MessageBus;
import com.intellij.util.messages.MessageBusFactory;
import junit.framework.TestCase;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.test.testFramework.mock.*;
import org.picocontainer.ComponentAdapter;
import org.picocontainer.MutablePicoContainer;
import java.io.File;
import java.io.IOException;
import java.util.Set;
@SuppressWarnings("ALL")
public abstract class KtParsingTestCase extends KtPlatformLiteFixture {
public static final Key<Document> HARD_REF_TO_DOCUMENT_KEY = Key.create("HARD_REF_TO_DOCUMENT_KEY");
protected String myFilePrefix = "";
protected String myFileExt;
protected final String myFullDataPath;
protected PsiFile myFile;
private MockPsiManager myPsiManager;
private PsiFileFactoryImpl myFileFactory;
protected Language myLanguage;
private final ParserDefinition[] myDefinitions;
private final boolean myLowercaseFirstLetter;
protected KtParsingTestCase(@NonNls @NotNull String dataPath, @NotNull String fileExt, @NotNull ParserDefinition... definitions) {
this(dataPath, fileExt, false, definitions);
}
protected KtParsingTestCase(@NonNls @NotNull String dataPath, @NotNull String fileExt, boolean lowercaseFirstLetter, @NotNull ParserDefinition... definitions) {
myDefinitions = definitions;
myFullDataPath = getTestDataPath() + "/" + dataPath;
myFileExt = fileExt;
myLowercaseFirstLetter = lowercaseFirstLetter;
}
@Override
protected void setUp() throws Exception {
super.setUp();
initApplication();
ComponentAdapter component = getApplication().getPicoContainer().getComponentAdapter(ProgressManager.class.getName());
Extensions.registerAreaClass("IDEA_PROJECT", null);
myProject = new MockProjectEx(getTestRootDisposable());
myPsiManager = new MockPsiManager(myProject);
myFileFactory = new PsiFileFactoryImpl(myPsiManager);
MutablePicoContainer appContainer = getApplication().getPicoContainer();
registerComponentInstance(appContainer, MessageBus.class, MessageBusFactory.newMessageBus(getApplication()));
final MockEditorFactory editorFactory = new MockEditorFactory();
registerComponentInstance(appContainer, EditorFactory.class, editorFactory);
registerComponentInstance(appContainer, FileDocumentManager.class, new MockFileDocumentManagerImpl(new Function<CharSequence, Document>() {
@Override
public Document fun(CharSequence charSequence) {
return editorFactory.createDocument(charSequence);
}
}, HARD_REF_TO_DOCUMENT_KEY));
registerComponentInstance(appContainer, PsiDocumentManager.class, new MockPsiDocumentManager());
registerApplicationService(PsiBuilderFactory.class, new PsiBuilderFactoryImpl());
registerApplicationService(DefaultASTFactory.class, new CoreASTFactory());
registerApplicationService(ReferenceProvidersRegistry.class, new ReferenceProvidersRegistryImpl());
registerApplicationService(ProgressManager.class, new CoreProgressManager());
myProject.registerService(CachedValuesManager.class, new CachedValuesManagerImpl(myProject, new PsiCachedValuesFactory(myPsiManager)));
myProject.registerService(PsiManager.class, myPsiManager);
this.registerExtensionPoint(FileTypeFactory.FILE_TYPE_FACTORY_EP, FileTypeFactory.class);
registerExtensionPoint(MetaLanguage.EP_NAME, MetaLanguage.class);
for (ParserDefinition definition : myDefinitions) {
addExplicitExtension(LanguageParserDefinitions.INSTANCE, definition.getFileNodeType().getLanguage(), definition);
}
if (myDefinitions.length > 0) {
configureFromParserDefinition(myDefinitions[0], myFileExt);
}
// That's for reparse routines
final PomModelImpl pomModel = new PomModelImpl(myProject);
myProject.registerService(PomModel.class, pomModel);
new TreeAspect(pomModel);
}
public void configureFromParserDefinition(ParserDefinition definition, String extension) {
myLanguage = definition.getFileNodeType().getLanguage();
myFileExt = extension;
addExplicitExtension(LanguageParserDefinitions.INSTANCE, this.myLanguage, definition);
registerComponentInstance(
getApplication().getPicoContainer(), FileTypeManager.class,
new KtMockFileTypeManager(new KtMockLanguageFileType(myLanguage, myFileExt)));
}
protected <T> void addExplicitExtension(final LanguageExtension<T> instance, final Language language, final T object) {
instance.addExplicitExtension(language, object);
Disposer.register(myProject, new Disposable() {
@Override
public void dispose() {
instance.removeExplicitExtension(language, object);
}
});
}
@Override
protected <T> void registerExtensionPoint(final ExtensionPointName<T> extensionPointName, Class<T> aClass) {
super.registerExtensionPoint(extensionPointName, aClass);
Disposer.register(myProject, new Disposable() {
@Override
public void dispose() {
Extensions.getRootArea().unregisterExtensionPoint(extensionPointName.getName());
}
});
}
protected <T> void registerApplicationService(final Class<T> aClass, T object) {
getApplication().registerService(aClass, object);
Disposer.register(myProject, new Disposable() {
@Override
public void dispose() {
getApplication().getPicoContainer().unregisterComponent(aClass.getName());
}
});
}
public MockProjectEx getProject() {
return myProject;
}
public MockPsiManager getPsiManager() {
return myPsiManager;
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
myFile = null;
myProject = null;
myPsiManager = null;
}
protected String getTestDataPath() {
return PathManager.getHomePath();
}
@NotNull
public final String getTestName() {
return getTestName(myLowercaseFirstLetter);
}
protected boolean includeRanges() {
return false;
}
protected boolean skipSpaces() {
return false;
}
protected boolean checkAllPsiRoots() {
return true;
}
protected void doTest(boolean checkResult) {
String name = getTestName();
try {
String text = loadFile(name + "." + myFileExt);
myFile = createPsiFile(name, text);
ensureParsed(myFile);
assertEquals("light virtual file text mismatch", text, ((LightVirtualFile)myFile.getVirtualFile()).getContent().toString());
assertEquals("virtual file text mismatch", text, LoadTextUtil.loadText(myFile.getVirtualFile()));
assertEquals("doc text mismatch", text, myFile.getViewProvider().getDocument().getText());
assertEquals("psi text mismatch", text, myFile.getText());
ensureCorrectReparse(myFile);
if (checkResult){
checkResult(name, myFile);
}
else{
toParseTreeText(myFile, skipSpaces(), includeRanges());
}
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
protected void doTest(String suffix) throws IOException {
String name = getTestName();
String text = loadFile(name + "." + myFileExt);
myFile = createPsiFile(name, text);
ensureParsed(myFile);
assertEquals(text, myFile.getText());
checkResult(name + suffix, myFile);
}
protected void doCodeTest(String code) throws IOException {
String name = getTestName();
myFile = createPsiFile("a", code);
ensureParsed(myFile);
assertEquals(code, myFile.getText());
checkResult(myFilePrefix + name, myFile);
}
protected PsiFile createPsiFile(String name, String text) {
return createFile(name + "." + myFileExt, text);
}
protected PsiFile createFile(@NonNls String name, String text) {
LightVirtualFile virtualFile = new LightVirtualFile(name, myLanguage, text);
virtualFile.setCharset(CharsetToolkit.UTF8_CHARSET);
return createFile(virtualFile);
}
protected PsiFile createFile(LightVirtualFile virtualFile) {
return myFileFactory.trySetupPsiForFile(virtualFile, myLanguage, true, false);
}
protected void checkResult(@NonNls @TestDataFile String targetDataName, final PsiFile file) throws IOException {
doCheckResult(myFullDataPath, file, checkAllPsiRoots(), targetDataName, skipSpaces(), includeRanges());
}
public static void doCheckResult(String testDataDir,
PsiFile file,
boolean checkAllPsiRoots,
String targetDataName,
boolean skipSpaces,
boolean printRanges) throws IOException {
FileViewProvider provider = file.getViewProvider();
Set<Language> languages = provider.getLanguages();
if (!checkAllPsiRoots || languages.size() == 1) {
doCheckResult(testDataDir, targetDataName + ".txt", toParseTreeText(file, skipSpaces, printRanges).trim());
return;
}
for (Language language : languages) {
PsiFile root = provider.getPsi(language);
String expectedName = targetDataName + "." + language.getID() + ".txt";
doCheckResult(testDataDir, expectedName, toParseTreeText(root, skipSpaces, printRanges).trim());
}
}
protected void checkResult(String actual) throws IOException {
String name = getTestName();
doCheckResult(myFullDataPath, myFilePrefix + name + ".txt", actual);
}
protected void checkResult(@TestDataFile @NonNls String targetDataName, String actual) throws IOException {
doCheckResult(myFullDataPath, targetDataName, actual);
}
public static void doCheckResult(String fullPath, String targetDataName, String actual) throws IOException {
String expectedFileName = fullPath + File.separatorChar + targetDataName;
KtUsefulTestCase.assertSameLinesWithFile(expectedFileName, actual);
}
protected static String toParseTreeText(PsiElement file, boolean skipSpaces, boolean printRanges) {
return DebugUtil.psiToString(file, skipSpaces, printRanges);
}
protected String loadFile(@NonNls @TestDataFile String name) throws IOException {
return loadFileDefault(myFullDataPath, name);
}
public static String loadFileDefault(String dir, String name) throws IOException {
return FileUtil.loadFile(new File(dir, name), CharsetToolkit.UTF8, true).trim();
}
public static void ensureParsed(PsiFile file) {
file.accept(new PsiElementVisitor() {
@Override
public void visitElement(PsiElement element) {
element.acceptChildren(this);
}
});
}
public static void ensureCorrectReparse(@NotNull PsiFile file) {
String psiToStringDefault = DebugUtil.psiToString(file, false, false);
String fileText = file.getText();
DiffLog diffLog = (new BlockSupportImpl(file.getProject())).reparseRange(
file, file.getNode(), TextRange.allOf(fileText), fileText, new EmptyProgressIndicator(), fileText);
diffLog.performActualPsiChange(file);
TestCase.assertEquals(psiToStringDefault, DebugUtil.psiToString(file, false, false));
}
}
@@ -0,0 +1,80 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework;
import com.intellij.core.CoreEncodingProjectManager;
import com.intellij.mock.MockApplicationEx;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.ExtensionPoint;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.extensions.ExtensionsArea;
import com.intellij.openapi.fileTypes.FileTypeManager;
import com.intellij.openapi.vfs.encoding.EncodingManager;
import org.picocontainer.MutablePicoContainer;
import java.lang.reflect.Modifier;
public abstract class KtPlatformLiteFixture extends KtUsefulTestCase {
protected MockProjectEx myProject;
@Override
protected void setUp() throws Exception {
super.setUp();
Extensions.cleanRootArea(getTestRootDisposable());
}
public static MockApplicationEx getApplication() {
return (MockApplicationEx)ApplicationManager.getApplication();
}
public void initApplication() {
MockApplicationEx instance = new MockApplicationEx(getTestRootDisposable());
ApplicationManager.setApplication(instance, FileTypeManager::getInstance, getTestRootDisposable());
getApplication().registerService(EncodingManager.class, CoreEncodingProjectManager.class);
}
@Override
protected void tearDown() throws Exception {
super.tearDown();
clearFields(this);
myProject = null;
}
protected <T> void registerExtensionPoint(ExtensionPointName<T> extensionPointName, Class<T> aClass) {
registerExtensionPoint(Extensions.getRootArea(), extensionPointName, aClass);
}
private static <T> void registerExtensionPoint(
ExtensionsArea area, ExtensionPointName<T> extensionPointName,
Class<? extends T> aClass
) {
String name = extensionPointName.getName();
if (!area.hasExtensionPoint(name)) {
ExtensionPoint.Kind kind = aClass.isInterface() || (aClass.getModifiers() & Modifier.ABSTRACT) != 0 ? ExtensionPoint.Kind.INTERFACE : ExtensionPoint.Kind.BEAN_CLASS;
area.registerExtensionPoint(name, aClass.getName(), kind);
}
}
public static <T> T registerComponentInstance(MutablePicoContainer container, Class<T> key, T implementation) {
Object old = container.getComponentInstance(key);
container.unregisterComponent(key);
container.registerComponentInstance(key, implementation);
//noinspection unchecked
return (T)old;
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.ui.Queryable;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
// Based on com.intellij.testFramework.PlatformTestUtil
public class KtPlatformTestUtil {
@NotNull
public static String getTestName(@NotNull String name, boolean lowercaseFirstLetter) {
name = StringUtil.trimStart(name, "test");
return StringUtil.isEmpty(name) ? "" : lowercaseFirstLetter(name, lowercaseFirstLetter);
}
@NotNull
public static String lowercaseFirstLetter(@NotNull String name, boolean lowercaseFirstLetter) {
if (lowercaseFirstLetter && !isAllUppercaseName(name)) {
name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
}
return name;
}
public static boolean isAllUppercaseName(@NotNull String name) {
int uppercaseChars = 0;
for (int i = 0; i < name.length(); i++) {
if (Character.isLowerCase(name.charAt(i))) {
return false;
}
if (Character.isUpperCase(name.charAt(i))) {
uppercaseChars++;
}
}
return uppercaseChars >= 3;
}
@Nullable
protected static String toString(@Nullable Object node, @Nullable Queryable.PrintInfo printInfo) {
if (node instanceof AbstractTreeNode) {
if (printInfo != null) {
return ((AbstractTreeNode) node).toTestString(printInfo);
}
else {
@SuppressWarnings({"deprecation", "UnnecessaryLocalVariable"}) String presentation =
((AbstractTreeNode) node).getTestPresentation();
return presentation;
}
}
if (node == null) {
return "NULL";
}
return node.toString();
}
}
@@ -0,0 +1,496 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.io.FileSystemUtil;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.rt.execution.junit.FileComparisonFailure;
import com.intellij.testFramework.TestLoggerFactory;
import com.intellij.util.ReflectionUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.hash.HashMap;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashSet;
import junit.framework.AssertionFailedError;
import junit.framework.TestCase;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.types.FlexibleTypeImpl;
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
import org.junit.Assert;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.util.*;
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public abstract class KtUsefulTestCase extends TestCase {
private static final String TEMP_DIR_MARKER = "unitTest_";
private static final String ORIGINAL_TEMP_DIR = FileUtil.getTempDirectory();
private static final Map<String, Long> TOTAL_SETUP_COST_MILLIS = new HashMap<>();
private static final Map<String, Long> TOTAL_TEARDOWN_COST_MILLIS = new HashMap<>();
private Application application;
static {
Logger.setFactory(TestLoggerFactory.class);
}
@NotNull
protected final Disposable myTestRootDisposable = new TestDisposable();
private static final String ourPathToKeep = null;
private final List<String> myPathsToKeep = new ArrayList<>();
private String myTempDir;
static {
// Radar #5755208: Command line Java applications need a way to launch without a Dock icon.
System.setProperty("apple.awt.UIElement", "true");
FlexibleTypeImpl.RUN_SLOW_ASSERTIONS = true;
}
private boolean oldDisposerDebug;
@Override
protected void setUp() throws Exception {
application = ApplicationManager.getApplication();
super.setUp();
String testName = FileUtil.sanitizeFileName(getTestName(true));
if (StringUtil.isEmptyOrSpaces(testName)) testName = "";
testName = new File(testName).getName(); // in case the test name contains file separators
myTempDir = new File(ORIGINAL_TEMP_DIR, TEMP_DIR_MARKER + testName).getPath();
FileUtil.resetCanonicalTempPathCache(myTempDir);
boolean isStressTest = isStressTest();
ApplicationInfoImpl.setInStressTest(isStressTest);
// turn off Disposer debugging for performance tests
oldDisposerDebug = Disposer.setDebugMode(Disposer.isDebugMode() && !isStressTest);
}
@Override
protected void tearDown() throws Exception {
try {
Disposer.dispose(myTestRootDisposable);
cleanupSwingDataStructures();
cleanupDeleteOnExitHookList();
}
finally {
Disposer.setDebugMode(oldDisposerDebug);
FileUtil.resetCanonicalTempPathCache(ORIGINAL_TEMP_DIR);
if (hasTmpFilesToKeep()) {
File[] files = new File(myTempDir).listFiles();
if (files != null) {
for (File file : files) {
if (!shouldKeepTmpFile(file)) {
FileUtil.delete(file);
}
}
}
}
else {
FileUtil.delete(new File(myTempDir));
}
}
UIUtil.removeLeakingAppleListeners();
super.tearDown();
resetApplicationToNull(application);
application = null;
}
public static void resetApplicationToNull(Application old) {
if (old != null) return;
resetApplicationToNull();
}
public static void resetApplicationToNull() {
try {
Field ourApplicationField = ApplicationManager.class.getDeclaredField("ourApplication");
ourApplicationField.setAccessible(true);
ourApplicationField.set(null, null);
}
catch (Exception e) {
throw ExceptionUtilsKt.rethrow(e);
}
}
private boolean hasTmpFilesToKeep() {
return !myPathsToKeep.isEmpty();
}
private boolean shouldKeepTmpFile(File file) {
String path = file.getPath();
if (FileUtil.pathsEqual(path, ourPathToKeep)) return true;
for (String pathToKeep : myPathsToKeep) {
if (FileUtil.pathsEqual(path, pathToKeep)) return true;
}
return false;
}
private static final Set<String> DELETE_ON_EXIT_HOOK_DOT_FILES;
private static final Class DELETE_ON_EXIT_HOOK_CLASS;
static {
Class<?> aClass;
try {
aClass = Class.forName("java.io.DeleteOnExitHook");
}
catch (Exception e) {
throw new RuntimeException(e);
}
Set<String> files = ReflectionUtil.getStaticFieldValue(aClass, Set.class, "files");
DELETE_ON_EXIT_HOOK_CLASS = aClass;
DELETE_ON_EXIT_HOOK_DOT_FILES = files;
}
private static void cleanupDeleteOnExitHookList() throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
// try to reduce file set retained by java.io.DeleteOnExitHook
List<String> list;
synchronized (DELETE_ON_EXIT_HOOK_CLASS) {
if (DELETE_ON_EXIT_HOOK_DOT_FILES.isEmpty()) return;
list = new ArrayList<>(DELETE_ON_EXIT_HOOK_DOT_FILES);
}
for (int i = list.size() - 1; i >= 0; i--) {
String path = list.get(i);
if (FileSystemUtil.getAttributes(path) == null || new File(path).delete()) {
synchronized (DELETE_ON_EXIT_HOOK_CLASS) {
DELETE_ON_EXIT_HOOK_DOT_FILES.remove(path);
}
}
}
}
private static void cleanupSwingDataStructures() throws Exception {
Object manager = ReflectionUtil.getDeclaredMethod(Class.forName("javax.swing.KeyboardManager"), "getCurrentManager").invoke(null);
Map componentKeyStrokeMap = ReflectionUtil.getField(manager.getClass(), manager, Hashtable.class, "componentKeyStrokeMap");
componentKeyStrokeMap.clear();
Map containerMap = ReflectionUtil.getField(manager.getClass(), manager, Hashtable.class, "containerMap");
containerMap.clear();
}
@NotNull
public final Disposable getTestRootDisposable() {
return myTestRootDisposable;
}
@Override
protected void runTest() throws Throwable {
Throwable[] throwables = new Throwable[1];
Runnable runnable = () -> {
try {
super.runTest();
}
catch (InvocationTargetException e) {
e.fillInStackTrace();
throwables[0] = e.getTargetException();
}
catch (IllegalAccessException e) {
e.fillInStackTrace();
throwables[0] = e;
}
catch (Throwable e) {
throwables[0] = e;
}
};
invokeTestRunnable(runnable);
if (throwables[0] != null) {
throw throwables[0];
}
}
private static void invokeTestRunnable(@NotNull Runnable runnable) throws Exception {
EdtTestUtil.runInEdtAndWait(runnable);
}
private void defaultRunBare() throws Throwable {
Throwable exception = null;
try {
long setupStart = System.nanoTime();
setUp();
long setupCost = (System.nanoTime() - setupStart) / 1000000;
logPerClassCost(setupCost, TOTAL_SETUP_COST_MILLIS);
runTest();
TestLoggerFactory.onTestFinished(true);
}
catch (Throwable running) {
TestLoggerFactory.onTestFinished(false);
exception = running;
}
finally {
try {
long teardownStart = System.nanoTime();
tearDown();
long teardownCost = (System.nanoTime() - teardownStart) / 1000000;
logPerClassCost(teardownCost, TOTAL_TEARDOWN_COST_MILLIS);
}
catch (Throwable tearingDown) {
if (exception == null) exception = tearingDown;
}
}
if (exception != null) throw exception;
}
/**
* Logs the setup cost grouped by test fixture class (superclass of the current test class).
*
* @param cost setup cost in milliseconds
*/
private void logPerClassCost(long cost, Map<String, Long> costMap) {
Class<?> superclass = getClass().getSuperclass();
Long oldCost = costMap.get(superclass.getName());
long newCost = oldCost == null ? cost : oldCost + cost;
costMap.put(superclass.getName(), newCost);
}
@Override
public void runBare() throws Throwable {
this.defaultRunBare();
}
@NonNls
public static String toString(Iterable<?> collection) {
if (!collection.iterator().hasNext()) {
return "<empty>";
}
StringBuilder builder = new StringBuilder();
for (Object o : collection) {
if (o instanceof THashSet) {
builder.append(new TreeSet<Object>((THashSet)o));
}
else {
builder.append(o);
}
builder.append("\n");
}
return builder.toString();
}
private static <T> void assertOrderedEquals(String errorMsg, @NotNull Iterable<T> actual, @NotNull T... expected) {
Assert.assertNotNull(actual);
Assert.assertNotNull(expected);
assertOrderedEquals(errorMsg, actual, Arrays.asList(expected));
}
public static <T> void assertOrderedEquals(
String erroMsg,
Iterable<? extends T> actual,
Collection<? extends T> expected) {
ArrayList<T> list = new ArrayList<>();
for (T t : actual) {
list.add(t);
}
if (!list.equals(new ArrayList<T>(expected))) {
String expectedString = toString(expected);
String actualString = toString(actual);
Assert.assertEquals(erroMsg, expectedString, actualString);
Assert.fail("Warning! 'toString' does not reflect the difference.\nExpected: " + expectedString + "\nActual: " + actualString);
}
}
public static <T> void assertSameElements(T[] collection, T... expected) {
assertSameElements(Arrays.asList(collection), expected);
}
public static <T> void assertSameElements(Collection<? extends T> collection, T... expected) {
assertSameElements(collection, Arrays.asList(expected));
}
public static <T> void assertSameElements(Collection<? extends T> collection, Collection<T> expected) {
assertSameElements(null, collection, expected);
}
public static <T> void assertSameElements(String message, Collection<? extends T> collection, Collection<T> expected) {
assertNotNull(collection);
assertNotNull(expected);
if (collection.size() != expected.size() || !new HashSet<>(expected).equals(new HashSet<T>(collection))) {
Assert.assertEquals(message, toString(expected, "\n"), toString(collection, "\n"));
Assert.assertEquals(message, new HashSet<>(expected), new HashSet<T>(collection));
}
}
public static String toString(Object[] collection, String separator) {
return toString(Arrays.asList(collection), separator);
}
public static String toString(Collection<?> collection, String separator) {
List<String> list = ContainerUtil.map2List(collection, String::valueOf);
Collections.sort(list);
StringBuilder builder = new StringBuilder();
boolean flag = false;
for (String o : list) {
if (flag) {
builder.append(separator);
}
builder.append(o);
flag = true;
}
return builder.toString();
}
@Contract("null, _ -> fail")
public static <T> T assertInstanceOf(Object o, Class<T> aClass) {
Assert.assertNotNull("Expected instance of: " + aClass.getName() + " actual: " + null, o);
Assert.assertTrue("Expected instance of: " + aClass.getName() + " actual: " + o.getClass().getName(), aClass.isInstance(o));
@SuppressWarnings("unchecked") T t = (T)o;
return t;
}
protected static <T> void assertEmpty(String errorMsg, Collection<T> collection) {
//noinspection unchecked
assertOrderedEquals(errorMsg, collection);
}
protected static void assertSize(int expectedSize, Object[] array) {
assertEquals(toString(Arrays.asList(array)), expectedSize, array.length);
}
public static void assertSameLines(String expected, String actual) {
String expectedText = StringUtil.convertLineSeparators(expected.trim());
String actualText = StringUtil.convertLineSeparators(actual.trim());
Assert.assertEquals(expectedText, actualText);
}
protected String getTestName(boolean lowercaseFirstLetter) {
return getTestName(getName(), lowercaseFirstLetter);
}
public static String getTestName(String name, boolean lowercaseFirstLetter) {
return name == null ? "" : KtPlatformTestUtil.getTestName(name, lowercaseFirstLetter);
}
/** @deprecated use {@link KtPlatformTestUtil#lowercaseFirstLetter(String, boolean)} (to be removed in IDEA 17) */
@SuppressWarnings("unused")
public static String lowercaseFirstLetter(String name, boolean lowercaseFirstLetter) {
return KtPlatformTestUtil.lowercaseFirstLetter(name, lowercaseFirstLetter);
}
/** @deprecated use {@link KtPlatformTestUtil#isAllUppercaseName(String)} (to be removed in IDEA 17) */
@SuppressWarnings("unused")
public static boolean isAllUppercaseName(String name) {
return KtPlatformTestUtil.isAllUppercaseName(name);
}
public static void assertSameLinesWithFile(String filePath, String actualText) {
assertSameLinesWithFile(filePath, actualText, true);
}
public static void assertSameLinesWithFile(String filePath, String actualText, boolean trimBeforeComparing) {
String fileText;
try {
fileText = FileUtil.loadFile(new File(filePath), CharsetToolkit.UTF8_CHARSET);
}
catch (FileNotFoundException e) {
VfsTestUtil.overwriteTestData(filePath, actualText);
throw new AssertionFailedError("No output text found. File " + filePath + " created.");
}
catch (IOException e) {
throw new RuntimeException(e);
}
String expected = StringUtil.convertLineSeparators(trimBeforeComparing ? fileText.trim() : fileText);
String actual = StringUtil.convertLineSeparators(trimBeforeComparing ? actualText.trim() : actualText);
if (!Comparing.equal(expected, actual)) {
throw new FileComparisonFailure(null, expected, actual, filePath);
}
}
public static void clearFields(Object test) throws IllegalAccessException {
Class aClass = test.getClass();
while (aClass != null) {
clearDeclaredFields(test, aClass);
aClass = aClass.getSuperclass();
}
}
private static void clearDeclaredFields(Object test, Class aClass) throws IllegalAccessException {
if (aClass == null) return;
for (Field field : aClass.getDeclaredFields()) {
@NonNls String name = field.getDeclaringClass().getName();
if (!name.startsWith("junit.framework.") && !name.startsWith("com.intellij.testFramework.")) {
int modifiers = field.getModifiers();
if ((modifiers & Modifier.FINAL) == 0 && (modifiers & Modifier.STATIC) == 0 && !field.getType().isPrimitive()) {
field.setAccessible(true);
field.set(test, null);
}
}
}
}
private static boolean isPerformanceTest(@Nullable String testName, @Nullable String className) {
return testName != null && testName.contains("Performance") ||
className != null && className.contains("Performance");
}
/**
* @return true for a test which performs A LOT of computations.
* Such test should typically avoid performing expensive checks, e.g. data structure consistency complex validations.
* If you want your test to be treated as "Stress", please mention one of these words in its name: "Stress", "Slow".
* For example: {@code public void testStressPSIFromDifferentThreads()}
*/
private boolean isStressTest() {
return isStressTest(getName(), getClass().getName());
}
private static boolean isStressTest(String testName, String className) {
return isPerformanceTest(testName, className) ||
containsStressWords(testName) ||
containsStressWords(className);
}
private static boolean containsStressWords(@Nullable String name) {
return name != null && (name.contains("Stress") || name.contains("Slow"));
}
public class TestDisposable implements Disposable {
@Override
public void dispose() {
}
@Override
public String toString() {
String testName = getTestName(false);
return KtUsefulTestCase.this.getClass() + (StringUtil.isEmpty(testName) ? "" : ".test" + testName);
}
};
}
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework
import com.intellij.mock.MockProject
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.project.Project
interface ProjectEx : Project {
fun init()
fun setProjectName(name: String)
}
class MockProjectEx(parentDisposable: Disposable) : MockProject(if (ApplicationManager.getApplication() != null) ApplicationManager.getApplication().picoContainer else null, parentDisposable), ProjectEx {
override fun setProjectName(name: String) {
}
override fun init() {
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework;
import org.jetbrains.annotations.TestOnly;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
public class TestRunnerUtil {
private TestRunnerUtil() {
}
@TestOnly
public static boolean isJUnit4TestClass(Class aClass) {
int modifiers = aClass.getModifiers();
if ((modifiers & Modifier.ABSTRACT) != 0) return false;
if ((modifiers & Modifier.PUBLIC) == 0) return false;
if (aClass.getAnnotation(RunWith.class) != null) return true;
for (Method method : aClass.getMethods()) {
if (method.getAnnotation(Test.class) != null) return true;
}
return false;
}
}
@@ -0,0 +1,95 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.PathUtil;
import com.intellij.util.text.StringTokenizer;
import org.junit.Assert;
import java.io.File;
import java.io.IOException;
public class VfsTestUtil {
private VfsTestUtil() {
}
public static VirtualFile createFile(VirtualFile root, String relativePath) {
return createFile(root, relativePath, "");
}
public static VirtualFile createFile(VirtualFile root, String relativePath, String text) {
return createFileOrDir(root, relativePath, text, false);
}
private static VirtualFile createFileOrDir(
VirtualFile root,
String relativePath,
String text,
boolean dir) {
try {
AccessToken token = WriteAction.start();
try {
VirtualFile parent = root;
Assert.assertNotNull(parent);
StringTokenizer parents = new StringTokenizer(PathUtil.getParentPath(relativePath), "/");
while (parents.hasMoreTokens()) {
String name = parents.nextToken();
VirtualFile child = parent.findChild(name);
if (child == null || !child.isValid()) {
child = parent.createChildDirectory(VfsTestUtil.class, name);
}
parent = child;
}
VirtualFile file;
parent.getChildren();//need this to ensure that fileCreated event is fired
if (dir) {
file = parent.createChildDirectory(VfsTestUtil.class, PathUtil.getFileName(relativePath));
}
else {
file = parent.findFileByRelativePath(relativePath);
if (file == null) {
file = parent.createChildData(VfsTestUtil.class, PathUtil.getFileName(relativePath));
}
VfsUtil.saveText(file, text);
}
return file;
}
finally {
token.finish();
}
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings("UnusedDeclaration")
public static void overwriteTestData(String filePath, String actual) {
try {
FileUtil.writeToFile(new File(filePath), actual);
}
catch (IOException e) {
throw new AssertionError(e);
}
}
}
@@ -0,0 +1,167 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework.mock;
import com.intellij.openapi.fileTypes.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ArrayUtil;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
public class KtMockFileTypeManager extends FileTypeManager {
private final FileType fileType;
public KtMockFileTypeManager(FileType fileType) {
this.fileType = fileType;
}
@Override
@NotNull
public String getIgnoredFilesList() {
throw new IncorrectOperationException();
}
@Override
public void setIgnoredFilesList(@NotNull String list) {
}
@Override
public void registerFileType(@NotNull FileType type, @NotNull List<FileNameMatcher> defaultAssociations) {
}
@Override
@NotNull
public FileType getFileTypeByFileName(@NotNull String fileName) {
return fileType;
}
@Override
@NotNull
public FileType getFileTypeByFile(@NotNull VirtualFile file) {
return fileType;
}
@Override
@NotNull
public FileType getFileTypeByExtension(@NotNull String extension) {
return fileType;
}
@Override
@NotNull
public FileType[] getRegisteredFileTypes() {
return FileType.EMPTY_ARRAY;
}
@Override
public boolean isFileIgnored(@NotNull String name) {
return false;
}
@Override
public boolean isFileIgnored(@NotNull VirtualFile file) {
return false;
}
@Override
@NotNull
public String[] getAssociatedExtensions(@NotNull FileType type) {
return ArrayUtil.EMPTY_STRING_ARRAY;
}
@Override
public void addFileTypeListener(@NotNull FileTypeListener listener) {
}
@Override
public void removeFileTypeListener(@NotNull FileTypeListener listener) {
}
@Override
public FileType getKnownFileTypeOrAssociate(@NotNull VirtualFile file) {
return file.getFileType();
}
@Override
public FileType getKnownFileTypeOrAssociate(@NotNull VirtualFile file, @NotNull Project project) {
return getKnownFileTypeOrAssociate(file);
}
@Override
@NotNull
public List<FileNameMatcher> getAssociations(@NotNull FileType type) {
return Collections.emptyList();
}
@Override
public void associate(@NotNull FileType type, @NotNull FileNameMatcher matcher) {
}
@Override
public void removeAssociation(@NotNull FileType type, @NotNull FileNameMatcher matcher) {
}
@Override
@NotNull
public FileType getStdFileType(@NotNull @NonNls String fileTypeName) {
if ("ARCHIVE".equals(fileTypeName) || "CLASS".equals(fileTypeName)) return UnknownFileType.INSTANCE;
if ("PLAIN_TEXT".equals(fileTypeName)) return PlainTextFileType.INSTANCE;
if ("JAVA".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.JavaFileType", fileTypeName);
if ("XML".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.XmlFileType", fileTypeName);
if ("DTD".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.DTDFileType", fileTypeName);
if ("JSP".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.NewJspFileType", fileTypeName);
if ("JSPX".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.JspxFileType", fileTypeName);
if ("HTML".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.HtmlFileType", fileTypeName);
if ("XHTML".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.ide.highlighter.XHtmlFileType", fileTypeName);
if ("JavaScript".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.lang.javascript.JavaScriptFileType", fileTypeName);
if ("Properties".equals(fileTypeName)) return loadFileTypeSafe("com.intellij.lang.properties.PropertiesFileType", fileTypeName);
return new KtMockLanguageFileType(PlainTextLanguage.INSTANCE, fileTypeName.toLowerCase());
}
private static FileType loadFileTypeSafe(String className, String fileTypeName) {
try {
return (FileType)Class.forName(className).getField("INSTANCE").get(null);
}
catch (Exception ignored) {
return new KtMockLanguageFileType(PlainTextLanguage.INSTANCE, fileTypeName.toLowerCase(Locale.ENGLISH));
}
}
@Override
public boolean isFileOfType(@NotNull VirtualFile file, @NotNull FileType type) {
return false;
}
@NotNull
@Override
public FileType detectFileTypeFromContent(@NotNull VirtualFile file) {
return UnknownFileType.INSTANCE;
}
@Nullable
@Override
public FileType findFileTypeByName(@NotNull String fileTypeName) {
return null;
}
}
@@ -0,0 +1,64 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework.mock;
import com.intellij.lang.Language;
import com.intellij.openapi.fileTypes.LanguageFileType;
import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public class KtMockLanguageFileType extends LanguageFileType {
private final String myExtension;
public KtMockLanguageFileType(@NotNull Language language, String extension) {
super(language);
this.myExtension = extension;
}
@Override
@NotNull
public String getName() {
return this.getLanguage().getID();
}
@Override
@NotNull
public String getDescription() {
return "";
}
@Override
@NotNull
public String getDefaultExtension() {
String var10000 = this.myExtension;
if(this.myExtension == null) {
throw new IllegalStateException(String.format("@NotNull method %s.%s must not return null", new Object[]{"com/intellij/mock/KtMockLanguageFileType", "getDefaultExtension"}));
} else {
return var10000;
}
}
@Override
public Icon getIcon() {
return null;
}
public boolean equals(Object obj) {
return obj instanceof LanguageFileType && this.getLanguage().equals(((LanguageFileType) obj).getLanguage());
}
}
@@ -0,0 +1,94 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework.mock;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.editor.event.*;
import org.jetbrains.annotations.NotNull;
public class MockEditorEventMulticaster implements EditorEventMulticaster {
public MockEditorEventMulticaster() {
}
@Override
public void addDocumentListener(@NotNull DocumentListener listener) {
}
@Override
public void addDocumentListener(@NotNull DocumentListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeDocumentListener(@NotNull DocumentListener listener) {
}
@Override
public void addEditorMouseListener(@NotNull EditorMouseListener listener) {
}
@Override
public void addEditorMouseListener(@NotNull EditorMouseListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeEditorMouseListener(@NotNull EditorMouseListener listener) {
}
@Override
public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) {
}
@Override
public void addEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeEditorMouseMotionListener(@NotNull EditorMouseMotionListener listener) {
}
@Override
public void addCaretListener(@NotNull CaretListener listener) {
}
@Override
public void addCaretListener(@NotNull CaretListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeCaretListener(@NotNull CaretListener listener) {
}
@Override
public void addSelectionListener(@NotNull SelectionListener listener) {
}
@Override
public void addSelectionListener(@NotNull SelectionListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeSelectionListener(@NotNull SelectionListener listener) {
}
@Override
public void addVisibleAreaListener(@NotNull VisibleAreaListener listener) {
}
@Override
public void removeVisibleAreaListener(@NotNull VisibleAreaListener listener) {
}
}
@@ -0,0 +1,141 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework.mock;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.EditorKind;
import com.intellij.openapi.editor.event.EditorEventMulticaster;
import com.intellij.openapi.editor.event.EditorFactoryListener;
import com.intellij.openapi.editor.impl.DocumentImpl;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.text.CharArrayCharSequence;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class MockEditorFactory extends EditorFactory {
@Override
public Editor createEditor(@NotNull Document document) {
return null;
}
@Override
public Editor createViewer(@NotNull Document document) {
return null;
}
@Override
public Editor createEditor(@NotNull Document document, Project project) {
return null;
}
@Override
public Editor createEditor(@NotNull Document document, Project project, @NotNull VirtualFile file, boolean isViewer) {
return null;
}
@Override
public Editor createEditor(@NotNull Document document, Project project, @NotNull FileType fileType, boolean isViewer) {
return null;
}
@Override
public Editor createViewer(@NotNull Document document, Project project) {
return null;
}
@Override
public Editor createEditor(
@NotNull Document document, @Nullable Project project, @NotNull EditorKind kind
) {
return null;
}
@Override
public Editor createEditor(
@NotNull Document document, Project project, @NotNull VirtualFile file, boolean isViewer, @NotNull EditorKind kind
) {
return null;
}
@Override
public Editor createViewer(
@NotNull Document document, @Nullable Project project, @NotNull EditorKind kind
) {
return null;
}
@Override
public void releaseEditor(@NotNull Editor editor) {
}
@Override
@NotNull
public Editor[] getEditors(@NotNull Document document, Project project) {
return Editor.EMPTY_ARRAY;
}
@Override
@NotNull
public Editor[] getEditors(@NotNull Document document) {
return getEditors(document, null);
}
@Override
@NotNull
public Editor[] getAllEditors() {
return Editor.EMPTY_ARRAY;
}
@Override
public void addEditorFactoryListener(@NotNull EditorFactoryListener listener) {
}
@Override
public void addEditorFactoryListener(@NotNull EditorFactoryListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removeEditorFactoryListener(@NotNull EditorFactoryListener listener) {
}
@Override
@NotNull
public EditorEventMulticaster getEventMulticaster() {
return new MockEditorEventMulticaster();
}
@Override
@NotNull
public Document createDocument(@NotNull CharSequence text) {
return new DocumentImpl(text);
}
@Override
@NotNull
public Document createDocument(@NotNull char[] text) {
return createDocument(new CharArrayCharSequence(text));
}
@Override
public void refreshAllEditors() {
}
}
@@ -0,0 +1,115 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework.mock;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SingleRootFileViewProvider;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.file.impl.FileManager;
import com.intellij.util.containers.ConcurrentWeakFactoryMap;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.FactoryMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Map;
public class MockFileManager implements FileManager {
private final PsiManagerEx myManager;
// in mock tests it's LightVirtualFile, they're only alive when they're referenced,
// and there can not be several instances representing the same file
private final FactoryMap<VirtualFile, FileViewProvider> myViewProviders = new ConcurrentWeakFactoryMap<VirtualFile, FileViewProvider>() {
@Override
protected Map<VirtualFile, FileViewProvider> createMap() {
return ContainerUtil.createConcurrentWeakKeyWeakValueMap();
}
@Override
protected FileViewProvider create(VirtualFile key) {
return new SingleRootFileViewProvider(myManager, key);
}
};
@Override
@NotNull
public FileViewProvider createFileViewProvider(@NotNull VirtualFile file, boolean eventSystemEnabled) {
return new SingleRootFileViewProvider(myManager, file, eventSystemEnabled);
}
public MockFileManager(PsiManagerEx manager) {
myManager = manager;
}
@Override
public void dispose() {
throw new UnsupportedOperationException("Method dispose is not yet implemented in " + getClass().getName());
}
@Override
@Nullable
public PsiFile findFile(@NotNull VirtualFile vFile) {
return getCachedPsiFile(vFile);
}
@Override
@Nullable
public PsiDirectory findDirectory(@NotNull VirtualFile vFile) {
throw new UnsupportedOperationException("Method findDirectory is not yet implemented in " + getClass().getName());
}
@Override
public void reloadFromDisk(@NotNull PsiFile file) //Q: move to PsiFile(Impl)?
{
throw new UnsupportedOperationException("Method reloadFromDisk is not yet implemented in " + getClass().getName());
}
@Override
@Nullable
public PsiFile getCachedPsiFile(@NotNull VirtualFile vFile) {
FileViewProvider provider = findCachedViewProvider(vFile);
return provider.getPsi(provider.getBaseLanguage());
}
@Override
public void cleanupForNextTest() {
myViewProviders.clear();
}
@Override
public FileViewProvider findViewProvider(@NotNull VirtualFile file) {
throw new UnsupportedOperationException("Method findViewProvider is not yet implemented in " + getClass().getName());
}
@Override
public FileViewProvider findCachedViewProvider(@NotNull VirtualFile file) {
return myViewProviders.get(file);
}
@Override
public void setViewProvider(@NotNull VirtualFile virtualFile, FileViewProvider fileViewProvider) {
myViewProviders.put(virtualFile, fileViewProvider);
}
@Override
@NotNull
public List<PsiFile> getAllCachedFiles() {
throw new UnsupportedOperationException("Method getAllCachedFiles is not yet implemented in " + getClass().getName());
}
}
@@ -0,0 +1,157 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework.mock;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public class MockPsiDocumentManager extends PsiDocumentManager {
@Override
@Nullable
public PsiFile getPsiFile(@NotNull Document document) {
throw new UnsupportedOperationException("Method getPsiFile is not yet implemented in " + getClass().getName());
}
@Override
@Nullable
public PsiFile getCachedPsiFile(@NotNull Document document) {
throw new UnsupportedOperationException("Method getCachedPsiFile is not yet implemented in " + getClass().getName());
}
@Override
@Nullable
public Document getDocument(@NotNull PsiFile file) {
return null;
}
@Override
@Nullable
public Document getCachedDocument(@NotNull PsiFile file) {
VirtualFile vFile = file.getViewProvider().getVirtualFile();
return FileDocumentManager.getInstance().getCachedDocument(vFile);
}
@Override
public void commitAllDocuments() {
}
@Override
public void performForCommittedDocument(@NotNull Document document, @NotNull Runnable action) {
action.run();
}
@Override
public void commitDocument(@NotNull Document document) {
}
@NotNull
@Override
public CharSequence getLastCommittedText(@NotNull Document document) {
return document.getImmutableCharSequence();
}
@Override
public long getLastCommittedStamp(@NotNull Document document) {
return document.getModificationStamp();
}
@Nullable
@Override
public Document getLastCommittedDocument(@NotNull PsiFile file) {
return null;
}
@Override
@NotNull
public Document[] getUncommittedDocuments() {
throw new UnsupportedOperationException("Method getUncommittedDocuments is not yet implemented in " + getClass().getName());
}
@Override
public boolean isUncommited(@NotNull Document document) {
throw new UnsupportedOperationException("Method isUncommited is not yet implemented in " + getClass().getName());
}
@Override
public boolean isCommitted(@NotNull Document document) {
throw new UnsupportedOperationException();
}
@Override
public boolean hasUncommitedDocuments() {
throw new UnsupportedOperationException("Method hasUncommitedDocuments is not yet implemented in " + getClass().getName());
}
@Override
public void commitAndRunReadAction(@NotNull Runnable runnable) {
throw new UnsupportedOperationException("Method commitAndRunReadAction is not yet implemented in " + getClass().getName());
}
@Override
public <T> T commitAndRunReadAction(@NotNull Computable<T> computation) {
throw new UnsupportedOperationException("Method commitAndRunReadAction is not yet implemented in " + getClass().getName());
}
@Override
public void addListener(@NotNull Listener listener) {
throw new UnsupportedOperationException("Method addListener is not yet implemented in " + getClass().getName());
}
@Override
public void removeListener(@NotNull Listener listener) {
throw new UnsupportedOperationException("Method removeListener is not yet implemented in " + getClass().getName());
}
@Override
public boolean isDocumentBlockedByPsi(@NotNull Document doc) {
throw new UnsupportedOperationException("Method isDocumentBlockedByPsi is not yet implemented in " + getClass().getName());
}
@Override
public void doPostponedOperationsAndUnblockDocument(@NotNull Document doc) {
throw new UnsupportedOperationException(
"Method doPostponedOperationsAndUnblockDocument is not yet implemented in " + getClass().getName());
}
@Override
public boolean performWhenAllCommitted(@NotNull Runnable action) {
throw new UnsupportedOperationException();
}
@Override
public void performLaterWhenAllCommitted(@NotNull Runnable runnable) {
throw new UnsupportedOperationException();
}
@Override
public void reparseFiles(@NotNull Collection<VirtualFile> files, boolean includeOpenFiles) {
throw new UnsupportedOperationException();
}
@Override
public void performLaterWhenAllCommitted(@NotNull Runnable runnable, ModalityState modalityState) {
throw new UnsupportedOperationException();
}
}
@@ -0,0 +1,197 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework.mock;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiManagerEx;
import com.intellij.psi.impl.PsiModificationTrackerImpl;
import com.intellij.psi.impl.PsiTreeChangeEventImpl;
import com.intellij.psi.impl.file.impl.FileManager;
import com.intellij.psi.util.PsiModificationTracker;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Map;
public class MockPsiManager extends PsiManagerEx {
private final Project myProject;
private final Map<VirtualFile,PsiDirectory> myDirectories = new THashMap<>();
private MockFileManager myMockFileManager;
private PsiModificationTrackerImpl myPsiModificationTracker;
public MockPsiManager(@NotNull Project project) {
myProject = project;
}
@SuppressWarnings("unused")
public void addPsiDirectory(VirtualFile file, PsiDirectory psiDirectory) {
myDirectories.put(file, psiDirectory);
}
@Override
@NotNull
public Project getProject() {
return myProject;
}
@Override
public PsiFile findFile(@NotNull VirtualFile file) {
return null;
}
@Override
@Nullable
public
FileViewProvider findViewProvider(@NotNull VirtualFile file) {
return null;
}
@Override
public PsiDirectory findDirectory(@NotNull VirtualFile file) {
return myDirectories.get(file);
}
@Override
public boolean areElementsEquivalent(PsiElement element1, PsiElement element2) {
return Comparing.equal(element1, element2);
}
@Override
public void reloadFromDisk(@NotNull PsiFile file) {
}
@Override
public void addPsiTreeChangeListener(@NotNull PsiTreeChangeListener listener) {
}
@Override
public void addPsiTreeChangeListener(@NotNull PsiTreeChangeListener listener, @NotNull Disposable parentDisposable) {
}
@Override
public void removePsiTreeChangeListener(@NotNull PsiTreeChangeListener listener) {
}
@Override
@NotNull
public PsiModificationTracker getModificationTracker() {
if (myPsiModificationTracker == null) {
myPsiModificationTracker = new PsiModificationTrackerImpl(myProject);
}
return myPsiModificationTracker;
}
@Override
public void startBatchFilesProcessingMode() {
}
@Override
public void finishBatchFilesProcessingMode() {
}
@Override
public <T> T getUserData(@NotNull Key<T> key) {
return null;
}
@Override
public <T> void putUserData(@NotNull Key<T> key, T value) {
}
@Override
public boolean isDisposed() {
return false;
}
@Override
public void dropResolveCaches() {
getFileManager().cleanupForNextTest();
}
@Override
public boolean isInProject(@NotNull PsiElement element) {
return false;
}
@Override
public boolean isBatchFilesProcessingMode() {
return false;
}
@Override
public void setAssertOnFileLoadingFilter(@NotNull VirtualFileFilter filter, @NotNull Disposable parentDisposable) {}
@Override
public boolean isAssertOnFileLoading(@NotNull VirtualFile file) {
return false;
}
@Override
public void beforeChange(boolean isPhysical) {
throw new UnsupportedOperationException();
}
@Override
public void afterChange(boolean isPhysical) {
throw new UnsupportedOperationException();
}
@Override
public void registerRunnableToRunOnChange(@NotNull Runnable runnable) {
}
@Override
public void registerRunnableToRunOnAnyChange(@NotNull Runnable runnable) {
}
@Override
public void registerRunnableToRunAfterAnyChange(@NotNull Runnable runnable) {
throw new UnsupportedOperationException("Method registerRunnableToRunAfterAnyChange is not yet implemented in " + getClass().getName());
}
@Override
@NotNull
public FileManager getFileManager() {
if (myMockFileManager == null) {
myMockFileManager = new MockFileManager(this);
}
return myMockFileManager;
}
@Override
public void beforeChildRemoval(@NotNull PsiTreeChangeEventImpl event) {
}
@Override
public void beforeChildReplacement(@NotNull PsiTreeChangeEventImpl event) {
}
@Override
public void beforeChildAddition(@NotNull PsiTreeChangeEventImpl event) {
}
@Override
public void dropPsiCaches() {
}
}
@@ -0,0 +1,23 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.testFramework
import com.intellij.openapi.application.ApplicationManager
fun <T> runWriteAction(action: () -> T): T {
return ApplicationManager.getApplication().runWriteAction<T>(action)
}
@@ -0,0 +1,572 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.util;
import com.google.common.collect.Lists;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.incremental.components.NoLookupLocation;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.KotlinTypeKt;
import org.junit.Assert;
import java.io.PrintStream;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
public class DescriptorValidator {
public static void validate(@NotNull ValidationVisitor validationStrategy, DeclarationDescriptor descriptor) {
DiagnosticCollectorForTests collector = new DiagnosticCollectorForTests();
validate(validationStrategy, descriptor, collector);
collector.done();
}
public static void validate(
@NotNull ValidationVisitor validator,
@NotNull DeclarationDescriptor descriptor,
@NotNull DiagnosticCollector collector
) {
RecursiveDescriptorProcessor.process(descriptor, collector, validator);
}
private static void report(@NotNull DiagnosticCollector collector, @NotNull DeclarationDescriptor descriptor, @NotNull String message) {
collector.report(new ValidationDiagnostic(descriptor, message));
}
public interface DiagnosticCollector {
void report(@NotNull ValidationDiagnostic diagnostic);
}
public static class ValidationVisitor implements DeclarationDescriptorVisitor<Boolean, DiagnosticCollector> {
public static ValidationVisitor errorTypesForbidden() {
return new ValidationVisitor();
}
public static ValidationVisitor errorTypesAllowed() {
return new ValidationVisitor().allowErrorTypes();
}
private boolean allowErrorTypes = false;
private Predicate<DeclarationDescriptor> recursiveFilter = descriptor -> true;
protected ValidationVisitor() {
}
@NotNull
public ValidationVisitor withStepIntoFilter(@NotNull Predicate<DeclarationDescriptor> filter) {
this.recursiveFilter = filter;
return this;
}
@NotNull
public ValidationVisitor allowErrorTypes() {
this.allowErrorTypes = true;
return this;
}
protected void validateScope(DeclarationDescriptor scopeOwner, @NotNull MemberScope scope, @NotNull DiagnosticCollector collector) {
for (DeclarationDescriptor descriptor : DescriptorUtils.getAllDescriptors(scope)) {
if (recursiveFilter.test(descriptor)) {
descriptor.accept(new ScopeValidatorVisitor(collector), scope);
}
}
}
private void validateType(
@NotNull DeclarationDescriptor descriptor,
@Nullable KotlinType type,
@NotNull DiagnosticCollector collector
) {
if (type == null) {
report(collector, descriptor, "No type");
return;
}
if (!allowErrorTypes && KotlinTypeKt.isError(type)) {
report(collector, descriptor, "Error type: " + type);
return;
}
validateScope(descriptor, type.getMemberScope(), collector);
}
private void validateReturnType(CallableDescriptor descriptor, DiagnosticCollector collector) {
validateType(descriptor, descriptor.getReturnType(), collector);
}
private static void validateTypeParameters(DiagnosticCollector collector, List<TypeParameterDescriptor> parameters) {
for (int i = 0; i < parameters.size(); i++) {
TypeParameterDescriptor typeParameterDescriptor = parameters.get(i);
if (typeParameterDescriptor.getIndex() != i) {
report(collector, typeParameterDescriptor, "Incorrect index: " + typeParameterDescriptor.getIndex() + " but must be " + i);
}
}
}
private static void validateValueParameters(DiagnosticCollector collector, List<ValueParameterDescriptor> parameters) {
for (int i = 0; i < parameters.size(); i++) {
ValueParameterDescriptor valueParameterDescriptor = parameters.get(i);
if (valueParameterDescriptor.getIndex() != i) {
report(collector, valueParameterDescriptor, "Incorrect index: " + valueParameterDescriptor.getIndex() + " but must be " + i);
}
}
}
private void validateTypes(
DeclarationDescriptor descriptor,
DiagnosticCollector collector,
Collection<KotlinType> types
) {
for (KotlinType type : types) {
validateType(descriptor, type, collector);
}
}
private void validateCallable(CallableDescriptor descriptor, DiagnosticCollector collector) {
validateReturnType(descriptor, collector);
validateTypeParameters(collector, descriptor.getTypeParameters());
validateValueParameters(collector, descriptor.getValueParameters());
}
private static <T> void assertEquals(
DeclarationDescriptor descriptor,
DiagnosticCollector collector,
String name,
T expected,
T actual
) {
if (!expected.equals(actual)) {
report(collector, descriptor, "Wrong " + name + ": " + actual + " must be " + expected);
}
}
private static <T> void assertEqualTypes(
DeclarationDescriptor descriptor,
DiagnosticCollector collector,
String name,
KotlinType expected,
KotlinType actual
) {
if (KotlinTypeKt.isError(expected) && KotlinTypeKt.isError(actual)) {
assertEquals(descriptor, collector, name, expected.toString(), actual.toString());
}
else if (!expected.equals(actual)) {
report(collector, descriptor, "Wrong " + name + ": " + actual + " must be " + expected);
}
}
private static void validateAccessor(
PropertyDescriptor descriptor,
DiagnosticCollector collector,
PropertyAccessorDescriptor accessor,
String name
) {
// TODO: fix the discrepancies in descriptor construction and enable these checks
//assertEquals(accessor, collector, name + " visibility", descriptor.getVisibility(), accessor.getVisibility());
//assertEquals(accessor, collector, name + " modality", descriptor.getModality(), accessor.getModality());
assertEquals(accessor, collector, "corresponding property", descriptor, accessor.getCorrespondingProperty());
}
@Override
public Boolean visitPackageFragmentDescriptor(
PackageFragmentDescriptor descriptor, DiagnosticCollector collector
) {
validateScope(descriptor, descriptor.getMemberScope(), collector);
return true;
}
@Override
public Boolean visitPackageViewDescriptor(PackageViewDescriptor descriptor, DiagnosticCollector collector) {
if (!recursiveFilter.test(descriptor)) return false;
validateScope(descriptor, descriptor.getMemberScope(), collector);
return true;
}
@Override
public Boolean visitVariableDescriptor(
VariableDescriptor descriptor, DiagnosticCollector collector
) {
validateReturnType(descriptor, collector);
return true;
}
@Override
public Boolean visitFunctionDescriptor(
FunctionDescriptor descriptor, DiagnosticCollector collector
) {
validateCallable(descriptor, collector);
return true;
}
@Override
public Boolean visitTypeParameterDescriptor(
TypeParameterDescriptor descriptor, DiagnosticCollector collector
) {
validateTypes(descriptor, collector, descriptor.getUpperBounds());
validateType(descriptor, descriptor.getDefaultType(), collector);
return true;
}
@Override
public Boolean visitClassDescriptor(
ClassDescriptor descriptor, DiagnosticCollector collector
) {
validateTypeParameters(collector, descriptor.getDeclaredTypeParameters());
Collection<KotlinType> supertypes = descriptor.getTypeConstructor().getSupertypes();
if (supertypes.isEmpty() && descriptor.getKind() != ClassKind.INTERFACE
&& !KotlinBuiltIns.isSpecialClassWithNoSupertypes(descriptor)) {
report(collector, descriptor, "No supertypes for non-trait");
}
validateTypes(descriptor, collector, supertypes);
validateType(descriptor, descriptor.getDefaultType(), collector);
validateScope(descriptor, descriptor.getUnsubstitutedInnerClassesScope(), collector);
List<ConstructorDescriptor> primary = Lists.newArrayList();
for (ConstructorDescriptor constructorDescriptor : descriptor.getConstructors()) {
if (constructorDescriptor.isPrimary()) {
primary.add(constructorDescriptor);
}
}
if (primary.size() > 1) {
report(collector, descriptor, "Many primary constructors: " + primary);
}
ConstructorDescriptor primaryConstructor = descriptor.getUnsubstitutedPrimaryConstructor();
if (primaryConstructor != null) {
if (!descriptor.getConstructors().contains(primaryConstructor)) {
report(collector, primaryConstructor,
"Primary constructor not in getConstructors() result: " + descriptor.getConstructors());
}
}
ClassDescriptor companionObjectDescriptor = descriptor.getCompanionObjectDescriptor();
if (companionObjectDescriptor != null && !companionObjectDescriptor.isCompanionObject()) {
report(collector, companionObjectDescriptor, "Companion object should be marked as such");
}
return true;
}
@Override
public Boolean visitTypeAliasDescriptor(
TypeAliasDescriptor descriptor, DiagnosticCollector data
) {
// TODO typealias
return true;
}
@Override
public Boolean visitModuleDeclaration(
ModuleDescriptor descriptor, DiagnosticCollector collector
) {
return true;
}
@Override
public Boolean visitConstructorDescriptor(
ConstructorDescriptor constructorDescriptor, DiagnosticCollector collector
) {
visitFunctionDescriptor(constructorDescriptor, collector);
assertEqualTypes(constructorDescriptor, collector,
"return type",
constructorDescriptor.getContainingDeclaration().getDefaultType(),
constructorDescriptor.getReturnType());
return true;
}
@Override
public Boolean visitScriptDescriptor(
ScriptDescriptor scriptDescriptor, DiagnosticCollector collector
) {
return true;
}
@Override
public Boolean visitPropertyDescriptor(
PropertyDescriptor descriptor, DiagnosticCollector collector
) {
validateCallable(descriptor, collector);
PropertyGetterDescriptor getter = descriptor.getGetter();
if (getter != null) {
assertEqualTypes(getter, collector, "getter return type", descriptor.getType(), getter.getReturnType());
validateAccessor(descriptor, collector, getter, "getter");
}
PropertySetterDescriptor setter = descriptor.getSetter();
if (setter != null) {
assertEquals(setter, collector, "setter parameter count", 1, setter.getValueParameters().size());
assertEqualTypes(setter, collector, "setter parameter type", descriptor.getType(), setter.getValueParameters().get(0).getType());
assertEquals(setter, collector, "corresponding property", descriptor, setter.getCorrespondingProperty());
}
return true;
}
@Override
public Boolean visitValueParameterDescriptor(
ValueParameterDescriptor descriptor, DiagnosticCollector collector
) {
return visitVariableDescriptor(descriptor, collector);
}
@Override
public Boolean visitPropertyGetterDescriptor(
PropertyGetterDescriptor descriptor, DiagnosticCollector collector
) {
return visitFunctionDescriptor(descriptor, collector);
}
@Override
public Boolean visitPropertySetterDescriptor(
PropertySetterDescriptor descriptor, DiagnosticCollector collector
) {
return visitFunctionDescriptor(descriptor, collector);
}
@Override
public Boolean visitReceiverParameterDescriptor(
ReceiverParameterDescriptor descriptor, DiagnosticCollector collector
) {
validateType(descriptor, descriptor.getType(), collector);
return true;
}
}
private static class ScopeValidatorVisitor implements DeclarationDescriptorVisitor<Void, MemberScope> {
private final DiagnosticCollector collector;
public ScopeValidatorVisitor(DiagnosticCollector collector) {
this.collector = collector;
}
private void report(DeclarationDescriptor expected, String message) {
DescriptorValidator.report(collector, expected, message);
}
private void assertFound(
@NotNull MemberScope scope,
@NotNull DeclarationDescriptor expected,
@Nullable DeclarationDescriptor found,
boolean shouldBeSame
) {
if (found == null) {
report(expected, "Not found in " + scope);
}
if (shouldBeSame ? expected != found : !expected.equals(found)) {
report(expected, "Lookup error in " + scope + ": " + found);
}
}
private void assertFound(
@NotNull MemberScope scope,
@NotNull DeclarationDescriptor expected,
@NotNull Collection<? extends DeclarationDescriptor> found
) {
if (!found.contains(expected)) {
report(expected, "Not found in " + scope + ": " + found);
}
}
@Override
public Void visitPackageFragmentDescriptor(
PackageFragmentDescriptor descriptor, MemberScope scope
) {
return null;
}
@Override
public Void visitPackageViewDescriptor(
PackageViewDescriptor descriptor, MemberScope scope
) {
return null;
}
@Override
public Void visitVariableDescriptor(
VariableDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedVariables(descriptor.getName(), NoLookupLocation.FROM_TEST));
return null;
}
@Override
public Void visitFunctionDescriptor(
FunctionDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedFunctions(descriptor.getName(), NoLookupLocation.FROM_TEST));
return null;
}
@Override
public Void visitTypeParameterDescriptor(
TypeParameterDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedClassifier(descriptor.getName(), NoLookupLocation.FROM_TEST), true);
return null;
}
@Override
public Void visitClassDescriptor(
ClassDescriptor descriptor, MemberScope scope
) {
assertFound(scope, descriptor, scope.getContributedClassifier(descriptor.getName(), NoLookupLocation.FROM_TEST), true);
return null;
}
@Override
public Void visitTypeAliasDescriptor(TypeAliasDescriptor descriptor, MemberScope data) {
// TODO typealias
return null;
}
@Override
public Void visitModuleDeclaration(
ModuleDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Module found in scope: " + scope);
return null;
}
@Override
public Void visitConstructorDescriptor(
ConstructorDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Constructor found in scope: " + scope);
return null;
}
@Override
public Void visitScriptDescriptor(
ScriptDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Script found in scope: " + scope);
return null;
}
@Override
public Void visitPropertyDescriptor(
PropertyDescriptor descriptor, MemberScope scope
) {
return visitVariableDescriptor(descriptor, scope);
}
@Override
public Void visitValueParameterDescriptor(
ValueParameterDescriptor descriptor, MemberScope scope
) {
return visitVariableDescriptor(descriptor, scope);
}
@Override
public Void visitPropertyGetterDescriptor(
PropertyGetterDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Getter found in scope: " + scope);
return null;
}
@Override
public Void visitPropertySetterDescriptor(
PropertySetterDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Setter found in scope: " + scope);
return null;
}
@Override
public Void visitReceiverParameterDescriptor(
ReceiverParameterDescriptor descriptor, MemberScope scope
) {
report(descriptor, "Receiver parameter found in scope: " + scope);
return null;
}
}
public static class ValidationDiagnostic {
private final DeclarationDescriptor descriptor;
private final String message;
private final Throwable stackTrace;
private ValidationDiagnostic(@NotNull DeclarationDescriptor descriptor, @NotNull String message) {
this.descriptor = descriptor;
this.message = message;
this.stackTrace = new Throwable();
}
@NotNull
public DeclarationDescriptor getDescriptor() {
return descriptor;
}
@NotNull
public String getMessage() {
return message;
}
@NotNull
public Throwable getStackTrace() {
return stackTrace;
}
public void printStackTrace(@NotNull PrintStream out) {
out.println(descriptor);
out.println(message);
stackTrace.printStackTrace(out);
}
@Override
public String toString() {
return descriptor + " > " + message;
}
}
private static class DiagnosticCollectorForTests implements DiagnosticCollector {
private boolean errorsFound = false;
@Override
public void report(@NotNull ValidationDiagnostic diagnostic) {
diagnostic.printStackTrace(System.err);
errorsFound = true;
}
public void done() {
if (errorsFound) {
Assert.fail("Descriptor validation failed (see messages above)");
}
}
}
private DescriptorValidator() {}
}
@@ -0,0 +1,391 @@
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.util;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import kotlin.Unit;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.contracts.description.*;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.descriptors.impl.SubpackagesScope;
import org.jetbrains.kotlin.jvm.compiler.ExpectedLoadErrorsUtil;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.renderer.*;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import org.jetbrains.kotlin.resolve.MemberComparator;
import org.jetbrains.kotlin.resolve.MultiTargetPlatform;
import org.jetbrains.kotlin.resolve.scopes.ChainedMemberScope;
import org.jetbrains.kotlin.resolve.scopes.MemberScope;
import org.jetbrains.kotlin.test.KotlinTestUtils;
import org.jetbrains.kotlin.utils.Printer;
import org.junit.Assert;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Predicate;
import static org.jetbrains.kotlin.resolve.DescriptorUtils.isEnumEntry;
import static org.jetbrains.kotlin.test.util.DescriptorValidator.ValidationVisitor.errorTypesForbidden;
public class RecursiveDescriptorComparator {
private static final DescriptorRenderer DEFAULT_RENDERER = DescriptorRenderer.Companion.withOptions(
options -> {
options.setWithDefinedIn(false);
options.setExcludedAnnotationClasses(Collections.singleton(new FqName(ExpectedLoadErrorsUtil.ANNOTATION_CLASS_NAME)));
options.setOverrideRenderingPolicy(OverrideRenderingPolicy.RENDER_OPEN_OVERRIDE);
options.setIncludePropertyConstant(true);
options.setClassifierNamePolicy(ClassifierNamePolicy.FULLY_QUALIFIED.INSTANCE);
options.setVerbose(true);
options.setAnnotationArgumentsRenderingPolicy(AnnotationArgumentsRenderingPolicy.UNLESS_EMPTY);
options.setModifiers(DescriptorRendererModifier.ALL);
return Unit.INSTANCE;
}
);
public static final Configuration DONT_INCLUDE_METHODS_OF_OBJECT = new Configuration(false, false, false, false,
false, descriptor -> true, errorTypesForbidden(), DEFAULT_RENDERER);
public static final Configuration RECURSIVE = new Configuration(false, false, true, false,
false, descriptor -> true, errorTypesForbidden(), DEFAULT_RENDERER);
public static final Configuration RECURSIVE_ALL = new Configuration(true, true, true, false,
true, descriptor -> true, errorTypesForbidden(), DEFAULT_RENDERER);
public static final Predicate<DeclarationDescriptor> SKIP_BUILT_INS_PACKAGES = descriptor -> {
if (descriptor instanceof PackageViewDescriptor) {
return !KotlinBuiltIns.BUILT_INS_PACKAGE_FQ_NAME.equals(((PackageViewDescriptor) descriptor).getFqName());
}
return true;
};
private static final ImmutableSet<String> KOTLIN_ANY_METHOD_NAMES = ImmutableSet.of("equals", "hashCode", "toString");
private final Configuration conf;
public RecursiveDescriptorComparator(@NotNull Configuration conf) {
this.conf = conf;
}
public String serializeRecursively(@NotNull DeclarationDescriptor declarationDescriptor) {
StringBuilder result = new StringBuilder();
appendDeclarationRecursively(declarationDescriptor, DescriptorUtils.getContainingModule(declarationDescriptor),
new Printer(result, 1), true);
return result.toString();
}
private void appendDeclarationRecursively(
@NotNull DeclarationDescriptor descriptor,
@NotNull ModuleDescriptor module,
@NotNull Printer printer,
boolean topLevel
) {
if (!isFromModule(descriptor, module)) return;
boolean isEnumEntry = isEnumEntry(descriptor);
boolean isClassOrPackage =
(descriptor instanceof ClassOrPackageFragmentDescriptor || descriptor instanceof PackageViewDescriptor) && !isEnumEntry;
if (isClassOrPackage && !topLevel) {
printer.println();
}
boolean isPrimaryConstructor = descriptor instanceof ConstructorDescriptor && ((ConstructorDescriptor) descriptor).isPrimary();
printer.print(isPrimaryConstructor && conf.checkPrimaryConstructors ? "/*primary*/ " : "", conf.renderer.render(descriptor));
if (descriptor instanceof FunctionDescriptor && conf.checkFunctionContracts) {
FunctionDescriptor functionDescriptor = (FunctionDescriptor) descriptor;
printEffectsIfAny(functionDescriptor, printer);
}
if (isClassOrPackage) {
if (!topLevel) {
printer.printlnWithNoIndent(" {").pushIndent();
}
else {
printer.println();
printer.println();
}
if (descriptor instanceof ClassDescriptor) {
ClassDescriptor klass = (ClassDescriptor) descriptor;
appendSubDescriptors(descriptor, module,
klass.getDefaultType().getMemberScope(), klass.getConstructors(), printer);
MemberScope staticScope = klass.getStaticScope();
if (!DescriptorUtils.getAllDescriptors(staticScope).isEmpty()) {
printer.println();
printer.println("// Static members");
appendSubDescriptors(descriptor, module, staticScope, Collections.emptyList(), printer);
}
}
else if (descriptor instanceof PackageFragmentDescriptor) {
appendSubDescriptors(descriptor, module,
((PackageFragmentDescriptor) descriptor).getMemberScope(),
Collections.emptyList(), printer);
}
else if (descriptor instanceof PackageViewDescriptor) {
appendSubDescriptors(descriptor, module,
getPackageScopeInModule((PackageViewDescriptor) descriptor, module),
Collections.emptyList(), printer);
}
if (!topLevel) {
printer.popIndent().println("}");
}
}
else if (conf.checkPropertyAccessors && descriptor instanceof PropertyDescriptor) {
printer.printlnWithNoIndent();
printer.pushIndent();
PropertyDescriptor propertyDescriptor = (PropertyDescriptor) descriptor;
PropertyGetterDescriptor getter = propertyDescriptor.getGetter();
if (getter != null) {
printer.println(conf.renderer.render(getter));
}
PropertySetterDescriptor setter = propertyDescriptor.getSetter();
if (setter != null) {
printer.println(conf.renderer.render(setter));
}
printer.popIndent();
}
else {
printer.printlnWithNoIndent();
}
if (isEnumEntry) {
printer.println();
}
}
private void printEffectsIfAny(FunctionDescriptor functionDescriptor, Printer printer) {
LazyContractProvider contractProvider = functionDescriptor.getUserData(ContractProviderKey.INSTANCE);
if (contractProvider == null) return;
ContractDescription contractDescription = contractProvider.getContractDescription();
if (contractDescription == null || contractDescription.getEffects().isEmpty()) return;
printer.println();
printer.pushIndent();
for (EffectDeclaration effect : contractDescription.getEffects()) {
StringBuilder sb = new StringBuilder();
ContractDescriptionRenderer renderer = new ContractDescriptionRenderer(sb);
effect.accept(renderer, Unit.INSTANCE);
printer.println(sb.toString());
}
printer.popIndent();
}
@NotNull
private MemberScope getPackageScopeInModule(@NotNull PackageViewDescriptor descriptor, @NotNull ModuleDescriptor module) {
// See LazyPackageViewDescriptorImpl#memberScope
List<MemberScope> scopes = new ArrayList<>();
for (PackageFragmentDescriptor fragment : descriptor.getFragments()) {
if (isFromModule(fragment, module)) {
scopes.add(fragment.getMemberScope());
}
}
scopes.add(new SubpackagesScope(module, descriptor.getFqName()));
return ChainedMemberScope.Companion.create("test", scopes);
}
private boolean isFromModule(@NotNull DeclarationDescriptor descriptor, @NotNull ModuleDescriptor module) {
if (conf.renderDeclarationsFromOtherModules) return true;
if (descriptor instanceof PackageViewDescriptor) {
// PackageViewDescriptor does not belong to any module, so we check if one of its containing fragments is in our module
for (PackageFragmentDescriptor fragment : ((PackageViewDescriptor) descriptor).getFragments()) {
if (module.equals(DescriptorUtils.getContainingModule(fragment))) return true;
}
}
// 'expected' declarations do not belong to the platform-specific module, even though they participate in the analysis
if (descriptor instanceof MemberDescriptor && ((MemberDescriptor) descriptor).isExpect() &&
module.getCapability(MultiTargetPlatform.CAPABILITY) != MultiTargetPlatform.Common.INSTANCE) return false;
return module.equals(DescriptorUtils.getContainingModule(descriptor));
}
private boolean shouldSkip(@NotNull DeclarationDescriptor subDescriptor) {
boolean isFunctionFromAny = subDescriptor.getContainingDeclaration() instanceof ClassDescriptor
&& subDescriptor instanceof FunctionDescriptor
&& KOTLIN_ANY_METHOD_NAMES.contains(subDescriptor.getName().asString());
return (isFunctionFromAny && !conf.includeMethodsOfKotlinAny) || !conf.recursiveFilter.test(subDescriptor);
}
private void appendSubDescriptors(
@NotNull DeclarationDescriptor descriptor,
@NotNull ModuleDescriptor module,
@NotNull MemberScope memberScope,
@NotNull Collection<? extends DeclarationDescriptor> extraSubDescriptors,
@NotNull Printer printer
) {
if (!isFromModule(descriptor, module)) return;
List<DeclarationDescriptor> subDescriptors = Lists.newArrayList();
subDescriptors.addAll(DescriptorUtils.getAllDescriptors(memberScope));
subDescriptors.addAll(extraSubDescriptors);
subDescriptors.sort(MemberComparator.INSTANCE);
for (DeclarationDescriptor subDescriptor : subDescriptors) {
if (!shouldSkip(subDescriptor)) {
appendDeclarationRecursively(subDescriptor, module, printer, false);
}
}
}
private static void compareDescriptorWithFile(
@NotNull DeclarationDescriptor actual,
@NotNull Configuration configuration,
@NotNull File txtFile
) {
doCompareDescriptors(null, actual, configuration, txtFile);
}
public static void compareDescriptors(
@NotNull DeclarationDescriptor expected,
@NotNull DeclarationDescriptor actual,
@NotNull Configuration configuration,
@Nullable File txtFile
) {
if (expected == actual) {
throw new IllegalArgumentException("Don't invoke this method with expected == actual." +
"Invoke compareDescriptorWithFile() instead.");
}
doCompareDescriptors(expected, actual, configuration, txtFile);
}
public static void validateAndCompareDescriptorWithFile(
@NotNull DeclarationDescriptor actual,
@NotNull Configuration configuration,
@NotNull File txtFile
) {
DescriptorValidator.validate(configuration.validationStrategy, actual);
compareDescriptorWithFile(actual, configuration, txtFile);
}
public static void validateAndCompareDescriptors(
@NotNull DeclarationDescriptor expected,
@NotNull DeclarationDescriptor actual,
@NotNull Configuration configuration,
@Nullable File txtFile
) {
DescriptorValidator.validate(configuration.validationStrategy, expected);
DescriptorValidator.validate(configuration.validationStrategy, actual);
compareDescriptors(expected, actual, configuration, txtFile);
}
private static void doCompareDescriptors(
@Nullable DeclarationDescriptor expected,
@NotNull DeclarationDescriptor actual,
@NotNull Configuration configuration,
@Nullable File txtFile
) {
RecursiveDescriptorComparator comparator = new RecursiveDescriptorComparator(configuration);
String actualSerialized = comparator.serializeRecursively(actual);
if (expected != null) {
String expectedSerialized = comparator.serializeRecursively(expected);
Assert.assertEquals("Expected and actual descriptors differ", expectedSerialized, actualSerialized);
}
if (txtFile != null) {
KotlinTestUtils.assertEqualsToFile(txtFile, actualSerialized);
}
}
public static class Configuration {
private final boolean checkPrimaryConstructors;
private final boolean checkPropertyAccessors;
private final boolean includeMethodsOfKotlinAny;
private final boolean renderDeclarationsFromOtherModules;
private final boolean checkFunctionContracts;
private final Predicate<DeclarationDescriptor> recursiveFilter;
private final DescriptorRenderer renderer;
private final DescriptorValidator.ValidationVisitor validationStrategy;
public Configuration(
boolean checkPrimaryConstructors,
boolean checkPropertyAccessors,
boolean includeMethodsOfKotlinAny,
boolean renderDeclarationsFromOtherModules,
boolean checkFunctionContracts,
Predicate<DeclarationDescriptor> recursiveFilter,
DescriptorValidator.ValidationVisitor validationStrategy,
DescriptorRenderer renderer
) {
this.checkPrimaryConstructors = checkPrimaryConstructors;
this.checkPropertyAccessors = checkPropertyAccessors;
this.includeMethodsOfKotlinAny = includeMethodsOfKotlinAny;
this.renderDeclarationsFromOtherModules = renderDeclarationsFromOtherModules;
this.checkFunctionContracts = checkFunctionContracts;
this.recursiveFilter = recursiveFilter;
this.validationStrategy = validationStrategy;
this.renderer = renderer;
}
public Configuration filterRecursion(@NotNull Predicate<DeclarationDescriptor> stepIntoFilter) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny,
renderDeclarationsFromOtherModules, checkFunctionContracts, stepIntoFilter,
validationStrategy.withStepIntoFilter(stepIntoFilter), renderer);
}
public Configuration checkPrimaryConstructors(boolean checkPrimaryConstructors) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny,
renderDeclarationsFromOtherModules, checkFunctionContracts, recursiveFilter, validationStrategy, renderer);
}
public Configuration checkPropertyAccessors(boolean checkPropertyAccessors) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny,
renderDeclarationsFromOtherModules, checkFunctionContracts, recursiveFilter, validationStrategy, renderer);
}
public Configuration checkFunctionContracts(boolean checkFunctionContracts) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny,
renderDeclarationsFromOtherModules, checkFunctionContracts, recursiveFilter, validationStrategy, renderer);
}
public Configuration includeMethodsOfKotlinAny(boolean includeMethodsOfKotlinAny) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny,
renderDeclarationsFromOtherModules, checkFunctionContracts, recursiveFilter, validationStrategy, renderer);
}
public Configuration renderDeclarationsFromOtherModules(boolean renderDeclarationsFromOtherModules) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny,
renderDeclarationsFromOtherModules, checkFunctionContracts, recursiveFilter, validationStrategy, renderer);
}
public Configuration withValidationStrategy(@NotNull DescriptorValidator.ValidationVisitor validationStrategy) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny,
renderDeclarationsFromOtherModules, checkFunctionContracts, recursiveFilter, validationStrategy, renderer);
}
public Configuration withRenderer(@NotNull DescriptorRenderer renderer) {
return new Configuration(checkPrimaryConstructors, checkPropertyAccessors, includeMethodsOfKotlinAny,
renderDeclarationsFromOtherModules, checkFunctionContracts, recursiveFilter, validationStrategy, renderer);
}
}
}
@@ -0,0 +1,154 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.*;
import org.jetbrains.kotlin.name.FqName;
import org.jetbrains.kotlin.resolve.DescriptorUtils;
import java.util.Collection;
public class RecursiveDescriptorProcessor {
public static <D> boolean process(
@NotNull DeclarationDescriptor descriptor,
D data,
@NotNull DeclarationDescriptorVisitor<Boolean, D> visitor
) {
return descriptor.accept(new RecursiveVisitor<>(visitor), data);
}
private static class RecursiveVisitor<D> implements DeclarationDescriptorVisitor<Boolean, D> {
private final DeclarationDescriptorVisitor<Boolean, D> worker;
private RecursiveVisitor(@NotNull DeclarationDescriptorVisitor<Boolean, D> worker) {
this.worker = worker;
}
private boolean visitChildren(Collection<? extends DeclarationDescriptor> descriptors, D data) {
for (DeclarationDescriptor descriptor : descriptors) {
if (!descriptor.accept(this, data)) return false;
}
return true;
}
private boolean visitChildren(@Nullable DeclarationDescriptor descriptor, D data) {
if (descriptor == null) return true;
return descriptor.accept(this, data);
}
private boolean applyWorker(@NotNull DeclarationDescriptor descriptor, D data) {
return descriptor.accept(worker, data);
}
private boolean processCallable(CallableDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(descriptor.getTypeParameters(), data)
&& visitChildren(descriptor.getExtensionReceiverParameter(), data)
&& visitChildren(descriptor.getValueParameters(), data);
}
@Override
public Boolean visitPackageFragmentDescriptor(PackageFragmentDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(DescriptorUtils.getAllDescriptors(descriptor.getMemberScope()), data);
}
@Override
public Boolean visitPackageViewDescriptor(PackageViewDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(DescriptorUtils.getAllDescriptors(descriptor.getMemberScope()), data);
}
@Override
public Boolean visitVariableDescriptor(VariableDescriptor descriptor, D data) {
return processCallable(descriptor, data);
}
@Override
public Boolean visitPropertyDescriptor(PropertyDescriptor descriptor, D data) {
return processCallable(descriptor, data)
&& visitChildren(descriptor.getGetter(), data)
&& visitChildren(descriptor.getSetter(), data);
}
@Override
public Boolean visitFunctionDescriptor(FunctionDescriptor descriptor, D data) {
return processCallable(descriptor, data);
}
@Override
public Boolean visitTypeParameterDescriptor(TypeParameterDescriptor descriptor, D data) {
return applyWorker(descriptor, data);
}
@Override
public Boolean visitClassDescriptor(ClassDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(descriptor.getThisAsReceiverParameter(), data)
&& visitChildren(descriptor.getConstructors(), data)
&& visitChildren(descriptor.getTypeConstructor().getParameters(), data)
&& visitChildren(DescriptorUtils.getAllDescriptors(descriptor.getDefaultType().getMemberScope()), data);
}
@Override
public Boolean visitTypeAliasDescriptor(TypeAliasDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(descriptor.getDeclaredTypeParameters(), data);
}
@Override
public Boolean visitModuleDeclaration(ModuleDescriptor descriptor, D data) {
return applyWorker(descriptor, data)
&& visitChildren(descriptor.getPackage(FqName.ROOT), data);
}
@Override
public Boolean visitConstructorDescriptor(ConstructorDescriptor constructorDescriptor, D data) {
return visitFunctionDescriptor(constructorDescriptor, data);
}
@Override
public Boolean visitScriptDescriptor(ScriptDescriptor scriptDescriptor, D data) {
return visitClassDescriptor(scriptDescriptor, data);
}
@Override
public Boolean visitValueParameterDescriptor(ValueParameterDescriptor descriptor, D data) {
return visitVariableDescriptor(descriptor, data);
}
@Override
public Boolean visitPropertyGetterDescriptor(PropertyGetterDescriptor descriptor, D data) {
return visitFunctionDescriptor(descriptor, data);
}
@Override
public Boolean visitPropertySetterDescriptor(PropertySetterDescriptor descriptor, D data) {
return visitFunctionDescriptor(descriptor, data);
}
@Override
public Boolean visitReceiverParameterDescriptor(ReceiverParameterDescriptor descriptor, D data) {
return applyWorker(descriptor, data);
}
}
}
@@ -0,0 +1,62 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.test.util
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import com.intellij.util.SmartFMap
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.psi.KtPackageDirective
import org.jetbrains.kotlin.psi.KtTreeVisitorVoid
import java.io.File
fun String.trimTrailingWhitespacesAndAddNewlineAtEOF(): String =
this.split('\n').map { it.trimEnd() }.joinToString(separator = "\n").let {
result -> if (result.endsWith("\n")) result else result + "\n"
}
fun PsiFile.findElementByCommentPrefix(commentText: String): PsiElement? =
findElementsByCommentPrefix(commentText).keys.singleOrNull()
fun PsiFile.findElementsByCommentPrefix(prefix: String): Map<PsiElement, String> {
var result = SmartFMap.emptyMap<PsiElement, String>()
accept(
object : KtTreeVisitorVoid() {
override fun visitComment(comment: PsiComment) {
val commentText = comment.text
if (commentText.startsWith(prefix)) {
val parent = comment.parent
val elementToAdd = when (parent) {
is KtDeclaration -> parent
is PsiMember -> parent
else -> PsiTreeUtil.skipSiblingsForward(
comment,
PsiWhiteSpace::class.java, PsiComment::class.java, KtPackageDirective::class.java
)
} ?: return
result = result.plus(elementToAdd, commentText.substring(prefix.length).trim())
}
}
}
)
return result
}
fun findLastModifiedFile(dir: File, skipFile: (File) -> Boolean): File {
return dir.walk().filterNot(skipFile).maxBy { it.lastModified() }!!
}
@@ -0,0 +1,49 @@
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.tests.di
import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.config.JvmTarget
import org.jetbrains.kotlin.config.LanguageVersionSettingsImpl
import org.jetbrains.kotlin.container.StorageComponentContainer
import org.jetbrains.kotlin.container.getValue
import org.jetbrains.kotlin.container.useImpl
import org.jetbrains.kotlin.container.useInstance
import org.jetbrains.kotlin.context.ModuleContext
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.frontend.di.configureModule
import org.jetbrains.kotlin.resolve.*
import org.jetbrains.kotlin.resolve.jvm.platform.JvmPlatform
import org.jetbrains.kotlin.types.expressions.ExpressionTypingServices
import org.jetbrains.kotlin.types.expressions.FakeCallResolver
fun createContainerForTests(project: Project, module: ModuleDescriptor): ContainerForTests {
return ContainerForTests(createContainer("Tests", JvmPlatform) {
configureModule(ModuleContext(module, project), JvmPlatform, JvmTarget.JVM_1_6)
useInstance(LanguageVersionSettingsImpl.DEFAULT)
useImpl<AnnotationResolverImpl>()
useImpl<ExpressionTypingServices>()
})
}
class ContainerForTests(container: StorageComponentContainer) {
val descriptorResolver: DescriptorResolver by container
val functionDescriptorResolver: FunctionDescriptorResolver by container
val typeResolver: TypeResolver by container
val fakeCallResolver: FakeCallResolver by container
val expressionTypingServices: ExpressionTypingServices by container
}