Move compiler/tests-common/{src -> tests}, adjust dependencies
This commit is contained in:
@@ -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 }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
+193
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+47
@@ -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)
|
||||
}
|
||||
}
|
||||
+43
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+42
@@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
+130
@@ -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;
|
||||
}
|
||||
}
|
||||
+232
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+171
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+35
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+151
@@ -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;
|
||||
}
|
||||
}
|
||||
+44
@@ -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
|
||||
}
|
||||
}
|
||||
+134
@@ -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;
|
||||
}
|
||||
}
|
||||
+84
@@ -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];
|
||||
}
|
||||
}
|
||||
+75
@@ -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(":")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -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);
|
||||
}
|
||||
}
|
||||
+275
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+137
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -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);
|
||||
}
|
||||
}
|
||||
+25
@@ -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)
|
||||
}
|
||||
+25
@@ -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";
|
||||
}
|
||||
}
|
||||
+169
@@ -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) {}
|
||||
}
|
||||
}
|
||||
+340
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -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)
|
||||
}
|
||||
}
|
||||
+55
@@ -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)
|
||||
)
|
||||
}
|
||||
+135
@@ -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() {
|
||||
}
|
||||
}
|
||||
+150
@@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+45
@@ -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()
|
||||
}
|
||||
}
|
||||
+259
@@ -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)
|
||||
+159
@@ -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);
|
||||
}
|
||||
}
|
||||
+186
@@ -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) + ")");
|
||||
}
|
||||
}
|
||||
+65
@@ -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()));
|
||||
}
|
||||
}
|
||||
+390
@@ -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();
|
||||
}
|
||||
}
|
||||
+35
@@ -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))
|
||||
}
|
||||
}
|
||||
+56
@@ -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() }}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+158
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+40
@@ -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)
|
||||
}
|
||||
}
|
||||
+118
@@ -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(".+(?=:)")
|
||||
}
|
||||
}
|
||||
+149
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
+77
@@ -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();
|
||||
}
|
||||
}
|
||||
+37
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
+340
@@ -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));
|
||||
}
|
||||
}
|
||||
+80
@@ -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;
|
||||
}
|
||||
}
|
||||
+71
@@ -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();
|
||||
}
|
||||
}
|
||||
+496
@@ -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() {
|
||||
}
|
||||
}
|
||||
+40
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+167
@@ -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;
|
||||
}
|
||||
}
|
||||
+64
@@ -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());
|
||||
}
|
||||
}
|
||||
+94
@@ -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) {
|
||||
}
|
||||
|
||||
}
|
||||
+141
@@ -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() {
|
||||
}
|
||||
|
||||
}
|
||||
+115
@@ -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());
|
||||
}
|
||||
}
|
||||
+157
@@ -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();
|
||||
}
|
||||
}
|
||||
+197
@@ -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() {}
|
||||
}
|
||||
+391
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+154
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user