[kpm] kgp-idea: Implement serialization test fixtures

^KT-52568 In Progress
This commit is contained in:
sebastian.sellmair
2022-06-13 14:31:16 +02:00
committed by Space
parent ca45200e68
commit b57f5f2742
7 changed files with 210 additions and 0 deletions
@@ -0,0 +1,95 @@
/*
* 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.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.IdeaKpmProject
import org.jetbrains.kotlin.kpm.idea.proto.IdeaKpmProject
import org.jetbrains.kotlin.kpm.idea.proto.toByteArray
import org.jetbrains.kotlin.tooling.core.UnsafeApi
import java.lang.reflect.InvocationHandler
import java.lang.reflect.Method
import java.lang.reflect.Proxy
import kotlin.reflect.full.primaryConstructor
import kotlin.reflect.jvm.javaGetter
import kotlin.reflect.jvm.javaMethod
@OptIn(UnsafeApi::class)
fun TestIdeaKpmClassLoaderProjectSerializer(): TestIdeaKpmClassLoaderProjectSerializer =
TestIdeaKpmProtoClassLoaderProjectSerializer(TestIdeaKpmClassLoaderProjectSerializer::class.java.classLoader)
@OptIn(UnsafeApi::class)
fun TestIdeaKpmClassLoaderProjectSerializer(classLoader: ClassLoader): TestIdeaKpmClassLoaderProjectSerializer {
/*
Instantiates the `TestIdeaKpmProtoClassLoaderProjectSerializer` in the previous version of the classes
(using the specified classLoader). A java proxy will be used to bridge this implementation and the return type interface
*/
val serializerInstance = classLoader.loadClass(TestIdeaKpmProtoClassLoaderProjectSerializer::class.java.name)
.kotlin.primaryConstructor?.call(classLoader) ?: error(
"Failed to construct ${TestIdeaKpmProtoClassLoaderProjectSerializer::class.java.name} in $classLoader"
)
return Proxy.newProxyInstance(
/* loader = */ TestIdeaKpmClassLoaderProjectSerializer::class.java.classLoader,
/* interfaces = */ arrayOf(TestIdeaKpmClassLoaderProjectSerializer::class.java),
/* h = */ ProxyInvocationHandler(classLoader, serializerInstance)
) as TestIdeaKpmClassLoaderProjectSerializer
}
/**
* Test Util to serialize / deserialize [IdeaKpmProject] within a dedicated ClassLoader.
* The serialization context used will be [TestIdeaKpmSerializationContext]. Note, that this context
* might also depend on the version shipped by the specified [ClassLoader].
*/
interface TestIdeaKpmClassLoaderProjectSerializer {
val classLoader: ClassLoader
val reports: List<TestIdeaKpmSerializationLogger.Report>
fun serialize(project: Any): ByteArray
fun deserialize(data: ByteArray): Any?
}
@UnsafeApi
internal class TestIdeaKpmProtoClassLoaderProjectSerializer(
override val classLoader: ClassLoader
) : TestIdeaKpmClassLoaderProjectSerializer {
private val context = TestIdeaKpmSerializationContext()
override val reports: List<TestIdeaKpmSerializationLogger.Report>
get() = context.logger.reports
override fun serialize(project: Any): ByteArray {
return (project as IdeaKpmProject).toByteArray(context)
}
override fun deserialize(data: ByteArray): Any? {
return context.IdeaKpmProject(data)
}
}
private class ProxyInvocationHandler(
private val classLoader: ClassLoader,
private val serializerInstance: Any
) : InvocationHandler {
override fun invoke(proxy: Any, method: Method, args: Array<out Any>?): Any? {
if (method == TestIdeaKpmClassLoaderProjectSerializer::classLoader.getter.javaMethod) {
return classLoader
}
val targetMethod = serializerInstance.javaClass.methods.find { it.name == method.name } ?: error("Missing $method")
val result = targetMethod.invoke(serializerInstance, *args.orEmpty())
/*
The result objects here are also part of the test-fixtures, which will have different classes, depending on the
ClassLoader being used. The reports here, will be copied (serialized and then deserialized in this ClassLoader).
*/
if (method == TestIdeaKpmClassLoaderProjectSerializer::reports.javaGetter) {
/* Copy into 'our' ClassLoader */
return result?.copy()
}
return result
}
}
@@ -0,0 +1,10 @@
/*
* 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.kpm.idea.testFixtures
import java.io.Serializable
data class TestIdeaKpmExtra(val id: Any) : Serializable
@@ -0,0 +1,27 @@
/*
* 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.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.serialize.IdeaKpmExtrasSerializationExtension
import org.jetbrains.kotlin.gradle.kpm.idea.serialize.IdeaKpmExtrasSerializer
import org.jetbrains.kotlin.tooling.core.Extras
import org.jetbrains.kotlin.tooling.core.Type
import org.jetbrains.kotlin.tooling.core.extrasKeyOf
@Suppress("UNCHECKED_CAST")
object TestIdeaKpmExtrasSerializationExtension : IdeaKpmExtrasSerializationExtension {
val ignoredStringKey = extrasKeyOf<String>("ignored")
val anySerializableKey = extrasKeyOf<Any>("serializable")
override fun <T : Any> serializer(key: Extras.Key<T>): IdeaKpmExtrasSerializer<T>? = when {
key == ignoredStringKey -> null
key == anySerializableKey -> IdeaKpmExtrasSerializer.javaIoSerializable<Any>()
key.type == Type<String>() -> TestIdeaKpmStringExtrasSerializer
key.type == Type<Int>() -> TestIdeaKpmIntExtrasSerializer
else -> null
} as? IdeaKpmExtrasSerializer<T>
}
@@ -0,0 +1,20 @@
/*
* 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.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.serialize.IdeaKpmExtrasSerializer
import org.jetbrains.kotlin.gradle.kpm.idea.serialize.IdeaKpmSerializationContext
import java.nio.ByteBuffer
object TestIdeaKpmIntExtrasSerializer : IdeaKpmExtrasSerializer<Int> {
override fun serialize(context: IdeaKpmSerializationContext, value: Int): ByteArray {
return ByteBuffer.allocate(Int.SIZE_BYTES).putInt(value).array()
}
override fun deserialize(context: IdeaKpmSerializationContext, data: ByteArray): Int {
return ByteBuffer.wrap(data).int
}
}
@@ -0,0 +1,13 @@
/*
* 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.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.serialize.IdeaKpmSerializationContext
class TestIdeaKpmSerializationContext : IdeaKpmSerializationContext {
override val logger = TestIdeaKpmSerializationLogger()
override val extrasSerializationExtension = TestIdeaKpmExtrasSerializationExtension
}
@@ -0,0 +1,29 @@
/*
* 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.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.serialize.IdeaKpmSerializationLogger
import org.jetbrains.kotlin.gradle.kpm.idea.serialize.IdeaKpmSerializationLogger.Severity
import java.io.Serializable
import kotlin.collections.toList
class TestIdeaKpmSerializationLogger : IdeaKpmSerializationLogger {
data class Report(
val severity: Severity,
val message: String,
val cause: Throwable? = null
) : Serializable {
override fun toString(): String = "[${severity.name}]: $message"
}
private val _reports = mutableListOf<Report>()
val reports get() = _reports.toList()
override fun report(severity: Severity, message: String, cause: Throwable?) {
_reports.add(Report(severity, message, cause))
}
}
@@ -0,0 +1,16 @@
/*
* 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.kpm.idea.testFixtures
import org.jetbrains.kotlin.gradle.kpm.idea.serialize.IdeaKpmExtrasSerializer
import org.jetbrains.kotlin.gradle.kpm.idea.serialize.IdeaKpmSerializationContext
import kotlin.text.decodeToString
import kotlin.text.encodeToByteArray
object TestIdeaKpmStringExtrasSerializer : IdeaKpmExtrasSerializer<String> {
override fun serialize(context: IdeaKpmSerializationContext, value: String): ByteArray = value.encodeToByteArray()
override fun deserialize(context: IdeaKpmSerializationContext, data: ByteArray) = data.decodeToString()
}