-
Notifications
You must be signed in to change notification settings - Fork 5.6k
/
AbstractJvmRuntimeDescriptorLoaderTest.kt
238 lines (205 loc) · 11.8 KB
/
AbstractJvmRuntimeDescriptorLoaderTest.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*
* 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.runtime
import com.intellij.openapi.util.io.FileUtil
import org.jetbrains.kotlin.cli.common.output.outputUtils.writeAllTo
import org.jetbrains.kotlin.codegen.GenerationUtils
import org.jetbrains.kotlin.codegen.forTestCompile.ForTestCompileRuntime
import org.jetbrains.kotlin.descriptors.*
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.JvmAnnotationNames
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.DescriptorRenderer
import org.jetbrains.kotlin.renderer.DescriptorRendererModifier
import org.jetbrains.kotlin.renderer.OverrideRenderingPolicy
import org.jetbrains.kotlin.renderer.ParameterNameRenderingPolicy
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.lazy.JvmResolveUtil
import org.jetbrains.kotlin.resolve.scopes.*
import org.jetbrains.kotlin.serialization.deserialization.findClassAcrossModuleDependencies
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.types.TypeSubstitutor
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
public abstract class AbstractJvmRuntimeDescriptorLoaderTest : TestCaseWithTmpdir() {
companion object {
private val renderer = DescriptorRenderer.withOptions {
withDefinedIn = false
excludedAnnotationClasses = (listOf(
FqName(ExpectedLoadErrorsUtil.ANNOTATION_CLASS_NAME)
) + JvmAnnotationNames.ANNOTATIONS_COPIED_TO_TYPES).toSet()
overrideRenderingPolicy = OverrideRenderingPolicy.RENDER_OPEN_OVERRIDE
parameterNameRenderingPolicy = ParameterNameRenderingPolicy.NONE
includePropertyConstant = false
verbose = true
renderDefaultAnnotationArguments = true
modifiers = DescriptorRendererModifier.ALL
}
}
// 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 TestJdkKind.MOCK_JDK
compileFile(file, text, jdkKind)
val classLoader = URLClassLoader(arrayOf(tmpdir.toURI().toURL()), ForTestCompileRuntime.runtimeAndReflectJarClassLoader())
val actual = createReflectedPackageView(classLoader, JvmResolveUtil.TEST_MODULE_NAME)
val comparatorConfiguration = Configuration(
/* checkPrimaryConstructors = */ fileName.endsWith(".kt"),
/* checkPropertyAccessors = */ true,
/* includeMethodsOfKotlinAny = */ 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, getTestRootDisposable(), jdkKind, ConfigurationKind.ALL, true
).first
RecursiveDescriptorComparator.validateAndCompareDescriptors(expected, actual, comparatorConfiguration, null)
}
private fun DeclarationDescriptor.isJavaAnnotationConstructor() =
this is ConstructorDescriptor &&
getContainingDeclaration().let { container ->
container is JavaClassDescriptor &&
container.getKind() == ClassKind.ANNOTATION_CLASS
}
private fun compileFile(file: File, text: String, jdkKind: TestJdkKind) {
val fileName = file.getName()
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
)
val jetFile = KotlinTestUtils.createFile(file.getPath(), text, environment.project)
GenerationUtils.compileFileGetClassFileFactoryForTest(jetFile, environment).writeAllTo(tmpdir)
}
}
}
private fun createReflectedPackageView(classLoader: URLClassLoader, moduleName: String): SyntheticPackageViewForTest {
val moduleData = RuntimeModuleData.create(classLoader)
moduleData.packageFacadeProvider.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.relativeTo(tmpdir).substringBeforeLast(".class").replace('/', '.').replace('\\', '.')
val klass = classLoader.loadClass(className).sure { "Couldn't load class $className" }
val header = ReflectKotlinClass.create(klass)?.getClassHeader()
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 && !header.isLocalClass)) {
// 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")
return typeAnnotations.fold(text) { text, annotation -> text.replace("@$annotation", "") }.replace(
"@interface",
"@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @interface"
)
}
private class SyntheticPackageViewForTest(override val module: ModuleDescriptor,
packageScopes: List<MemberScope>,
classes: List<ClassifierDescriptor>) : PackageViewDescriptor {
private val scope: MemberScope
init {
scope = ChainedScope("synthetic package view for test", ScopeWithClassifiers(classes), *packageScopes.toTypedArray())
}
override val fqName: FqName
get() = LoadDescriptorUtil.TEST_PACKAGE_FQNAME
override val memberScope: MemberScope
get() = 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 substitute(substitutor: TypeSubstitutor) = throw UnsupportedOperationException()
override fun acceptVoid(visitor: DeclarationDescriptorVisitor<Void, Void>?) = throw UnsupportedOperationException()
override fun getAnnotations() = throw UnsupportedOperationException()
override fun getName() = throw UnsupportedOperationException()
override val fragments: Nothing
get() = throw UnsupportedOperationException()
}
private class ScopeWithClassifiers(classifiers: List<ClassifierDescriptor>) : MemberScopeImpl() {
private val classifierMap = HashMap<Name, ClassifierDescriptor>()
val redeclarationHandler = RedeclarationHandler.THROW_EXCEPTION
init {
for (classifier in classifiers) {
classifierMap.put(classifier.name, classifier)?.let {
redeclarationHandler.handleRedeclaration(it, 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")
}
}
}