Skip to content

Commit 56da3b8

Browse files
committed
Stop reporting discovery issues for synthetic methods
Prior to this commit, _all_ methods of a potential test class were checked for being test methods, including synthetic ones. However, when resolving a test class only non-synthetic methods were taken into account. Since the Kotlin compiler generates a synthetic, static method suffixed with `$suspendImpl` for every open (i.e. non-final) method and copies its annotations, such methods were included in the check. In addition to filtering out synthetic methods from being checked, `KotlinReflectionUtils` no longer recognizes them as suspend functions. Fixes #5102.
1 parent 7116a82 commit 56da3b8

File tree

6 files changed

+79
-3
lines changed

6 files changed

+79
-3
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-6.0.1.adoc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ repository on GitHub.
4545
to avoid conflicts with other control characters.
4646
* Fix `IllegalAccessError` thrown when using the Kotlin-specific `assertDoesNotThrow`
4747
assertion.
48+
* Stop reporting discovery issues for synthetic methods, particularly in conjunction with
49+
Kotlin suspend functions.
4850

4951
[[release-notes-6.0.1-junit-jupiter-deprecations-and-breaking-changes]]
5052
==== Deprecations and Breaking Changes

junit-jupiter-engine/src/main/java/org/junit/jupiter/engine/discovery/predicates/IsTestableMethod.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ abstract class IsTestableMethod implements Predicate<Method> {
4545

4646
@Override
4747
public boolean test(Method candidate) {
48-
if (isAnnotated(candidate, this.annotationType)) {
48+
if (!candidate.isSynthetic() && isAnnotated(candidate, this.annotationType)) {
4949
return condition.check(candidate) && isNotAbstract(candidate);
5050
}
5151
return false;

junit-platform-commons/src/main/java/org/junit/platform/commons/util/KotlinReflectionUtils.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ private static Try<Class<? extends Annotation>> tryToLoadKotlinMetadataClass() {
6767
*/
6868
@API(status = INTERNAL, since = "6.0")
6969
public static boolean isKotlinSuspendingFunction(Method method) {
70-
if (kotlinCoroutineContinuation != null && isKotlinType(method.getDeclaringClass())) {
70+
if (!method.isSynthetic() && kotlinCoroutineContinuation != null && isKotlinType(method.getDeclaringClass())) {
7171
int parameterCount = method.getParameterCount();
7272
return parameterCount > 0 //
7373
&& method.getParameterTypes()[parameterCount - 1] == kotlinCoroutineContinuation;

jupiter-tests/src/test/kotlin/org/junit/jupiter/api/kotlin/KotlinSuspendFunctionsTests.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ class KotlinSuspendFunctionsTests : AbstractJupiterTestEngineTests() {
4141
assertThat(getPublishedEvents(results)).containsExactly("test")
4242
}
4343

44+
@Test
45+
fun suspendingOpenTestMethodsAreSupported() {
46+
val results = executeTestsForClass(OpenTestMethodTestCase::class)
47+
results.allEvents().debug()
48+
assertAllTestsPassed(results, 1)
49+
assertThat(getPublishedEvents(results)).containsExactly("test")
50+
}
51+
4452
@Test
4553
fun suspendingTestTemplateMethodsAreSupported() {
4654
val results = executeTestsForClass(TestTemplateTestCase::class)
@@ -187,6 +195,14 @@ class KotlinSuspendFunctionsTests : AbstractJupiterTestEngineTests() {
187195
}
188196
}
189197

198+
@Suppress("JUnitMalformedDeclaration")
199+
open class OpenTestMethodTestCase {
200+
@Test // https://github.com/junit-team/junit-framework/issues/5102
201+
open suspend fun `check getRandomPositiveInt`(reporter: TestReporter) {
202+
suspendingPublish(reporter, "test")
203+
}
204+
}
205+
190206
@Suppress("RedundantSuspendModifier")
191207
companion object {
192208
suspend fun suspendingPublish(

platform-tests/platform-tests.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import org.gradle.plugins.ide.eclipse.model.Classpath
55
import org.gradle.plugins.ide.eclipse.model.SourceFolder
66

77
plugins {
8-
id("junitbuild.java-library-conventions")
8+
id("junitbuild.kotlin-library-conventions")
99
id("junitbuild.junit4-compatibility")
1010
id("junitbuild.testing-conventions")
1111
id("junitbuild.jmh-conventions")
@@ -54,6 +54,7 @@ dependencies {
5454
testImplementation(libs.openTestReporting.tooling.core)
5555
testImplementation(libs.picocli)
5656
testImplementation(libs.bundles.xmlunit)
57+
testImplementation(kotlin("stdlib"))
5758
testImplementation(testFixtures(projects.junitJupiterApi))
5859
testImplementation(testFixtures(projects.junitPlatformReporting))
5960
testImplementation(projects.platformTests) {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright 2015-2025 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
package org.junit.platform.commons.util
11+
12+
import org.junit.jupiter.api.Assertions.assertFalse
13+
import org.junit.jupiter.api.Assertions.assertTrue
14+
import org.junit.jupiter.api.Test
15+
import org.junit.jupiter.api.TestReporter
16+
import org.junit.platform.commons.support.ModifierSupport
17+
import kotlin.coroutines.Continuation
18+
19+
class KotlinReflectionUtilsTests {
20+
@Test
21+
fun recognizesSuspendFunction() {
22+
val method =
23+
OpenTestMethodTestCase::class.java.getDeclaredMethod(
24+
"test",
25+
TestReporter::class.java,
26+
Continuation::class.java
27+
)
28+
29+
assertFalse(ModifierSupport.isStatic(method))
30+
assertFalse(method.isSynthetic)
31+
32+
assertTrue(KotlinReflectionUtils.isKotlinSuspendingFunction(method))
33+
}
34+
35+
@Test
36+
fun doesNotRecognizeSyntheticMethodAsSuspendFunction() {
37+
val method =
38+
OpenTestMethodTestCase::class.java.getDeclaredMethod(
39+
"test\$suspendImpl",
40+
OpenTestMethodTestCase::class.java,
41+
TestReporter::class.java,
42+
Continuation::class.java
43+
)
44+
45+
assertTrue(ModifierSupport.isStatic(method))
46+
assertTrue(method.isSynthetic)
47+
48+
assertFalse(KotlinReflectionUtils.isKotlinSuspendingFunction(method))
49+
}
50+
51+
@Suppress("JUnitMalformedDeclaration")
52+
open class OpenTestMethodTestCase {
53+
@Test
54+
open suspend fun test(reporter: TestReporter) {
55+
}
56+
}
57+
}

0 commit comments

Comments
 (0)