[Gradle] Implement IdeaKotlinObjectGraphTest and fix discovered issues

KT-54825
This commit is contained in:
Sebastian Sellmair
2022-11-10 14:51:06 +01:00
committed by Space Team
parent 9771e57951
commit 224a237385
6 changed files with 168 additions and 6 deletions
@@ -15,7 +15,6 @@ import org.jetbrains.kotlin.gradle.idea.proto.generated.kpm.IdeaKpmDependencyPro
import org.jetbrains.kotlin.gradle.idea.proto.generated.kpm.IdeaKpmDependencyProto.DependencyCase import org.jetbrains.kotlin.gradle.idea.proto.generated.kpm.IdeaKpmDependencyProto.DependencyCase
import org.jetbrains.kotlin.gradle.idea.proto.generated.kpm.ideaKpmDependencyProto import org.jetbrains.kotlin.gradle.idea.proto.generated.kpm.ideaKpmDependencyProto
import org.jetbrains.kotlin.gradle.idea.serialize.IdeaSerializationContext import org.jetbrains.kotlin.gradle.idea.serialize.IdeaSerializationContext
import org.jetbrains.kotlin.gradle.kpm.idea.*
internal fun IdeaSerializationContext.IdeaKpmDependencyProto(dependency: IdeaKpmDependency): IdeaKpmDependencyProto { internal fun IdeaSerializationContext.IdeaKpmDependencyProto(dependency: IdeaKpmDependency): IdeaKpmDependencyProto {
return ideaKpmDependencyProto { return ideaKpmDependencyProto {
@@ -17,10 +17,18 @@ data class IdeaKotlinResolvedBinaryDependency(
val binaryFile: File, val binaryFile: File,
override val extras: MutableExtras, override val extras: MutableExtras,
override val coordinates: IdeaKotlinBinaryCoordinates? override val coordinates: IdeaKotlinBinaryCoordinates?
) : IdeaKotlinBinaryDependency() ) : IdeaKotlinBinaryDependency() {
internal companion object {
const val serialVersionUID = 0L
}
}
data class IdeaKotlinUnresolvedBinaryDependency( data class IdeaKotlinUnresolvedBinaryDependency(
val cause: String?, val cause: String?,
override val coordinates: IdeaKotlinBinaryCoordinates?, override val coordinates: IdeaKotlinBinaryCoordinates?,
override val extras: MutableExtras override val extras: MutableExtras
) : IdeaKotlinBinaryDependency() ) : IdeaKotlinBinaryDependency() {
internal companion object {
const val serialVersionUID = 0L
}
}
@@ -5,7 +5,9 @@
package org.jetbrains.kotlin.gradle.idea.tcs package org.jetbrains.kotlin.gradle.idea.tcs
sealed interface IdeaKotlinDependencyCoordinates import java.io.Serializable
sealed interface IdeaKotlinDependencyCoordinates : Serializable
data class IdeaKotlinBinaryCoordinates( data class IdeaKotlinBinaryCoordinates(
val group: String, val group: String,
@@ -16,6 +18,11 @@ data class IdeaKotlinBinaryCoordinates(
override fun toString(): String { override fun toString(): String {
return "$group:$module:$version${sourceSetName?.let { ":$it" }.orEmpty()}" return "$group:$module:$version${sourceSetName?.let { ":$it" }.orEmpty()}"
} }
internal companion object {
const val serialVersionUID = 0L
}
} }
data class IdeaKotlinSourceCoordinates( data class IdeaKotlinSourceCoordinates(
@@ -27,4 +34,8 @@ data class IdeaKotlinSourceCoordinates(
override fun toString(): String { override fun toString(): String {
return "${buildId.takeIf { it != ":" }.orEmpty()}$projectPath/$sourceSetName" return "${buildId.takeIf { it != ":" }.orEmpty()}$projectPath/$sourceSetName"
} }
internal companion object {
const val serialVersionUID = 0L
}
} }
@@ -14,6 +14,14 @@ data class IdeaKotlinSourceDependency(
override val extras: MutableExtras override val extras: MutableExtras
) : IdeaKotlinDependency { ) : IdeaKotlinDependency {
enum class Type : Serializable { enum class Type : Serializable {
Regular, Friend, DependsOn Regular, Friend, DependsOn;
internal companion object {
const val serialVersionUID = 0L
}
}
internal companion object {
const val serialVersionUID = 0L
} }
} }
@@ -0,0 +1,137 @@
/*
* 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.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.Reflections
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
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
@RunWith(Parameterized::class)
class IdeaKotlinObjectGraphTest(private val node: KClass<*>, private val clazzName: String) {
@Test
fun `test - node implements Serializable`() {
assertTrue(
Serializable::class.java.isAssignableFrom(node.java),
"Expected clazz $clazzName to implement ${Serializable::class.qualifiedName}`"
)
}
@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"
)
}
}
private fun Class<*>.getDeclaredFieldOrNull(name: String): Field? {
return try {
getDeclaredField(name)
} catch (t: NoSuchFieldException) {
return null
}
}
companion object {
private val reflections = Reflections("org.jetbrains.kotlin")
private val ideaTcsReflections = Reflections("org.jetbrains.kotlin.gradle.idea.tcs")
private val ignoredNodes = setOf(
/*
Extras interface and AbstractExtras are okay for now:
Let's check known implementations for correctness
*/
Extras::class, MutableExtras::class, AbstractExtras::class
)
@JvmStatic
@Parameters(name = "{1}")
fun findClasses(): List<Array<Any>> {
val classes = mutableSetOf<KClass<*>>()
val resolveQueue = ArrayDeque<KClass<*>>()
resolveQueue += ideaTcsReflections.getAll(Scanners.SubTypes)
.map { Class.forName(it) }
.filter { it.name.startsWith("org.jetbrains.kotlin.gradle.idea.tcs") }
.map { it.kotlin }
while (resolveQueue.isNotEmpty()) {
val next = resolveQueue.removeFirst()
if (!classes.add(next)) continue
next.resolveReachableClasses().forEach { child ->
resolveQueue.add(child)
if (child.java.isInterface || Modifier.isAbstract(child.java.modifiers)) {
val subtypes = reflections.getSubTypesOf(child.java).map { it.kotlin }
assertTrue(subtypes.isNotEmpty(), "Missing implementations for $child")
resolveQueue.addAll(subtypes)
}
}
}
return classes
.filter { it !in ignoredNodes }
.map { clazz -> arrayOf(clazz, checkNotNull(clazz.displayName())) }
}
private fun KClass<*>.displayName() = java.name
.removePrefix("org.jetbrains.kotlin")
.removePrefix(".gradle")
.removePrefix(".idea")
.removePrefix(".tcs")
.removePrefix(".")
private fun KClass<*>.resolveReachableClasses(): Set<KClass<*>> {
return this.memberProperties
.map { member -> member.returnType }
.flatMap { type -> setOf(type) + type.arguments.mapNotNull { it.type } }
.mapNotNull { type -> type.classifier as? KClass<*> }
.filter { clazz -> clazz.java.name.startsWith("org.jetbrains") }
.toSet()
}
}
}
@@ -8,7 +8,6 @@
package org.jetbrains.kotlin.gradle.idea.testFixtures.kpm package org.jetbrains.kotlin.gradle.idea.testFixtures.kpm
import org.jetbrains.kotlin.gradle.idea.kpm.* import org.jetbrains.kotlin.gradle.idea.kpm.*
import org.jetbrains.kotlin.gradle.kpm.idea.*
import kotlin.test.fail import kotlin.test.fail
fun IdeaKpmProject.assertIsNotEmpty(): IdeaKpmProject = apply { fun IdeaKpmProject.assertIsNotEmpty(): IdeaKpmProject = apply {