Parcel: Invoke box tests as an external process to prevent class clashes with IntelliJ platform
This commit is contained in:
committed by
Yan Zhulanow
parent
11b6382518
commit
5641909d1d
+118
-28
@@ -22,52 +22,142 @@ import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
|
||||
import org.jetbrains.kotlin.cli.jvm.config.JvmClasspathRoot
|
||||
import org.jetbrains.kotlin.codegen.CodegenTestCase
|
||||
import org.jetbrains.kotlin.codegen.getClassFiles
|
||||
import org.jetbrains.org.objectweb.asm.ClassReader
|
||||
import org.jetbrains.org.objectweb.asm.tree.ClassNode
|
||||
import org.jetbrains.kotlin.utils.PathUtil
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES
|
||||
import org.jetbrains.org.objectweb.asm.ClassWriter.COMPUTE_MAXS
|
||||
import org.jetbrains.org.objectweb.asm.Label
|
||||
import org.jetbrains.org.objectweb.asm.Opcodes.*
|
||||
import org.jetbrains.org.objectweb.asm.Type
|
||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||
import org.junit.runner.JUnitCore
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.ByteBuffer
|
||||
import java.security.ProtectionDomain
|
||||
|
||||
import java.nio.file.Files
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
abstract class AbstractParcelBoxTest : CodegenTestCase() {
|
||||
protected companion object {
|
||||
val BASE_DIR = "plugins/android-extensions/android-extensions-compiler/testData/parcel/box"
|
||||
val LIBRARY_KT = File(File(BASE_DIR).parentFile, "boxLib.kt")
|
||||
|
||||
private val JUNIT_GENERATED_TEST_CLASS_BYTES by lazy { constructSyntheticTestClass() }
|
||||
private val JUNIT_GENERATED_TEST_CLASS_FQNAME = "test.JunitTest"
|
||||
|
||||
private fun constructSyntheticTestClass(): ByteArray {
|
||||
return with(ClassWriter(COMPUTE_MAXS or COMPUTE_FRAMES)) {
|
||||
visit(49, ACC_PUBLIC, JUNIT_GENERATED_TEST_CLASS_FQNAME.replace('.', '/'), null, "java/lang/Object", emptyArray())
|
||||
visitSource(null, null)
|
||||
|
||||
with(visitAnnotation("Lorg/junit/runner/RunWith;", true)) {
|
||||
visit("value", Type.getType("Lorg/robolectric/RobolectricTestRunner;"))
|
||||
visitEnd()
|
||||
}
|
||||
|
||||
with(visitAnnotation("Lorg/robolectric/annotation/Config;", true)) {
|
||||
visit("manifest", "--none")
|
||||
visitEnd()
|
||||
}
|
||||
|
||||
with(visitMethod(ACC_PUBLIC, "<init>", "()V", null, null)) {
|
||||
visitVarInsn(ALOAD, 0)
|
||||
visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
|
||||
|
||||
visitInsn(RETURN)
|
||||
visitMaxs(-1, -1)
|
||||
visitEnd()
|
||||
}
|
||||
|
||||
with(visitMethod(ACC_PUBLIC, "test", "()V", null, null)) {
|
||||
visitAnnotation("Lorg/junit/Test;", true).visitEnd()
|
||||
|
||||
val v = InstructionAdapter(this)
|
||||
|
||||
val assertionOk = Label()
|
||||
|
||||
v.invokestatic("test/TestKt", "box", "()Ljava/lang/String;", false) // -> ret
|
||||
v.dup() // -> ret, ret
|
||||
v.aconst("OK") // -> ret, ret, ok
|
||||
v.invokevirtual("java/lang/String", "equals", "(Ljava/lang/Object;)Z", false) // -> ret, eq
|
||||
v.ifne(assertionOk) // -> ret
|
||||
|
||||
val assertionErrorType = Type.getObjectType("java/lang/AssertionError")
|
||||
|
||||
v.anew(assertionErrorType) // -> ret, ae
|
||||
v.dupX1() // -> ae, ret, ae
|
||||
v.swap() // -> ae, ae, ret
|
||||
v.invokespecial(assertionErrorType.internalName, "<init>", "(Ljava/lang/Object;)V", false) // -> ae
|
||||
v.athrow()
|
||||
|
||||
v.visitLabel(assertionOk)
|
||||
v.pop() // -> [empty]
|
||||
v.areturn(Type.VOID_TYPE)
|
||||
|
||||
visitMaxs(-1, -1)
|
||||
visitEnd()
|
||||
}
|
||||
|
||||
visitEnd()
|
||||
toByteArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun doTest(filePath: String) {
|
||||
super.doTest(File(BASE_DIR, filePath + ".kt").absolutePath)
|
||||
}
|
||||
|
||||
open protected fun getClassLoaderWithGeneratedFiles(): ClassLoader {
|
||||
return object : URLClassLoader(arrayOf(), this::class.java.classLoader) {
|
||||
init {
|
||||
for (classFile in classFileFactory.getClassFiles().sortedBy { it.relativePath }) {
|
||||
val bytes = classFile.asByteArray()
|
||||
val className = ClassNode().also { ClassReader(bytes).accept(it, ClassReader.EXPAND_FRAMES) }.name
|
||||
try {
|
||||
defineClass(className.replace('/', '.'), ByteBuffer.wrap(bytes), null as ProtectionDomain?)
|
||||
} catch (e: Throwable) {
|
||||
throw RuntimeException("Can't load class $className", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun getClasspathForTest(): List<File> {
|
||||
val kotlinRuntimeJar = PathUtil.kotlinPathsForIdeaPlugin.stdlibPath
|
||||
val layoutLibJars = listOf(File("ideaSDK/plugins/android/lib/layoutlib.jar"), File("ideaSDK/plugins/android/lib/layoutlib-api.jar"))
|
||||
|
||||
val robolectricJars = File("dependencies/robolectric")
|
||||
.listFiles { f: File -> f.extension == "jar" }
|
||||
.sortedBy { it.nameWithoutExtension }
|
||||
|
||||
val junitCoreResourceName = JUnitCore::class.java.name.replace('.', '/') + ".class"
|
||||
val junitJar = File(JUnitCore::class.java.classLoader.getResource(junitCoreResourceName).file.substringBeforeLast('!'))
|
||||
|
||||
val androidExtensionsRuntime = File("out/production/android-extensions-runtime")
|
||||
|
||||
return listOf(kotlinRuntimeJar) + layoutLibJars + robolectricJars + junitJar + androidExtensionsRuntime
|
||||
}
|
||||
|
||||
override fun doMultiFileTest(wholeFile: File, files: List<TestFile>, javaFilesDir: File?) {
|
||||
if (true) { // TODO Parcelable box tests are disabled because of OOM. Currently investigating
|
||||
return
|
||||
}
|
||||
|
||||
compile(files + TestFile(LIBRARY_KT.name, LIBRARY_KT.readText()), javaFilesDir)
|
||||
|
||||
val testClass = Class.forName("test.TestKt", false, getClassLoaderWithGeneratedFiles())
|
||||
val javaBin = File(System.getProperty("java.home").takeIf { it.isNotEmpty() } ?: error("JAVA_HOME is not set"), "bin")
|
||||
val javaExe = File(javaBin, "java.exe").takeIf { it.exists() } ?: File(javaBin, "java")
|
||||
assert(javaExe.exists()) { "Can't find 'java' executable in $javaBin" }
|
||||
|
||||
val libraryClasspath = getClasspathForTest()
|
||||
val dirForTestClasses = Files.createTempDirectory("parcel").toFile()
|
||||
|
||||
fun writeClass(fqNameOrPath: String, bytes: ByteArray) {
|
||||
val path = if (fqNameOrPath.endsWith(".class")) fqNameOrPath else (fqNameOrPath.replace('.', '/') + ".class")
|
||||
File(dirForTestClasses, path).also { it.parentFile.mkdirs() }.writeBytes(bytes)
|
||||
}
|
||||
|
||||
try {
|
||||
testClass.getDeclaredMethod("box").invoke(testClass)
|
||||
} catch (e: Throwable) {
|
||||
throw AssertionError(classFileFactory.createText(), e)
|
||||
writeClass(JUNIT_GENERATED_TEST_CLASS_FQNAME, JUNIT_GENERATED_TEST_CLASS_BYTES)
|
||||
classFileFactory.getClassFiles().forEach { writeClass(it.relativePath, it.asByteArray()) }
|
||||
|
||||
val process = ProcessBuilder(
|
||||
javaExe.absolutePath,
|
||||
"-ea",
|
||||
"-classpath",
|
||||
(libraryClasspath + dirForTestClasses).joinToString(File.pathSeparator),
|
||||
JUnitCore::class.java.name,
|
||||
JUNIT_GENERATED_TEST_CLASS_FQNAME
|
||||
).inheritIO().start()
|
||||
|
||||
process.waitFor(3, TimeUnit.MINUTES)
|
||||
if (process.exitValue() != 0) {
|
||||
throw AssertionError(classFileFactory.createText())
|
||||
}
|
||||
} finally {
|
||||
if (!dirForTestClasses.deleteRecursively()) {
|
||||
throw AssertionError("Unable to delete $dirForTestClasses")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,14 +16,6 @@
|
||||
|
||||
package org.jetbrains.kotlin.android.parcel
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
//import org.robolectric.RobolectricTestRunner
|
||||
//import org.robolectric.annotation.Config
|
||||
|
||||
// This class is not generated because it uses the custom test runner
|
||||
//@RunWith(RobolectricTestRunner::class)
|
||||
//@Config(manifest = Config.NONE)
|
||||
class ParcelBoxTest : AbstractParcelBoxTest() {
|
||||
fun testSimple() = doTest("simple")
|
||||
fun testPrimitiveTypes() = doTest("primitiveTypes")
|
||||
|
||||
Reference in New Issue
Block a user