diff --git a/plugins/junit/java-tests/test/com/intellij/execution/junit/codeInspection/JavaJUnitMalformedDeclarationInspectionTest.kt b/plugins/junit/java-tests/test/com/intellij/execution/junit/codeInspection/JavaJUnitMalformedDeclarationInspectionTest.kt index d073eeb7837a..60c1cca6f2b8 100644 --- a/plugins/junit/java-tests/test/com/intellij/execution/junit/codeInspection/JavaJUnitMalformedDeclarationInspectionTest.kt +++ b/plugins/junit/java-tests/test/com/intellij/execution/junit/codeInspection/JavaJUnitMalformedDeclarationInspectionTest.kt @@ -669,6 +669,10 @@ class JavaJUnitMalformedDeclarationInspectionTest { @org.junit.jupiter.params.ParameterizedTest @org.junit.jupiter.params.provider.MethodSource("intStreamProvider") void injectTestReporter(int x, org.junit.jupiter.api.TestReporter testReporter) { System.out.println(x); } + + @org.junit.jupiter.params.ParameterizedTest + @org.junit.jupiter.params.provider.MethodSource("intStreamProvider") + void intStreamProvider(int x, org.junit.jupiter.api.TestReporter testReporter) { System.out.println(x); } static java.util.stream.Stream stream() { return null; } static java.util.Iterator iterator() { return null; } @@ -705,6 +709,44 @@ class JavaJUnitMalformedDeclarationInspectionTest { public java.util.stream.Stream getParameters() { return java.util.Arrays.asList( "Another execution", "Last execution").stream(); } } + @PerClass + abstract class PerClassBase1 { + public java.util.stream.Stream getParameters() { return java.util.Arrays.asList( "Another execution", "Last execution").stream(); } + } + + class PerClassTest1 extends PerClassBase1 { + @org.junit.jupiter.params.ParameterizedTest + @org.junit.jupiter.params.provider.MethodSource("getParameters") + public void shouldExecuteWithParameterizedMethodSource(String arguments) { } + } + + abstract class PerClassBase2 { + @org.junit.jupiter.params.ParameterizedTest + @org.junit.jupiter.params.provider.MethodSource("getParameters") + public void shouldExecuteWithParameterizedMethodSource(String arguments) { } + } + + @PerClass + class PerClassTest2 extends PerClassBase2 { + public java.util.stream.Stream getParameters() { return java.util.Arrays.asList( "Another execution", "Last execution").stream(); } + } + + abstract class PerClassBase3 { + @org.junit.jupiter.params.ParameterizedTest + @org.junit.jupiter.params.provider.MethodSource("getParameters") + public void shouldExecuteWithParameterizedMethodSource(String arguments) { } + + public java.util.stream.Stream getParameters() { return java.util.Arrays.asList( "Another execution", "Last execution").stream(); } + } + + @PerClass + class PerClassTest3 extends PerClassBase3 { + } + + @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) + @org.junit.jupiter.api.TestInstance(org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS) + @interface PerClass { } + class EnumSource { @org.junit.jupiter.params.ParameterizedTest @org.junit.jupiter.params.provider.EnumSource(names = "FIRST") diff --git a/plugins/junit/kotlin-tests-shared/test/com/intellij/execution/junit/kotlin/codeInspection/KotlinJUnitMalformedDeclarationInspectionTest.kt b/plugins/junit/kotlin-tests-shared/test/com/intellij/execution/junit/kotlin/codeInspection/KotlinJUnitMalformedDeclarationInspectionTest.kt index b2b9b2fbbb0f..e0b7be3a28e5 100644 --- a/plugins/junit/kotlin-tests-shared/test/com/intellij/execution/junit/kotlin/codeInspection/KotlinJUnitMalformedDeclarationInspectionTest.kt +++ b/plugins/junit/kotlin-tests-shared/test/com/intellij/execution/junit/kotlin/codeInspection/KotlinJUnitMalformedDeclarationInspectionTest.kt @@ -609,6 +609,47 @@ abstract class KotlinJUnitMalformedDeclarationInspectionTestLatest : KotlinJUnit fun injectTestReporter(x: Int, testReporter: org.junit.jupiter.api.TestReporter) { System.out.println("${'$'}x, ${'$'}testReporter") } + + @org.junit.jupiter.params.ParameterizedTest + @org.junit.jupiter.params.provider.MethodSource("intStreamProvider") + fun intStreamProvider(x: Int, testReporter: org.junit.jupiter.api.TestReporter) { System.out.println("${'$'}x, ${'$'}testReporter") } + + @PerClass + abstract class PerClassBase1 { + fun getParameters() = java.util.stream.Stream.of("Another execution", "Last execution") + } + + class PerClassTest1 : PerClassBase1() { + @org.junit.jupiter.params.ParameterizedTest + @org.junit.jupiter.params.provider.MethodSource("getParameters") + fun shouldExecuteWithParameterizedMethodSource(arguments: String) = Unit + } + + abstract class PerClassBase2 { + @org.junit.jupiter.params.ParameterizedTest + @org.junit.jupiter.params.provider.MethodSource("getParameters") + fun shouldExecuteWithParameterizedMethodSource(arguments: String) = Unit + } + + @PerClass + class PerClassTest2 : PerClassBase2() { + fun getParameters() = java.util.stream.Stream.of("Another execution", "Last execution") + } + + abstract class PerClassBase3 { + @org.junit.jupiter.params.ParameterizedTest + @org.junit.jupiter.params.provider.MethodSource("getParameters") + fun shouldExecuteWithParameterizedMethodSource(arguments: String) = Unit + + fun getParameters() = java.util.stream.Stream.of("Another execution", "Last execution") + } + + @PerClass + class PerClassTest3 : PerClassBase3() + + @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.RUNTIME) + @org.junit.jupiter.api.TestInstance(org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS) + annotation class PerClass companion object { @JvmStatic diff --git a/plugins/junit/src/com/intellij/execution/junit/references/BaseJunitAnnotationReference.kt b/plugins/junit/src/com/intellij/execution/junit/references/BaseJunitAnnotationReference.kt index f4495a445b57..bd9c4e23bddb 100644 --- a/plugins/junit/src/com/intellij/execution/junit/references/BaseJunitAnnotationReference.kt +++ b/plugins/junit/src/com/intellij/execution/junit/references/BaseJunitAnnotationReference.kt @@ -46,6 +46,12 @@ abstract class BaseJunitAnnotationReference( return if (results.size == 1) results[0].element else null } + private fun filteredMethod(clazzMethods: Array, uClass: UClass, testMethod: UMethod?): List { + val noStaticProblem = clazzMethods.filter { hasNoStaticProblem(it, uClass, testMethod) } + if (noStaticProblem.isNotEmpty()) return noStaticProblem + return clazzMethods.toList() + } + override fun getVariants(): Array { val myLiteral = element.toUElement(UExpression::class.java) ?: return emptyArray() val topLevelClass = myLiteral.getParentOfType(UClass::class.java) ?: return emptyArray() @@ -76,24 +82,24 @@ abstract class BaseJunitAnnotationReference( * @param scope The class scope used to resolve the method reference. * @return The resolved `PsiMethod` if found, or `null` if the method cannot be resolved or the link is not direct. */ - private fun directLink(literal: UExpression, scope: UClass): PsiMethod? { + private fun directLink(literal: UExpression, scope: UClass, testMethod: UMethod?): PsiMethod? { val string = literal.evaluate() as String? ?: return null val className = StringUtil.getPackageName(string, '#') if (className.isEmpty()) return null val methodName = StringUtil.getShortName(string, '#') if (methodName.isEmpty()) return null val directClass = ClassUtil.findPsiClass(scope.javaPsi.manager, className, null, false, scope.javaPsi.resolveScope) ?: return null - return directClass.findMethodsByName(methodName, false).firstOrNull() + return filteredMethod(directClass.findMethodsByName(methodName, false), scope, testMethod).firstOrNull() } - private fun fastResolveFor(literal: UExpression, scope: UClass): Set { + private fun fastResolveFor(literal: UExpression, scope: UClass, testMethod: UMethod?): Set { val methodName = literal.evaluate() as String? ?: return setOf() val psiClazz = scope.javaPsi - val clazzMethods = psiClazz.findMethodsByName(methodName, true) + val clazzMethods = filteredMethod(psiClazz.findMethodsByName(methodName, true), scope, testMethod) val methods = ClassInheritorsSearch.search(psiClazz, psiClazz.resolveScope, true) - .findAll() - .flatMap { aClazz -> aClazz.findMethodsByName(methodName, true).toList() } + .mapNotNull { aClazz -> aClazz.toUElement(UClass::class.java) } + .flatMap { uClazz -> filteredMethod(uClazz.javaPsi.findMethodsByName(methodName, true), uClazz, testMethod) } .toMutableSet() methods.addAll(clazzMethods) return methods @@ -106,10 +112,10 @@ abstract class BaseJunitAnnotationReference( private fun fastResolveFor(testMethod: UMethod): Set { val literal = element.toUElement(UExpression::class.java) ?: return setOf() val scope = literal.getParentOfType(UClass::class.java) ?: return setOf() - val directLink = directLink(literal, scope) + val directLink = directLink(literal, scope, testMethod) if (directLink != null) return setOf(directLink) val currentClass = testMethod.getParentOfType(UClass::class.java) ?: return setOf() - return fastResolveFor(literal, currentClass) + return fastResolveFor(literal, currentClass, testMethod) } /** @@ -125,10 +131,10 @@ abstract class BaseJunitAnnotationReference( override fun resolve(ref: BaseJunitAnnotationReference, incompleteCode: Boolean): Array { val literal = ref.element.toUElement(UExpression::class.java) ?: return ResolveResult.EMPTY_ARRAY val uClass = literal.getParentOfType(UClass::class.java) ?: return ResolveResult.EMPTY_ARRAY - val directLink = ref.directLink(literal, uClass) + val method = literal.getParentOfType(UMethod::class.java) + val directLink = ref.directLink(literal, uClass, method) if (directLink != null) return arrayOf(PsiMethodSourceResolveResult(directLink, listOf())) - val method = literal.getParentOfType(UMethod::class.java) if (method != null) { // direct annotation val owners = method.javaPsi.containingClass?.let { listOf(it) } ?: emptyList() return ref.fastResolveFor(method).map { PsiMethodSourceResolveResult(it, owners) }.toTypedArray() @@ -154,7 +160,7 @@ abstract class BaseJunitAnnotationReference( .mapNotNull { method -> method.toUElement(UMethod::class.java) } .mapNotNull { method -> method.getParentOfType(UClass::class.java) } .distinct() // process only classes - .map{ clazz -> clazz to ref.fastResolveFor(literal, clazz) } + .map{ clazz -> clazz to ref.fastResolveFor(literal, clazz, method) } .flatMap { (clazz, methods) -> methods.map { method -> method to clazz } } .groupBy({ it.first }, { it.second }) .map { (method, classes) -> PsiMethodSourceResolveResult(method, classes) }.toTypedArray() @@ -162,7 +168,7 @@ abstract class BaseJunitAnnotationReference( val clazz = literal.getParentOfType(UClass::class.java) if (clazz != null) { val owners = clazz.javaPsi.containingClass?.let { listOf(it) } ?: emptyList() - return ref.fastResolveFor(literal, clazz).map { PsiMethodSourceResolveResult(it, owners) }.toTypedArray() + return ref.fastResolveFor(literal, clazz, method).map { PsiMethodSourceResolveResult(it, owners) }.toTypedArray() } else { return ResolveResult.EMPTY_ARRAY }