mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-06 03:21:12 +07:00
[kotlin junit5] performance improvement IDEA-370050 IDEA-372531 IJ-CR-162315
GitOrigin-RevId: 4889a63228b0db3cfe35052b8a872d3ceeaa6497
This commit is contained in:
committed by
intellij-monorepo-bot
parent
a7b9a1e647
commit
e525c8afd3
@@ -74,8 +74,8 @@ public abstract class MetaAnnotationUtil {
|
||||
}
|
||||
|
||||
private static @NotNull @Unmodifiable Collection<PsiClass> findAnnotationClasses(@NotNull Module module,
|
||||
@NotNull String qualifiedName,
|
||||
boolean includeTests) {
|
||||
@NotNull String qualifiedName,
|
||||
boolean includeTests) {
|
||||
PsiClass annotationClass = JavaPsiFacade.getInstance(module.getProject())
|
||||
.findClass(qualifiedName, GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module));
|
||||
if (annotationClass == null || !annotationClass.isAnnotationType()) {
|
||||
@@ -198,7 +198,7 @@ public abstract class MetaAnnotationUtil {
|
||||
}
|
||||
|
||||
private static @NotNull @Unmodifiable Collection<PsiClass> findAnnotationTypesWithChildren(Collection<PsiClass> annotationClasses,
|
||||
GlobalSearchScope scope) {
|
||||
GlobalSearchScope scope) {
|
||||
if (scope == GlobalSearchScope.EMPTY_SCOPE) return annotationClasses;
|
||||
|
||||
Set<PsiClass> classes = CollectionFactory.createCustomHashingStrategySet(HASHING_STRATEGY);
|
||||
@@ -335,23 +335,23 @@ public abstract class MetaAnnotationUtil {
|
||||
stack.push(aClass);
|
||||
|
||||
while (!stack.isEmpty()) {
|
||||
PsiClass currentClass = stack.pop();
|
||||
|
||||
PsiAnnotation directAnnotation = AnnotationUtil.findAnnotation(currentClass, true, annotation);
|
||||
if (directAnnotation != null) {
|
||||
return directAnnotation;
|
||||
}
|
||||
PsiClass currentClass = stack.pop();
|
||||
|
||||
List<PsiClass> resolvedAnnotations = getResolvedClassesInAnnotationsList(currentClass);
|
||||
for (PsiClass resolvedAnnotation : resolvedAnnotations) {
|
||||
if (visited.add(resolvedAnnotation)) {
|
||||
stack.push(resolvedAnnotation);
|
||||
}
|
||||
PsiAnnotation directAnnotation = AnnotationUtil.findAnnotation(currentClass, true, annotation);
|
||||
if (directAnnotation != null) {
|
||||
return directAnnotation;
|
||||
}
|
||||
|
||||
List<PsiClass> resolvedAnnotations = getResolvedClassesInAnnotationsList(currentClass);
|
||||
for (PsiClass resolvedAnnotation : resolvedAnnotations) {
|
||||
if (visited.add(resolvedAnnotation)) {
|
||||
stack.push(resolvedAnnotation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static @NotNull Stream<PsiAnnotation> findMetaAnnotations(@NotNull PsiModifierListOwner listOwner,
|
||||
@NotNull Collection<String> annotations) {
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.kotlin.idea.testIntegration.framework
|
||||
|
||||
import com.intellij.codeInsight.MetaAnnotationUtil
|
||||
import com.intellij.java.library.JavaLibraryUtil
|
||||
import com.intellij.psi.impl.java.stubs.index.JavaFullClassNameIndex
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.util.Processor
|
||||
import com.intellij.util.ThreeState
|
||||
import com.intellij.util.ThreeState.*
|
||||
import org.jetbrains.kotlin.asJava.toLightClass
|
||||
import org.jetbrains.kotlin.idea.base.util.module
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
|
||||
import org.jetbrains.kotlin.idea.stubindex.KotlinTopLevelTypeAliasFqNameIndex
|
||||
@@ -80,28 +84,51 @@ abstract class AbstractKotlinPsiBasedTestFramework : KotlinPsiBasedTestFramework
|
||||
|| isAnnotated(declaration, disabledTestAnnotation)) && isTestMethod(declaration)
|
||||
}
|
||||
|
||||
protected fun checkNameMatch(file: KtFile, fqNames: Set<String>, shortName: String): Boolean {
|
||||
if (shortName in fqNames || "${file.packageFqName}.$shortName" in fqNames) return true
|
||||
protected fun checkNameMatch(file: KtFile, fqNames: Set<String>, shortName: String): Boolean =
|
||||
fqNames.intersect(findFqNameCandidates(file, shortName)).isNotEmpty()
|
||||
|
||||
private fun findFqNameCandidates(file: KtFile, shortName: String): Set<String> {
|
||||
val outer = shortName.substringBefore('.')
|
||||
val inner = shortName.substringAfter('.', "")
|
||||
fun append(innerPart: String?) = if (innerPart.isNullOrEmpty()) "" else ".$innerPart"
|
||||
|
||||
// direct import
|
||||
for (importDirective in file.importDirectives) {
|
||||
if (!importDirective.isValidImport) {
|
||||
continue
|
||||
}
|
||||
|
||||
if (!importDirective.isValidImport) continue
|
||||
val importedFqName = importDirective.importedFqName?.asString() ?: continue
|
||||
|
||||
if (!importDirective.isAllUnder) {
|
||||
if (importDirective.aliasName == shortName && importedFqName in fqNames) {
|
||||
return true
|
||||
} else if (importedFqName in fqNames && importedFqName.endsWith(".$shortName")) {
|
||||
return true
|
||||
}
|
||||
} else if ("$importedFqName.$shortName" in fqNames) {
|
||||
return true
|
||||
when {
|
||||
importDirective.aliasName == shortName -> return setOf(importedFqName)
|
||||
importedFqName.endsWith(".$shortName") -> return setOf(importedFqName)
|
||||
importDirective.aliasName == outer -> return setOf(importedFqName + append(inner))
|
||||
importedFqName.endsWith(".$outer") -> return setOf(importedFqName + append(inner))
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
val candidates = mutableSetOf<String>()
|
||||
|
||||
// in the current file
|
||||
PsiTreeUtil.findChildrenOfType(file, KtClassOrObject::class.java)
|
||||
.firstOrNull { it.name == outer }
|
||||
?.fqName?.asString()
|
||||
?.let { candidates.add(it + append(inner)) }
|
||||
|
||||
// same package
|
||||
if (file.packageFqName.isRoot) {
|
||||
candidates.add(shortName)
|
||||
} else {
|
||||
candidates.add("${file.packageFqName}.$shortName")
|
||||
}
|
||||
|
||||
// demand imports
|
||||
for (importDirective in file.importDirectives) {
|
||||
if (!importDirective.isValidImport) continue
|
||||
if (!importDirective.isAllUnder) continue
|
||||
val importedFqName = importDirective.importedFqName?.asString() ?: continue
|
||||
candidates.add("$importedFqName.$shortName")
|
||||
}
|
||||
|
||||
return candidates
|
||||
}
|
||||
|
||||
protected fun isAnnotated(element: KtAnnotated, fqName: String): Boolean {
|
||||
@@ -128,6 +155,24 @@ abstract class AbstractKotlinPsiBasedTestFramework : KotlinPsiBasedTestFramework
|
||||
}
|
||||
}
|
||||
|
||||
for (annotationEntry in annotationEntries) {
|
||||
val shortName = annotationEntry.shortName ?: continue
|
||||
val fqName = annotationEntry.typeReference?.text ?: shortName.asString()
|
||||
|
||||
val clazz = PsiTreeUtil.findChildrenOfType(file, KtClassOrObject::class.java)
|
||||
.firstOrNull { it.fqName?.asString()?.endsWith(".${fqName}") == true || it.fqName?.asString() == fqName }
|
||||
?.toLightClass()
|
||||
if (clazz != null && MetaAnnotationUtil.isMetaAnnotated(clazz, fqNames)) return annotationEntry
|
||||
|
||||
for (fq in findFqNameCandidates(file, fqName)) {
|
||||
val classes = JavaFullClassNameIndex.getInstance().getClasses(fq, element.project, element.resolveScope)
|
||||
for (cls in classes) {
|
||||
if (MetaAnnotationUtil.isMetaAnnotated(cls, fqNames)) {
|
||||
return annotationEntry
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.kotlin.idea.k2.codeInsight.codeVision
|
||||
|
||||
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
|
||||
import org.jetbrains.kotlin.idea.run.KotlinJUnitLightTest
|
||||
|
||||
class K2KotlinJUnitLightTest : KotlinJUnitLightTest() {
|
||||
override val pluginMode: KotlinPluginMode
|
||||
get() = KotlinPluginMode.K2
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.kotlin.idea.run
|
||||
|
||||
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
|
||||
|
||||
class K1KotlinJUnitLightTest : KotlinJUnitLightTest() {
|
||||
override val pluginMode: KotlinPluginMode
|
||||
get() = KotlinPluginMode.K1
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package org.jetbrains.kotlin.idea.run
|
||||
|
||||
import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection
|
||||
@@ -29,14 +29,18 @@ import com.intellij.psi.PsiClassOwner
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.psi.util.PsiTreeUtil
|
||||
import com.intellij.testFramework.TestActionEvent
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase
|
||||
import com.intellij.util.ThreeState
|
||||
import org.jetbrains.kotlin.asJava.toLightMethods
|
||||
import org.jetbrains.kotlin.idea.base.plugin.KotlinPluginMode
|
||||
import org.jetbrains.kotlin.idea.junit.JunitKotlinTestFrameworkProvider
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCaseBase
|
||||
import org.jetbrains.kotlin.psi.KtFunction
|
||||
import org.junit.Assert
|
||||
import org.junit.internal.runners.JUnit38ClassRunner
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
class KotlinJUnitLightTest : LightJavaCodeInsightFixtureTestCase() {
|
||||
@RunWith(JUnit38ClassRunner::class)
|
||||
abstract class KotlinJUnitLightTest : KotlinLightCodeInsightFixtureTestCaseBase() {
|
||||
private val tempSettings: MutableSet<RunnerAndConfigurationSettings> = HashSet()
|
||||
|
||||
@Throws(Exception::class)
|
||||
@@ -52,7 +56,7 @@ class KotlinJUnitLightTest : LightJavaCodeInsightFixtureTestCase() {
|
||||
super.tearDown()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun setUp() {
|
||||
super.setUp()
|
||||
myFixture.addClass("package junit.framework; public class TestCase {}")
|
||||
@@ -60,6 +64,7 @@ class KotlinJUnitLightTest : LightJavaCodeInsightFixtureTestCase() {
|
||||
myFixture.addClass("package org.junit.platform.commons.annotation; public @interface Testable{}")
|
||||
myFixture.addClass("package org.junit.jupiter.api; import org.junit.platform.commons.annotation.Testable; @Testable public @interface Test {}")
|
||||
myFixture.addClass("package org.junit.jupiter.api; public @interface Nested {}")
|
||||
myFixture.addClass("package org.junit.jupiter.api; public @interface BeforeEach {}")
|
||||
}
|
||||
|
||||
fun testAvailableInsideAnonymous() {
|
||||
@@ -179,7 +184,7 @@ class KotlinJUnitLightTest : LightJavaCodeInsightFixtureTestCase() {
|
||||
val settings = RunnerAndConfigurationSettingsImpl(manager as RunManagerImpl, test)
|
||||
manager.addConfiguration(settings)
|
||||
tempSettings.add(settings)
|
||||
|
||||
|
||||
val element = file.findElementAt(myFixture.caretOffset)!!
|
||||
|
||||
val location = PsiLocation(element)
|
||||
@@ -208,7 +213,7 @@ class KotlinJUnitLightTest : LightJavaCodeInsightFixtureTestCase() {
|
||||
tempSettings.add(settings)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun doTestClassWithMain(setupExisting: Runnable?) {
|
||||
myFixture.configureByText(
|
||||
"ATest.kt", """import org.junit.Test
|
||||
@@ -245,7 +250,7 @@ fun main(args: Array<String>) {}
|
||||
|
||||
fun testStackTraceParserAcceptsJavaStacktrace() {
|
||||
myFixture.configureByText("tests.kt",
|
||||
"""class tests : junit.framework.TestCase() {
|
||||
"""class tests : junit.framework.TestCase() {
|
||||
fun testMe() {
|
||||
doTest {
|
||||
assertTrue(false)
|
||||
@@ -289,6 +294,8 @@ fun main(args: Array<String>) {}
|
||||
}
|
||||
|
||||
fun `test unused beforeAll`() {
|
||||
if (pluginMode == KotlinPluginMode.K2) return
|
||||
|
||||
myFixture.addClass("package org.junit.jupiter.api; public @interface BeforeAll{}")
|
||||
myFixture.addClass("package kotlin.jvm; public @interface JvmStatic{}")
|
||||
myFixture.configureByText("Demo.kt", """
|
||||
@@ -306,4 +313,142 @@ class DemoTest {
|
||||
val ktFunction = PsiTreeUtil.getParentOfType(myFixture.elementAtCaret, KtFunction::class.java, false)
|
||||
assertTrue(UnusedDeclarationInspection().isEntryPoint(ktFunction!!.toLightMethods()[0]))
|
||||
}
|
||||
|
||||
fun testMethodWithTestAnnotation() {
|
||||
val file = myFixture.configureByText(
|
||||
"MyTest.kt", """
|
||||
class MyTest {
|
||||
@org.junit.jupiter.api.Test
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
annotation class TestAnnotation
|
||||
|
||||
@TestAnnotation
|
||||
fun `dispatch <caret>thread`() {}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val gutters = myFixture.findGuttersAtCaret()
|
||||
Assert.assertTrue("Test method with meta-annotation should have a gutter icon", gutters.isNotEmpty())
|
||||
|
||||
val element = file.findElementAt(myFixture.caretOffset)!!
|
||||
val location = PsiLocation(element)
|
||||
val context = ConfigurationContext.createEmptyContextForLocation(location)
|
||||
val contexts = context.configurationsFromContext
|
||||
|
||||
Assert.assertEquals(1, contexts?.size ?: 0)
|
||||
val fromContext = contexts?.get(0)
|
||||
Assert.assertTrue(fromContext?.configuration is JUnitConfiguration)
|
||||
val configuration = fromContext?.configuration as JUnitConfiguration
|
||||
|
||||
Assert.assertEquals("MyTest.dispatch thread", configuration.name)
|
||||
|
||||
val testObject = configuration.persistentData.TEST_OBJECT
|
||||
Assert.assertEquals(
|
||||
"Method should be suggested to run, but $testObject was used instead",
|
||||
JUnitConfiguration.TEST_METHOD,
|
||||
testObject
|
||||
)
|
||||
|
||||
Assert.assertNotNull(JunitKotlinTestFrameworkProvider.getInstance().getJavaTestEntity(element, checkMethod = true))
|
||||
}
|
||||
|
||||
fun testMethodWithTestAnnotationAndBeforeEach() {
|
||||
myFixture.addClass("""
|
||||
package c;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
@org.junit.jupiter.api.Test
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface TestAnnotation {
|
||||
}
|
||||
""")
|
||||
|
||||
val file = myFixture.configureByText(
|
||||
"MyTest.kt", """
|
||||
import c.*
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
|
||||
class MyTest {
|
||||
@BeforeEach
|
||||
fun cleanEDTQueue() {}
|
||||
|
||||
@TestAnnotation
|
||||
fun `dispatch <caret>thread`() {}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val gutters = myFixture.findGuttersAtCaret()
|
||||
Assert.assertTrue("Test method with meta-annotation should have a gutter icon", gutters.isNotEmpty())
|
||||
|
||||
val element = file.findElementAt(myFixture.caretOffset)!!
|
||||
val location = PsiLocation(element)
|
||||
val context = ConfigurationContext.createEmptyContextForLocation(location)
|
||||
val contexts = context.configurationsFromContext
|
||||
|
||||
Assert.assertEquals(1, contexts?.size ?: 0)
|
||||
val fromContext = contexts?.get(0)
|
||||
Assert.assertTrue(fromContext?.configuration is JUnitConfiguration)
|
||||
val configuration = fromContext?.configuration as JUnitConfiguration
|
||||
|
||||
Assert.assertEquals("MyTest.dispatch thread", configuration.name)
|
||||
|
||||
val testObject = configuration.persistentData.TEST_OBJECT
|
||||
Assert.assertEquals(
|
||||
"Method should be suggested to run, but $testObject was used instead",
|
||||
JUnitConfiguration.TEST_METHOD,
|
||||
testObject
|
||||
)
|
||||
Assert.assertNotNull(JunitKotlinTestFrameworkProvider.getInstance().getJavaTestEntity(element, checkMethod = true))
|
||||
}
|
||||
|
||||
fun testMethodWithInnerTestAnnotationAndBeforeEach() {
|
||||
val file = myFixture.configureByText(
|
||||
"MyTest.kt", """
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
|
||||
class MyTest {
|
||||
@BeforeEach
|
||||
fun cleanEDTQueue() {}
|
||||
|
||||
@Inner.Additional.TestAnnotation
|
||||
fun `dispatch <caret>thread`() {}
|
||||
|
||||
class Inner {
|
||||
class Additional {
|
||||
@Test
|
||||
@kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.RUNTIME)
|
||||
annotation class TestAnnotation
|
||||
}
|
||||
}
|
||||
}
|
||||
""".trimIndent()
|
||||
)
|
||||
|
||||
val gutters = myFixture.findGuttersAtCaret()
|
||||
Assert.assertTrue("Test method with meta-annotation should have a gutter icon", gutters.isNotEmpty())
|
||||
|
||||
val element = file.findElementAt(myFixture.caretOffset)!!
|
||||
val location = PsiLocation(element)
|
||||
val context = ConfigurationContext.createEmptyContextForLocation(location)
|
||||
val contexts = context.configurationsFromContext
|
||||
|
||||
Assert.assertEquals(1, contexts?.size ?: 0)
|
||||
val fromContext = contexts?.get(0)
|
||||
Assert.assertTrue(fromContext?.configuration is JUnitConfiguration)
|
||||
val configuration = fromContext?.configuration as JUnitConfiguration
|
||||
|
||||
Assert.assertEquals("MyTest.dispatch thread", configuration.name)
|
||||
|
||||
val testObject = configuration.persistentData.TEST_OBJECT
|
||||
Assert.assertEquals(
|
||||
"Method should be suggested to run, but $testObject was used instead",
|
||||
JUnitConfiguration.TEST_METHOD,
|
||||
testObject
|
||||
)
|
||||
Assert.assertNotNull(JunitKotlinTestFrameworkProvider.getInstance().getJavaTestEntity(element, checkMethod = true))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user