[Gradle] kgp-idea: Implement IdeaKotlinExtra

KT-55189
This commit is contained in:
Sebastian Sellmair
2022-11-30 16:45:44 +01:00
committed by Space Team
parent 8cee0496f3
commit bb039ae535
18 changed files with 223 additions and 94 deletions
@@ -12,4 +12,7 @@ annotation class IdeaKotlinEntity
annotation class IdeaKotlinModel
@IdeaKotlinEntity
annotation class IdeaKotlinService
annotation class IdeaKotlinService
@IdeaKotlinEntity
annotation class IdeaKotlinExtra
@@ -1,25 +0,0 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.tcs
import org.reflections.Reflections
import kotlin.reflect.KClass
object ReflectionTestUtils {
const val ideaTcsPackage = "org.jetbrains.kotlin.gradle.idea.tcs"
val ideaTcsReflections = Reflections(ideaTcsPackage)
const val kotlinPackage = "org.jetbrains.kotlin"
val kotlinReflections = Reflections(kotlinPackage)
fun KClass<*>.displayName() = java.name
.removePrefix("org.jetbrains.kotlin")
.removePrefix(".gradle")
.removePrefix(".idea")
.removePrefix(".tcs")
.removePrefix(".")
}
@@ -3,19 +3,22 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.kpm
package org.jetbrains.kotlin.gradle.idea.test.kpm
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.kotlin.dsl.create
import org.gradle.testfixtures.ProjectBuilder
import org.jetbrains.kotlin.gradle.idea.kpm.IdeaKpmFragment
import org.jetbrains.kotlin.gradle.idea.kpm.IdeaKpmProject
import org.jetbrains.kotlin.gradle.idea.kpm.name
import org.jetbrains.kotlin.gradle.idea.testFixtures.kpm.TestIdeaKpmExtra
import org.jetbrains.kotlin.gradle.idea.testFixtures.serialize.TestIdeaExtrasSerializationExtension.anySerializableKey
import org.jetbrains.kotlin.gradle.idea.testFixtures.utils.copy
import org.jetbrains.kotlin.gradle.idea.testFixtures.utils.createProxyInstance
import org.jetbrains.kotlin.gradle.idea.testFixtures.utils.unwrapProxyInstance
import org.jetbrains.kotlin.gradle.idea.testUtils.buildIdeaKpmProject
import org.jetbrains.kotlin.gradle.idea.testUtils.createKpmProject
import org.jetbrains.kotlin.gradle.idea.testUtils.deserializeIdeaKpmProjectWithBackwardsCompatibleClasses
import org.jetbrains.kotlin.gradle.idea.test.testUtils.buildIdeaKpmProject
import org.jetbrains.kotlin.gradle.idea.test.testUtils.createKpmProject
import org.jetbrains.kotlin.gradle.idea.test.testUtils.deserializeIdeaKpmProjectWithBackwardsCompatibleClasses
import org.jetbrains.kotlin.gradle.plugin.KotlinPm20PluginWrapper
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmIosX64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.GradleKpmLinuxX64Variant
@@ -3,14 +3,16 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.kpm
package org.jetbrains.kotlin.gradle.idea.test.kpm
import org.jetbrains.kotlin.gradle.idea.kpm.IdeaKpmProject
import org.jetbrains.kotlin.gradle.idea.kpm.name
import org.jetbrains.kotlin.gradle.idea.testFixtures.kpm.TestIdeaKpmInstances
import org.jetbrains.kotlin.gradle.idea.testFixtures.kpm.assertContainsFragment
import org.jetbrains.kotlin.gradle.idea.testFixtures.kpm.assertContainsModule
import org.jetbrains.kotlin.gradle.idea.testFixtures.utils.copy
import org.jetbrains.kotlin.gradle.idea.testFixtures.utils.createProxyInstance
import org.jetbrains.kotlin.gradle.idea.testUtils.deserializeIdeaKpmProjectWithBackwardsCompatibleClasses
import org.jetbrains.kotlin.gradle.idea.test.testUtils.deserializeIdeaKpmProjectWithBackwardsCompatibleClasses
import org.junit.Test
import kotlin.test.assertEquals
@@ -3,8 +3,11 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.kpm
package org.jetbrains.kotlin.gradle.idea.test.kpm
import org.jetbrains.kotlin.gradle.idea.kpm.IdeaKpmProjectBinaryContainer
import org.jetbrains.kotlin.gradle.idea.kpm.IdeaKpmProjectContainer
import org.jetbrains.kotlin.gradle.idea.kpm.IdeaKpmProjectInstanceContainer
import org.jetbrains.kotlin.gradle.idea.testFixtures.kpm.TestIdeaKpmInstances
import kotlin.test.*
@@ -3,8 +3,10 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.kpm
package org.jetbrains.kotlin.gradle.idea.test.kpm
import org.jetbrains.kotlin.gradle.idea.kpm.IdeaKpmProject
import org.jetbrains.kotlin.gradle.idea.kpm.WriteReplacedModel
import org.jetbrains.kotlin.tooling.core.AbstractExtras
import org.jetbrains.kotlin.tooling.core.Extras
import org.jetbrains.kotlin.tooling.core.MutableExtras
@@ -3,8 +3,10 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.serialize
package org.jetbrains.kotlin.gradle.idea.test.serialize
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinExtrasSerializer
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinJavaIoSerializableExtrasSerializer
import org.jetbrains.kotlin.gradle.idea.testFixtures.serialize.TestIdeaKotlinSerializationContext
import org.jetbrains.kotlin.tooling.core.withLinearClosure
import org.junit.Test
@@ -3,8 +3,9 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.serialize
package org.jetbrains.kotlin.gradle.idea.test.serialize
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinBooleanExtrasSerializer
import org.jetbrains.kotlin.gradle.idea.testFixtures.serialize.TestIdeaKotlinSerializationContext
import kotlin.test.Test
import kotlin.test.assertEquals
@@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.serialize
package org.jetbrains.kotlin.gradle.idea.test.serialize
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinIntExtrasSerializer.deserialize
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinIntExtrasSerializer.serialize
@@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.serialize
package org.jetbrains.kotlin.gradle.idea.test.serialize
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinStringExtrasSerializer.deserialize
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinStringExtrasSerializer.serialize
@@ -3,8 +3,12 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.tcs
package org.jetbrains.kotlin.gradle.idea.test.tcs
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinEntity
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinExtra
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinModel
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinService
import kotlin.reflect.KClass
import kotlin.reflect.full.allSuperclasses
import kotlin.reflect.full.findAnnotation
@@ -17,4 +21,6 @@ fun KClass<*>.findIdeaKotlinEntityAnnotations(): List<Annotation> {
val KClass<*>.isIdeaKotlinModel get() = findIdeaKotlinEntityAnnotations().singleOrNull() is IdeaKotlinModel
val KClass<*>.isIdeaKotlinExtra get() = findIdeaKotlinEntityAnnotations().singleOrNull() is IdeaKotlinExtra
val KClass<*>.isIdeaKotlinService get() = findIdeaKotlinEntityAnnotations().singleOrNull() is IdeaKotlinService
@@ -3,14 +3,15 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.tcs
package org.jetbrains.kotlin.gradle.idea.test.tcs
import org.jetbrains.kotlin.gradle.idea.tcs.ReflectionTestUtils.displayName
import org.jetbrains.kotlin.gradle.idea.tcs.ReflectionTestUtils.ideaTcsPackage
import org.jetbrains.kotlin.gradle.idea.tcs.ReflectionTestUtils.ideaTcsReflections
import org.jetbrains.kotlin.gradle.idea.tcs.IdeaKotlinEntity
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.displayName
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.getAllKotlinClasses
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.ideaTcsPackage
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.ideaTcsReflections
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.reflections.scanners.Scanners
import kotlin.reflect.KClass
import kotlin.test.Test
import kotlin.test.fail
@@ -35,10 +36,9 @@ class IdeaKotlinEntityTest(private val node: KClass<*>, private val clazzName: S
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun findClasses(): List<Array<Any>> {
return ideaTcsReflections.getAll(Scanners.SubTypes)
.map { Class.forName(it) }
.filter { !it.isAnnotation }
.map { it.kotlin }
return ideaTcsReflections.getAllKotlinClasses()
.filter { !it.java.isAnnotation }
.filter { !it.isCompanion }
.filter { it.qualifiedName.orEmpty().startsWith(ideaTcsPackage) }
.map { clazz -> arrayOf(clazz, checkNotNull(clazz.displayName())) }
@@ -0,0 +1,70 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.test.tcs
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.displayName
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.getAllKotlinClasses
import org.jetbrains.kotlin.tooling.core.Extras
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import java.io.Serializable
import kotlin.reflect.KClass
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
import kotlin.test.Test
import kotlin.test.fail
@RunWith(Parameterized::class)
class IdeaKotlinExtraTest(private val node: KClass<*>, private val clazzName: String) {
@Test
fun `test - node is data class`() {
if (!node.isData) fail("Expected $clazzName to be marked as data class")
}
@Test
fun `test - node is Serializable`() {
if (!node.isSubclassOf(Serializable::class)) fail("Expected $clazzName to implement ${Serializable::class}")
}
@Test
fun `test - contains serialVersionUID`() {
assertNodeContainsSerialVersionUID(node)
}
@Test
fun `test - all members properties are nullable`() {
node.memberProperties.forEach { member ->
if (!member.returnType.isMarkedNullable) fail("Expected $clazzName.${member.name} to be marked nullable")
}
}
@Test
fun `test - node has companion with key`() {
val companion = node.companionObject ?: fail("Missing companion on $clazzName")
val keyProperty = companion.memberProperties.find { it.name == "key" } ?: fail("Missing .key on $clazzName.${companion.simpleName}")
val keyType = keyProperty.returnType
val isCorrectType = when {
keyType.classifier != Extras.Key::class -> false
keyType.arguments.singleOrNull()?.type?.classifier != node -> false
else -> true
}
if (!isCorrectType) fail("Expected key to be type Extras.Key<${node.simpleName}>. Found $keyType")
}
companion object {
@JvmStatic
@Parameterized.Parameters(name = "{1}")
fun findClasses(): List<Array<Any>> {
return ReflectionTestUtils.ideaTcsReflections.getAllKotlinClasses()
.filter { it.isIdeaKotlinExtra }
.map { arrayOf(it, it.displayName()) }
}
}
}
@@ -3,20 +3,19 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.tcs
package org.jetbrains.kotlin.gradle.idea.test.tcs
import org.jetbrains.kotlin.gradle.idea.tcs.ReflectionTestUtils.displayName
import org.jetbrains.kotlin.gradle.idea.tcs.ReflectionTestUtils.ideaTcsReflections
import org.jetbrains.kotlin.gradle.idea.tcs.ReflectionTestUtils.kotlinReflections
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.displayName
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.getAllKotlinClasses
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.ideaTcsReflections
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.kotlinReflections
import org.jetbrains.kotlin.tooling.core.AbstractExtras
import org.jetbrains.kotlin.tooling.core.Extras
import org.jetbrains.kotlin.tooling.core.MutableExtras
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.junit.runners.Parameterized.Parameters
import org.reflections.scanners.Scanners
import java.io.Serializable
import java.lang.reflect.Field
import java.lang.reflect.Modifier
import kotlin.reflect.KClass
import kotlin.reflect.full.memberProperties
@@ -25,6 +24,13 @@ import kotlin.test.*
@RunWith(Parameterized::class)
class IdeaKotlinModelObjectGraphTest(private val node: KClass<*>, private val clazzName: String) {
@Test
fun `test - node is sealed`() {
if (node.java.isInterface || node.isAbstract) {
assertTrue(node.isSealed, "Expected $clazzName to be sealed")
}
}
@Test
fun `test - node implements Serializable`() {
assertTrue(
@@ -33,47 +39,13 @@ class IdeaKotlinModelObjectGraphTest(private val node: KClass<*>, private val cl
)
}
@Test
fun `test - node is sealed`() {
if (node.java.isInterface || node.isAbstract) {
assertTrue(node.isSealed, "Expected $clazzName to be sealed")
}
}
@Test
fun `test - node implementations contain serialVersionUID`() {
if (!node.java.isInterface && !Modifier.isAbstract(node.java.modifiers)) {
val serialVersionUID = assertNotNull(
node.java.getDeclaredFieldOrNull("serialVersionUID"),
"Expected $node to declare 'serialVersionUID' field"
)
assertTrue(
Modifier.isStatic(serialVersionUID.modifiers),
"Expected $node to declare 'serialVersionUID' statically"
)
assertTrue(
serialVersionUID.type.isPrimitive,
"Expected $node to declare primitive 'serialVersionUID'"
)
assertEquals(
serialVersionUID.type, Long::class.javaPrimitiveType,
"Expected $node to declare 'serialVersionUID' of type Long"
)
assertNodeContainsSerialVersionUID(node)
}
}
private fun Class<*>.getDeclaredFieldOrNull(name: String): Field? {
return try {
getDeclaredField(name)
} catch (t: NoSuchFieldException) {
return null
}
}
companion object {
private val ignoredNodes = setOf(
/*
@@ -90,9 +62,7 @@ class IdeaKotlinModelObjectGraphTest(private val node: KClass<*>, private val cl
val resolveQueue = ArrayDeque<KClass<*>>()
resolveQueue += ideaTcsReflections.getAll(Scanners.SubTypes)
.map { Class.forName(it) }
.map { it.kotlin }
resolveQueue += ideaTcsReflections.getAllKotlinClasses()
.filter { it.isIdeaKotlinModel }
while (resolveQueue.isNotEmpty()) {
@@ -0,0 +1,54 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.test.tcs
import javassist.bytecode.ClassFile
import org.reflections.Reflections
import org.reflections.scanners.Scanner
import org.reflections.scanners.Scanners
import java.lang.reflect.Field
import kotlin.reflect.KClass
import kotlin.reflect.full.superclasses
object ReflectionTestUtils {
const val ideaTcsPackage = "org.jetbrains.kotlin.gradle.idea.tcs"
val ideaTcsReflections = Reflections(ideaTcsPackage, AllClassScanner, Scanners.SubTypes, Scanners.TypesAnnotated)
const val kotlinPackage = "org.jetbrains.kotlin"
val kotlinReflections = Reflections(kotlinPackage, AllClassScanner, Scanners.SubTypes, Scanners.TypesAnnotated)
fun KClass<*>.displayName() = java.name
.removePrefix("org.jetbrains.kotlin")
.removePrefix(".gradle")
.removePrefix(".idea")
.removePrefix(".tcs")
.removePrefix(".")
fun Reflections.getAllKotlinClasses(): Set<KClass<*>> {
return getAll(AllClassScanner)
.mapNotNull { runCatching { Class.forName(it) }.getOrNull() }
.mapNotNull { runCatching { it.kotlin }.getOrNull() }
.filter { runCatching { it.superclasses }.isSuccess } // Filter out Packages and file facades
.filter { !it.qualifiedName.orEmpty().startsWith("$ideaTcsPackage.test") } // Filter out test sources
.toSet()
}
private object AllClassScanner : Scanner {
override fun scan(classFile: ClassFile): MutableList<MutableMap.MutableEntry<String, String>> {
return mutableListOf(entry(classFile.name, classFile.name))
}
}
fun Class<*>.getDeclaredFieldOrNull(name: String): Field? {
return try {
getDeclaredField(name)
} catch (t: NoSuchFieldException) {
return null
}
}
}
@@ -0,0 +1,38 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.test.tcs
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.displayName
import org.jetbrains.kotlin.gradle.idea.test.tcs.ReflectionTestUtils.getDeclaredFieldOrNull
import java.lang.reflect.Modifier
import kotlin.reflect.KClass
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
fun assertNodeContainsSerialVersionUID(node: KClass<*>) {
if (!node.java.isInterface && !Modifier.isAbstract(node.java.modifiers)) {
val serialVersionUID = assertNotNull(
node.java.getDeclaredFieldOrNull("serialVersionUID"),
"Expected ${node.displayName()} to declare 'serialVersionUID' field"
)
assertTrue(
Modifier.isStatic(serialVersionUID.modifiers),
"Expected ${node.displayName()} to declare 'serialVersionUID' statically"
)
assertTrue(
serialVersionUID.type.isPrimitive,
"Expected ${node.displayName()} to declare primitive 'serialVersionUID'"
)
assertEquals(
serialVersionUID.type, Long::class.javaPrimitiveType,
"Expected ${node.displayName()} to declare 'serialVersionUID' of type Long"
)
}
}
@@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.testUtils
package org.jetbrains.kotlin.gradle.idea.test.testUtils
import org.jetbrains.kotlin.gradle.idea.kpm.IdeaKpmProject
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaKotlinSerializationLogger
@@ -3,7 +3,7 @@
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.gradle.idea.testUtils
package org.jetbrains.kotlin.gradle.idea.test.testUtils
import org.gradle.api.Project
import org.gradle.api.artifacts.verification.DependencyVerificationMode