Add instrumentation to locate not cleared MockApplication
This commit is contained in:
@@ -40,6 +40,16 @@ fun Project.projectTest(taskName: String = "test", body: Test.() -> Unit = {}):
|
||||
}
|
||||
}
|
||||
|
||||
doFirst {
|
||||
val agent = tasks.findByPath(":test-instrumenter:jar")!!.outputs.files.singleFile
|
||||
|
||||
val args = project.findProperty("kotlin.test.instrumentation.args")?.let { "=$it" }.orEmpty()
|
||||
|
||||
jvmArgs("-javaagent:$agent$args")
|
||||
}
|
||||
|
||||
dependsOn(":test-instrumenter:jar")
|
||||
|
||||
jvmArgs("-ea", "-XX:+HeapDumpOnOutOfMemoryError", "-Xmx1100m", "-XX:+UseCodeCacheFlushing", "-XX:ReservedCodeCacheSize=128m", "-Djna.nosys=true")
|
||||
maxHeapSize = "1100m"
|
||||
systemProperty("idea.is.unit.test", "true")
|
||||
|
||||
+5
@@ -40,6 +40,7 @@ import org.jetbrains.annotations.Contract;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.testFramework.MockComponentManagerCreationTracer;
|
||||
import org.jetbrains.kotlin.types.FlexibleTypeImpl;
|
||||
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
|
||||
import org.junit.Assert;
|
||||
@@ -88,6 +89,10 @@ public abstract class KtUsefulTestCase extends TestCase {
|
||||
protected void setUp() throws Exception {
|
||||
application = ApplicationManager.getApplication();
|
||||
|
||||
if (application != null && application.isDisposed()) {
|
||||
MockComponentManagerCreationTracer.diagnoseDisposedButNotClearedApplication(application);
|
||||
}
|
||||
|
||||
super.setUp();
|
||||
|
||||
String testName = FileUtil.sanitizeFileName(getTestName(true));
|
||||
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.testFramework
|
||||
|
||||
import com.intellij.mock.MockComponentManager
|
||||
import com.intellij.openapi.application.Application
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import org.jetbrains.kotlin.test.testFramework.KtUsefulTestCase
|
||||
|
||||
object MockComponentManagerCreationTracer {
|
||||
|
||||
private val creationTraceMap = ContainerUtil.createConcurrentWeakMap<MockComponentManager, Throwable>()
|
||||
|
||||
@JvmStatic
|
||||
fun onCreate(manager: MockComponentManager) {
|
||||
creationTraceMap[manager] = Exception("Creation trace")
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun onGetComponentInstance(manager: MockComponentManager) {
|
||||
if (manager.isDisposed) {
|
||||
val trace = creationTraceMap[manager] ?: return
|
||||
trace.printStackTrace(System.err)
|
||||
}
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun diagnoseDisposedButNotClearedApplication(app: Application) {
|
||||
if (app is MockComponentManager) {
|
||||
KtUsefulTestCase.resetApplicationToNull()
|
||||
throw IllegalStateException("Some test disposed, but forgot to clear MockApplication", creationTraceMap[app])
|
||||
}
|
||||
}
|
||||
}
|
||||
+2
-1
@@ -142,7 +142,8 @@ include ":kotlin-build-common",
|
||||
// plugin markers:
|
||||
':kotlin-gradle-plugin:plugin-marker',
|
||||
':kotlin-allopen:plugin-marker',
|
||||
':kotlin-noarg:plugin-marker'
|
||||
':kotlin-noarg:plugin-marker',
|
||||
":test-instrumenter"
|
||||
|
||||
|
||||
rootProject.name = "kotlin"
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import org.gradle.jvm.tasks.Jar
|
||||
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
plugins { java }
|
||||
apply { plugin("kotlin") }
|
||||
|
||||
|
||||
|
||||
dependencies {
|
||||
compile(ideaSdkCoreDeps("intellij-core"))
|
||||
compile(ideaSdkDeps("asm-all"))
|
||||
compile(project(":kotlin-stdlib"))
|
||||
}
|
||||
|
||||
|
||||
sourceSets {
|
||||
"main" { projectDefault() }
|
||||
}
|
||||
|
||||
tasks {
|
||||
"jar" {
|
||||
this as Jar
|
||||
manifest {
|
||||
attributes["Manifest-Version"] = 1.0
|
||||
attributes["PreMain-Class"] = "org.jetbrains.kotlin.testFramework.TestInstrumentationAgent"
|
||||
}
|
||||
}
|
||||
}
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.testFramework
|
||||
|
||||
import org.jetbrains.org.objectweb.asm.*
|
||||
import org.jetbrains.org.objectweb.asm.util.TraceClassVisitor
|
||||
import java.io.PrintWriter
|
||||
import java.lang.instrument.ClassFileTransformer
|
||||
import java.security.ProtectionDomain
|
||||
|
||||
|
||||
class MockApplicationCreationTracingInstrumenter(private val debugInfo: Boolean) : ClassFileTransformer {
|
||||
|
||||
private fun loadTransformAndSerialize(classfileBuffer: ByteArray, lambda: (out: ClassVisitor) -> ClassVisitor): ByteArray {
|
||||
val reader = ClassReader(classfileBuffer)
|
||||
|
||||
val writer = ClassWriter(ClassWriter.COMPUTE_FRAMES or ClassWriter.COMPUTE_MAXS)
|
||||
|
||||
val pv = if (debugInfo) {
|
||||
TraceClassVisitor(writer, PrintWriter(System.out.writer()))
|
||||
}
|
||||
else {
|
||||
writer
|
||||
}
|
||||
|
||||
reader.accept(lambda(pv), 0)
|
||||
|
||||
return writer.toByteArray()
|
||||
}
|
||||
|
||||
private fun isMockComponentManagerCreationTracerCanBeLoaded(loader: ClassLoader): Boolean =
|
||||
loader.getResource("org/jetbrains/kotlin/testFramework/MockComponentManagerCreationTracer.class") != null
|
||||
|
||||
override fun transform(
|
||||
loader: ClassLoader,
|
||||
className: String,
|
||||
classBeingRedefined: Class<*>?,
|
||||
protectionDomain: ProtectionDomain,
|
||||
classfileBuffer: ByteArray
|
||||
): ByteArray? {
|
||||
if (loader::class.java.name == "org.jetbrains.kotlin.preloading.MemoryBasedClassLoader") return null
|
||||
|
||||
if (className == "com/intellij/mock/MockComponentManager" && isMockComponentManagerCreationTracerCanBeLoaded(loader)) {
|
||||
return loadTransformAndSerialize(classfileBuffer, this::transformMockComponentManager)
|
||||
}
|
||||
else if (className == "com/intellij/mock/MockComponentManager$1" && isMockComponentManagerCreationTracerCanBeLoaded(loader)) {
|
||||
return loadTransformAndSerialize(classfileBuffer, this::transformMockComponentManagerPicoContainer)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
private fun createMethodTransformClassVisitor(
|
||||
out: ClassVisitor,
|
||||
predicate: (name: String, desc: String) -> Boolean,
|
||||
transform: (original: MethodVisitor) -> MethodVisitor
|
||||
): ClassVisitor {
|
||||
return object : ClassVisitor(Opcodes.ASM6, out) {
|
||||
|
||||
var visited = false
|
||||
|
||||
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<out String>?): MethodVisitor {
|
||||
val original = super.visitMethod(access, name, desc, signature, exceptions)
|
||||
return if (predicate(name, desc)) {
|
||||
assert(!visited)
|
||||
visited = true
|
||||
transform(original)
|
||||
}
|
||||
else {
|
||||
original
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitEnd() {
|
||||
super.visitEnd()
|
||||
assert(visited)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun transformMockComponentManagerPicoContainer(out: ClassVisitor): ClassVisitor {
|
||||
return createMethodTransformClassVisitor(out, { name, _ -> name == "getComponentInstance" }) { original ->
|
||||
object : MethodVisitor(Opcodes.ASM6, original) {
|
||||
override fun visitCode() {
|
||||
super.visitCode()
|
||||
visitLabel(Label())
|
||||
visitVarInsn(Opcodes.ALOAD, 0)
|
||||
visitFieldInsn(Opcodes.GETFIELD, "com/intellij/mock/MockComponentManager$1", "this$0", "Lcom/intellij/mock/MockComponentManager;")
|
||||
visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
"org/jetbrains/kotlin/testFramework/MockComponentManagerCreationTracer",
|
||||
"onGetComponentInstance",
|
||||
"(Lcom/intellij/mock/MockComponentManager;)V",
|
||||
false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun transformMockComponentManager(out: ClassVisitor): ClassVisitor {
|
||||
return createMethodTransformClassVisitor(out, { name, _ -> name == "<init>" }) { original ->
|
||||
object : MethodVisitor(Opcodes.ASM6, original) {
|
||||
override fun visitInsn(opcode: Int) {
|
||||
if (opcode == Opcodes.RETURN) {
|
||||
visitVarInsn(Opcodes.ALOAD, 0)
|
||||
visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
"org/jetbrains/kotlin/testFramework/MockComponentManagerCreationTracer",
|
||||
"onCreate",
|
||||
"(Lcom/intellij/mock/MockComponentManager;)V",
|
||||
false
|
||||
)
|
||||
}
|
||||
super.visitInsn(opcode)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.testFramework
|
||||
|
||||
import java.lang.instrument.Instrumentation
|
||||
|
||||
@Suppress("unused")
|
||||
object TestInstrumentationAgent {
|
||||
@JvmStatic
|
||||
fun premain(arg: String?, instrumentation: Instrumentation) {
|
||||
|
||||
val arguments = arg.orEmpty().split(",")
|
||||
|
||||
val debug = "debug" in arguments
|
||||
if (debug) {
|
||||
println("org.jetbrains.kotlin.testFramework.TestInstrumentationAgent: premain")
|
||||
}
|
||||
instrumentation.addTransformer(MockApplicationCreationTracingInstrumenter(debug))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user