diff --git a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt index 390162de7ba..f1d9270cedf 100644 --- a/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt +++ b/libraries/tools/kotlin-gradle-plugin-idea/src/test/kotlin/org/jetbrains/kotlin/gradle/kpm/idea/BackwardsCompatibilityDeserializationTest.kt @@ -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().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>( - 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(serializable()) + val unretainedModelKey = KotlinExternalModelKey() + + 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 { .onEach { file -> if (!file.exists()) println("[WARNING] Missing $file") } } +private fun buildModel(project: Project): IdeaKotlinProjectModel { + return project.serviceOf().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>( + 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 +}