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")
|
jvmArgs("-ea", "-XX:+HeapDumpOnOutOfMemoryError", "-Xmx1100m", "-XX:+UseCodeCacheFlushing", "-XX:ReservedCodeCacheSize=128m", "-Djna.nosys=true")
|
||||||
maxHeapSize = "1100m"
|
maxHeapSize = "1100m"
|
||||||
systemProperty("idea.is.unit.test", "true")
|
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.NonNls;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.kotlin.testFramework.MockComponentManagerCreationTracer;
|
||||||
import org.jetbrains.kotlin.types.FlexibleTypeImpl;
|
import org.jetbrains.kotlin.types.FlexibleTypeImpl;
|
||||||
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
|
import org.jetbrains.kotlin.utils.ExceptionUtilsKt;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
@@ -88,6 +89,10 @@ public abstract class KtUsefulTestCase extends TestCase {
|
|||||||
protected void setUp() throws Exception {
|
protected void setUp() throws Exception {
|
||||||
application = ApplicationManager.getApplication();
|
application = ApplicationManager.getApplication();
|
||||||
|
|
||||||
|
if (application != null && application.isDisposed()) {
|
||||||
|
MockComponentManagerCreationTracer.diagnoseDisposedButNotClearedApplication(application);
|
||||||
|
}
|
||||||
|
|
||||||
super.setUp();
|
super.setUp();
|
||||||
|
|
||||||
String testName = FileUtil.sanitizeFileName(getTestName(true));
|
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:
|
// plugin markers:
|
||||||
':kotlin-gradle-plugin:plugin-marker',
|
':kotlin-gradle-plugin:plugin-marker',
|
||||||
':kotlin-allopen:plugin-marker',
|
':kotlin-allopen:plugin-marker',
|
||||||
':kotlin-noarg:plugin-marker'
|
':kotlin-noarg:plugin-marker',
|
||||||
|
":test-instrumenter"
|
||||||
|
|
||||||
|
|
||||||
rootProject.name = "kotlin"
|
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