[KPM] Implement test - attaching serializable models

KT-51262
KT-51220
This commit is contained in:
sebastian.sellmair
2022-02-14 15:59:21 +01:00
committed by Space
parent 1787d18faa
commit 9e19e3efa7
@@ -12,7 +12,12 @@ import org.gradle.api.internal.project.ProjectInternal
import org.gradle.configurationcache.extensions.serviceOf
import org.gradle.kotlin.dsl.create
import org.gradle.testfixtures.ProjectBuilder
import org.gradle.tooling.internal.adapter.ProtocolToModelAdapter
import org.gradle.tooling.provider.model.ToolingModelBuilderRegistry
import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelKey
import org.jetbrains.kotlin.gradle.kpm.KotlinExternalModelSerializer.Companion.serializable
import org.jetbrains.kotlin.gradle.kpm.external.ExternalVariantApi
import org.jetbrains.kotlin.gradle.kpm.external.external
import org.jetbrains.kotlin.gradle.plugin.KotlinPm20PluginWrapper
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinIosX64Variant
import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.KotlinLinuxX64Variant
@@ -21,9 +26,7 @@ import org.jetbrains.kotlin.gradle.plugin.mpp.pm20.jvm
import org.junit.Test
import java.io.*
import java.net.URLClassLoader
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertSame
import kotlin.test.*
/**
* This test is designed to test if serialized models can still be deserialized with a older (minimal supported)
@@ -55,42 +58,59 @@ class BackwardsCompatibilityDeserializationTest {
}
project.evaluate()
testDeserializeCompatibility(buildModel(project))
}
val model = buildModel(project)
val deserializedModel = deserializeModelWithBackwardsCompatibleClasses(model)
private fun buildModel(project: Project): IdeaKotlinProjectModel {
return project.serviceOf<ToolingModelBuilderRegistry>().getBuilder(IdeaKotlinProjectModel::class.java.name)
.buildAll(IdeaKotlinProjectModel::class.java.name, project) as IdeaKotlinProjectModel
}
/* Use reflection magic (aka ProtocolToModelAdapter) to proxy the deserialized model */
run {
val adaptedDeserializedModel = ProtocolToModelAdapter().adapt(IdeaKotlinProjectModel::class.java, deserializedModel)
private fun testDeserializeCompatibility(model: IdeaKotlinProjectModel) {
val serializedModel = ByteArrayOutputStream().run {
ObjectOutputStream(this).use { stream -> stream.writeObject(model) }
toByteArray()
}
val mainModule = adaptedDeserializedModel.modules.firstOrNull { it.moduleIdentifier.moduleClassifier == null }
?: fail("Missing main module")
val backwardsCompatibilityTestClassLoader = getClassLoaderForBackwardsCompatibilityTest()
val backwardsCompatibilityObjectInputStream = object : ObjectInputStream(ByteArrayInputStream(serializedModel)) {
override fun resolveClass(desc: ObjectStreamClass): Class<*> {
return backwardsCompatibilityTestClassLoader.loadClass(desc.name)
val testModule = adaptedDeserializedModel.modules.firstOrNull { it.moduleIdentifier.moduleClassifier == "test" }
?: fail("Missing test module")
listOf(mainModule, testModule).forEach { module ->
assertEquals(
model.modules.flatMap { it.fragments }.map { it.name }.toSet(),
module.fragments.map { it.name }.toSet(),
"Expected all fragment names to be present"
)
}
}
val backwardsCompatibilityModel = backwardsCompatibilityObjectInputStream.readObject()
assertSame(
backwardsCompatibilityModel.javaClass.classLoader,
backwardsCompatibilityTestClassLoader,
"Expected deserialized model being loaded by 'backwardsCompatibilityTestClassLoader'"
)
}
assertNotEquals<Class<*>>(
model.javaClass, backwardsCompatibilityModel.javaClass,
"Expected deserialized model java class to be different from origin"
)
@OptIn(ExternalVariantApi::class)
@Test
fun `test - attaching serializable models`() {
data class RetainedModel(val id: Int) : Serializable
data class UnretainedModel(val id: Int)
assertEquals(
model.javaClass.name, backwardsCompatibilityModel.javaClass.name,
"Expected deserialized model to be same java class as origin"
)
val retainedModelKey = KotlinExternalModelKey<RetainedModel>(serializable())
val unretainedModelKey = KotlinExternalModelKey<UnretainedModel>()
val project = ProjectBuilder.builder().build() as ProjectInternal
project.plugins.apply(KotlinPm20PluginWrapper::class.java)
/* Setup example project */
val kotlinExtension = project.extensions.getByType(KotlinPm20ProjectExtension::class.java)
kotlinExtension.main.common.external[retainedModelKey] = RetainedModel(2411)
kotlinExtension.main.common.external[unretainedModelKey] = UnretainedModel(510)
val model = buildModel(project)
val deserializedModel = deserializeModelWithBackwardsCompatibleClasses(model)
val adaptedDeserializedModel = ProtocolToModelAdapter().adapt(IdeaKotlinProjectModel::class.java, deserializedModel)
val mainModule = adaptedDeserializedModel.modules.find { it.moduleIdentifier.moduleClassifier == null }
?: fail("Missing main module")
val commonFragment = mainModule.fragments.find { it.name == "common" }
?: fail("Missing common fragment")
assertEquals(1, commonFragment.external.ids.size)
assertEquals(RetainedModel(2411), commonFragment.external[retainedModelKey])
assertNull(commonFragment.external[unretainedModelKey])
}
}
@@ -107,3 +127,39 @@ private fun getClasspathForBackwardsCompatibilityTest(): List<File> {
.onEach { file -> if (!file.exists()) println("[WARNING] Missing $file") }
}
private fun buildModel(project: Project): IdeaKotlinProjectModel {
return project.serviceOf<ToolingModelBuilderRegistry>().getBuilder(IdeaKotlinProjectModel::class.java.name)
.buildAll(IdeaKotlinProjectModel::class.java.name, project) as IdeaKotlinProjectModel
}
private fun deserializeModelWithBackwardsCompatibleClasses(model: IdeaKotlinProjectModel): Any {
val serializedModel = ByteArrayOutputStream().run {
ObjectOutputStream(this).use { stream -> stream.writeObject(model) }
toByteArray()
}
val backwardsCompatibilityTestClassLoader = getClassLoaderForBackwardsCompatibilityTest()
val backwardsCompatibilityObjectInputStream = object : ObjectInputStream(ByteArrayInputStream(serializedModel)) {
override fun resolveClass(desc: ObjectStreamClass): Class<*> {
return backwardsCompatibilityTestClassLoader.loadClass(desc.name)
}
}
val backwardsCompatibilityModel = backwardsCompatibilityObjectInputStream.use { it.readObject() }
assertSame(
backwardsCompatibilityModel.javaClass.classLoader,
backwardsCompatibilityTestClassLoader,
"Expected deserialized model being loaded by 'backwardsCompatibilityTestClassLoader'"
)
assertNotEquals<Class<*>>(
model.javaClass, backwardsCompatibilityModel.javaClass,
"Expected deserialized model java class to be different from origin"
)
assertEquals(
model.javaClass.name, backwardsCompatibilityModel.javaClass.name,
"Expected deserialized model to be same java class as origin"
)
return backwardsCompatibilityModel
}