-
Notifications
You must be signed in to change notification settings - Fork 53
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Restructure test name formatting to fix intermittent regression of is…
…olated method runs Since relying fully on JUnit 4's test discovery mechanism, we need to adhere to its expectations when it comes to method filtering. For dynamic tests with JUnit 5, this means that we have to remove the parameters and brackets from the display name when invoking an individual test through CLI or IDE, otherwise it cannot be found
- Loading branch information
1 parent
08c30f0
commit 75de00d
Showing
8 changed files
with
371 additions
and
43 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
...on/runner/src/main/kotlin/de/mannodermaus/junit5/internal/formatters/TestNameFormatter.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package de.mannodermaus.junit5.internal.formatters | ||
|
||
import org.junit.platform.launcher.TestIdentifier | ||
|
||
/** | ||
* A class for naming Jupiter test methods in a compatible manner, | ||
* taking into account several limitations imposed by the | ||
* Android instrumentation (e.g. on isolated test runs). | ||
*/ | ||
internal object TestNameFormatter { | ||
fun format(identifier: TestIdentifier, isIsolatedMethodRun: Boolean = false): String { | ||
// During isolated executions of a single test method, | ||
// construct a technical version of its name for backwards compatibility | ||
// with the JUnit 4-based instrumentation of Android by stripping the brackets of parameterized tests completely. | ||
// If this didn't happen, running them from the IDE will cause "No tests found" errors. | ||
// See AndroidX's TestRequestBuilder$MethodFilter for where this is cross-referenced in the instrumentation! | ||
// | ||
// History: | ||
// - #199 & #207 (the original unearthing of this behavior) | ||
// - #317 (making an exception for dynamic tests) | ||
// - #339 (retain indices of parameterized methods to avoid premature filtering by JUnit 4's test discovery) | ||
if (isIsolatedMethodRun) { | ||
val reportName = identifier.legacyReportingName | ||
val paramStartIndex = reportName.indexOf('(') | ||
if (paramStartIndex > -1) { | ||
val result = reportName.substring(0, paramStartIndex) | ||
|
||
val paramEndIndex = reportName.lastIndexOf('[') | ||
|
||
return if (paramEndIndex > -1) { | ||
// Retain suffix of parameterized methods (i.e. "[1]", "[2]" etc) | ||
// so that they won't be filtered out by JUnit 4 on isolated method runs | ||
result + reportName.substring(paramEndIndex) | ||
} else { | ||
result | ||
} | ||
} | ||
} | ||
|
||
return identifier.displayName.replace("()", "") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
30 changes: 30 additions & 0 deletions
30
instrumentation/runner/src/test/kotlin/de/mannodermaus/junit5/TestHelpers.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package de.mannodermaus.junit5 | ||
|
||
import org.junit.platform.engine.discovery.DiscoverySelectors | ||
import org.junit.platform.launcher.Launcher | ||
import org.junit.platform.launcher.TestPlan | ||
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder | ||
import org.junit.platform.launcher.core.LauncherFactory | ||
import kotlin.reflect.KClass | ||
|
||
/** | ||
* A quick one-liner for executing a Jupiter discover-and-execute pass | ||
* from inside of a Jupiter test. Useful for testing runner code | ||
* that needs to work with the innards of the [TestPlan], such as | ||
* individual test identifiers and such. | ||
*/ | ||
fun discoverTests( | ||
cls: KClass<*>, | ||
launcher: Launcher = LauncherFactory.create(), | ||
executeAsWell: Boolean = true, | ||
): TestPlan { | ||
return launcher.discover( | ||
LauncherDiscoveryRequestBuilder.request() | ||
.selectors(DiscoverySelectors.selectClass(cls.java)) | ||
.build() | ||
).also { plan -> | ||
if (executeAsWell) { | ||
launcher.execute(plan) | ||
} | ||
} | ||
} |
108 changes: 108 additions & 0 deletions
108
...nner/src/test/kotlin/de/mannodermaus/junit5/internal/formatters/TestNameFormatterTests.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package de.mannodermaus.junit5.internal.formatters | ||
|
||
import com.google.common.truth.Truth.assertThat | ||
import de.mannodermaus.junit5.HasParameterizedTest | ||
import de.mannodermaus.junit5.HasRepeatedTest | ||
import de.mannodermaus.junit5.HasTest | ||
import de.mannodermaus.junit5.HasTestFactory | ||
import de.mannodermaus.junit5.HasTestTemplate | ||
import de.mannodermaus.junit5.discoverTests | ||
import de.mannodermaus.junit5.internal.extensions.format | ||
import org.junit.jupiter.api.Test | ||
import org.junit.platform.engine.discovery.DiscoverySelectors | ||
import org.junit.platform.launcher.TestIdentifier | ||
import org.junit.platform.launcher.TestPlan | ||
import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder | ||
import org.junit.platform.launcher.core.LauncherFactory | ||
import kotlin.reflect.KClass | ||
|
||
class TestNameFormatterTests { | ||
|
||
@Test | ||
fun `normal junit5 test`() = runTestWith(HasTest::class) { identifier -> | ||
assertThat(identifier.format(false)).isEqualTo("method") | ||
assertThat(identifier.format(true)).isEqualTo("method") | ||
} | ||
|
||
@Test | ||
fun `repeated test`() = runTestWith(HasRepeatedTest::class) { identifier -> | ||
assertThat(identifier.format(false)).isEqualTo("method(RepetitionInfo)") | ||
assertThat(identifier.format(true)).isEqualTo("method") | ||
|
||
// Inspect individual executions, too | ||
assertChildren(identifier, expectedCount = 5) { index, child -> | ||
val number = index + 1 | ||
assertThat(child.format(false)).isEqualTo("repetition $number of 5") | ||
assertThat(child.format(true)).isEqualTo("method[$number]") | ||
} | ||
} | ||
|
||
@Test | ||
fun `test factory`() = runTestWith(HasTestFactory::class) { identifier -> | ||
assertThat(identifier.format(false)).isEqualTo("method") | ||
assertThat(identifier.format(true)).isEqualTo("method") | ||
|
||
// Inspect individual executions, too | ||
assertChildren(identifier, expectedCount = 2) { index, child -> | ||
val number = index + 1 | ||
assertThat(child.format(false)).isEqualTo(if (index == 0) "a" else "b") | ||
assertThat(child.format(true)).isEqualTo("method[$number]") | ||
} | ||
} | ||
|
||
@Test | ||
fun `test template`() = runTestWith(HasTestTemplate::class) { identifier -> | ||
assertThat(identifier.format(false)).isEqualTo("method(String)") | ||
assertThat(identifier.format(true)).isEqualTo("method") | ||
|
||
// Inspect individual executions, too | ||
assertChildren(identifier, expectedCount = 2) { index, child -> | ||
val number = index + 1 | ||
assertThat(child.format(false)).isEqualTo("param$number") | ||
assertThat(child.format(true)).isEqualTo("method[$number]") | ||
} | ||
} | ||
|
||
@Test | ||
fun `parameterized test`() = runTestWith(HasParameterizedTest::class) { identifier -> | ||
assertThat(identifier.format(false)).isEqualTo("method(String)") | ||
assertThat(identifier.format(true)).isEqualTo("method") | ||
|
||
// Inspect individual executions, too | ||
assertChildren(identifier, expectedCount = 2) { index, child -> | ||
val number = index + 1 | ||
assertThat(child.format(false)).isEqualTo("[$number] " + if (index == 0) "a" else "b") | ||
assertThat(child.format(true)).isEqualTo("method[$number]") | ||
} | ||
} | ||
|
||
/* Private */ | ||
|
||
private fun runTestWith(cls: KClass<*>, block: TestPlan.(TestIdentifier) -> Unit) { | ||
// Discover and execute the test plan of the given class | ||
// (execution is important to resolve any dynamic tests | ||
// that aren't generated until the test plan actually runs) | ||
val plan = discoverTests(cls, executeAsWell = true) | ||
|
||
// Validate common behavior of formatter against class names | ||
val root = plan.roots.first() | ||
val classIdentifier = plan.getChildren(root).first() | ||
assertThat(classIdentifier.format(false)).isEqualTo(cls.simpleName) | ||
assertThat(classIdentifier.format(true)).isEqualTo(cls.simpleName) | ||
|
||
// Delegate to the provided block for the test method of the class | ||
val methodIdentifier = plan.getChildren(classIdentifier).first() | ||
plan.block(methodIdentifier) | ||
} | ||
|
||
private fun TestPlan.assertChildren( | ||
of: TestIdentifier, | ||
expectedCount: Int, | ||
block: (Int, TestIdentifier) -> Unit | ||
) { | ||
with(getChildren(of)) { | ||
assertThat(size).isEqualTo(expectedCount) | ||
forEachIndexed(block) | ||
} | ||
} | ||
} |
Oops, something went wrong.