[junit 5, inspection] fix @MethodSource factory method resolving IDEA-375007

GitOrigin-RevId: 584f4c4e534ec356131a86f7abd42aada18fddd0
This commit is contained in:
Aleksey Dobrynin
2025-06-30 17:22:36 +00:00
committed by intellij-monorepo-bot
parent 10b68df268
commit 96da5ea3cf
3 changed files with 101 additions and 12 deletions
@@ -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<org.junit.jupiter.params.provider.Arguments> stream() { return null; }
static java.util.Iterator<org.junit.jupiter.params.provider.Arguments> 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")
@@ -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
@@ -46,6 +46,12 @@ abstract class BaseJunitAnnotationReference(
return if (results.size == 1) results[0].element else null
}
private fun filteredMethod(clazzMethods: Array<PsiMethod>, uClass: UClass, testMethod: UMethod?): List<PsiMethod> {
val noStaticProblem = clazzMethods.filter { hasNoStaticProblem(it, uClass, testMethod) }
if (noStaticProblem.isNotEmpty()) return noStaticProblem
return clazzMethods.toList()
}
override fun getVariants(): Array<Any> {
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<PsiMethod> {
private fun fastResolveFor(literal: UExpression, scope: UClass, testMethod: UMethod?): Set<PsiMethod> {
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<PsiMethod> {
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<ResolveResult> {
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
}