From 5641909d1d59b775c0b8caa0c7ca36b701b017f6 Mon Sep 17 00:00:00 2001 From: Yan Zhulanow Date: Thu, 27 Jul 2017 21:02:01 +0300 Subject: [PATCH] Parcel: Invoke box tests as an external process to prevent class clashes with IntelliJ platform --- .idea/libraries/robolectric.xml | 48 ++---- .../android/parcel/AbstractParcelBoxTest.kt | 146 ++++++++++++++---- .../kotlin/android/parcel/ParcelBoxTest.kt | 8 - update_dependencies.xml | 5 +- 4 files changed, 137 insertions(+), 70 deletions(-) diff --git a/.idea/libraries/robolectric.xml b/.idea/libraries/robolectric.xml index 768c8e5b709..e38e94b81fd 100644 --- a/.idea/libraries/robolectric.xml +++ b/.idea/libraries/robolectric.xml @@ -2,58 +2,40 @@ + + + - - + - - + + - - - - - - - - - - - - - - - - - - - - - + + + + + - - - - - + - + - - + + \ No newline at end of file diff --git a/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/AbstractParcelBoxTest.kt b/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/AbstractParcelBoxTest.kt index ee5d46dee1b..50b754fee4b 100644 --- a/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/AbstractParcelBoxTest.kt +++ b/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/AbstractParcelBoxTest.kt @@ -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, "", "()V", null, null)) { + visitVarInsn(ALOAD, 0) + visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()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, "", "(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 { + 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, 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") + } } } diff --git a/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/ParcelBoxTest.kt b/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/ParcelBoxTest.kt index 1b3827c4a25..4950eabf616 100644 --- a/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/ParcelBoxTest.kt +++ b/plugins/plugins-tests/tests/org/jetbrains/kotlin/android/parcel/ParcelBoxTest.kt @@ -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") diff --git a/update_dependencies.xml b/update_dependencies.xml index 05206734c41..93b63fd8c02 100644 --- a/update_dependencies.xml +++ b/update_dependencies.xml @@ -247,9 +247,12 @@ + +