Skip to content

Commit 6ffb7d9

Browse files
committed
Allow control over instrumentation tests on unsupported devices via configparam & DSL
1 parent 75d281c commit 6ffb7d9

20 files changed

Lines changed: 206 additions & 48 deletions

File tree

instrumentation/.idea/runConfigurations/Format_Code.xml

Lines changed: 31 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

instrumentation/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Change Log
88
- Update to Compose 1.10
99
- Support instrumentation with JUnit 5 and 6 (the plugin will choose the correct runtime accordingly)
1010
- Avoid error when a client doesn't include junit-jupiter-params on the runtime classpath
11+
- New: Instead of silently skipping tests when running on unsupported devices, fail test execution via configuration parameter `de.mannodermaus.junit.unsupported.behavior`
1112

1213
## 1.9.0 (2025-10-10)
1314

instrumentation/compose/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ android {
2121
junitPlatform {
2222
// Using local dependency instead of Maven coordinates
2323
instrumentationTests.enabled = false
24+
25+
// Fail test execution when running on unsupported device
26+
// (TODO: Change this to the proper instrumentationTests API once released as stable)
27+
configurationParameter("de.mannodermaus.junit.unsupported.behavior", "fail")
2428
}
2529

2630
dependencies {

instrumentation/core/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ junitPlatform {
1818
// See TaggedTests.kt for usage of this tag
1919
excludeTags("nope")
2020
}
21+
22+
// Fail test execution when running on unsupported device
23+
// (TODO: Change this to the proper instrumentationTests API once released as stable)
24+
configurationParameter("de.mannodermaus.junit.unsupported.behavior", "fail")
2125
}
2226

2327
// Use local project dependencies on android-test instrumentation libraries

instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/AndroidJUnitFrameworkBuilder.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,12 @@ public open class AndroidJUnitFrameworkBuilder internal constructor() : RunnerBu
4848
}
4949

5050
// One-time parsing setup for runner params, taken from instrumentation arguments
51-
private val params by lazy {
51+
private val params =
5252
JUnitFrameworkRunnerParams.create().also { params ->
5353
// Apply all environment variables & system properties to the running process
5454
params.registerEnvironmentVariables()
5555
params.registerSystemProperties()
5656
}
57-
}
5857

5958
@Throws(Throwable::class)
6059
override fun runnerForClass(testClass: Class<*>): Runner? {
@@ -63,7 +62,7 @@ public open class AndroidJUnitFrameworkBuilder internal constructor() : RunnerBu
6362

6463
try {
6564
return if (junitFrameworkAvailable) {
66-
tryCreateJUnitFrameworkRunner(testClass) { params }
65+
tryCreateJUnitFrameworkRunner(testClass, params)
6766
} else {
6867
null
6968
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package de.mannodermaus.junit5.internal
2+
3+
public object ConfigurationParameters {
4+
/**
5+
* How to behave when executing instrumentation tests on an unsupported device (i.e. too old).
6+
* Accepted values: "skip", "fail"
7+
*/
8+
public const val BEHAVIOR_FOR_UNSUPPORTED_DEVICES: String =
9+
"de.mannodermaus.junit.unsupported.behavior"
10+
}

instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/AndroidJUnitFramework.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import org.junit.runner.notification.RunNotifier
1818
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
1919
internal class AndroidJUnitFramework(
2020
private val testClass: Class<*>,
21-
paramsSupplier: () -> JUnitFrameworkRunnerParams = JUnitFrameworkRunnerParams::create,
21+
params: JUnitFrameworkRunnerParams,
2222
) : Runner() {
2323
private val launcher = LauncherFactory.create()
24-
private val testTree by lazy { generateTestTree(paramsSupplier()) }
24+
private val testTree by lazy { generateTestTree(params) }
2525

2626
override fun getDescription() = testTree.suiteDescription
2727

instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/DummyJUnitFramework.kt

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@ package de.mannodermaus.junit5.internal.runners
22

33
import android.os.Build
44
import android.util.Log
5+
import de.mannodermaus.junit5.internal.ConfigurationParameters
56
import de.mannodermaus.junit5.internal.JUNIT_FRAMEWORK_MINIMUM_SDK_VERSION
67
import de.mannodermaus.junit5.internal.LOG_TAG
78
import de.mannodermaus.junit5.internal.dummy.JupiterTestMethodFinder
8-
import java.lang.reflect.Method
9+
import org.junit.platform.commons.JUnitException
910
import org.junit.runner.Description
1011
import org.junit.runner.Runner
1112
import org.junit.runner.notification.RunNotifier
@@ -14,22 +15,28 @@ import org.junit.runner.notification.RunNotifier
1415
* Fake Runner that marks all JUnit Framework methods as ignored, used for old devices without the
1516
* required Java capabilities.
1617
*/
17-
internal class DummyJUnitFramework(private val testClass: Class<*>) : Runner() {
18+
internal class DummyJUnitFramework(
19+
private val testClass: Class<*>,
20+
params: JUnitFrameworkRunnerParams,
21+
) : Runner() {
1822

19-
private val testMethods: Set<Method> = JupiterTestMethodFinder.find(testClass)
23+
private val testMethods = JupiterTestMethodFinder.find(testClass)
24+
private val behaviorForUnsupportedDevices = params.behaviorForUnsupportedDevices
2025

2126
override fun run(notifier: RunNotifier) {
22-
Log.w(
23-
LOG_TAG,
24-
"JUnit Framework is not supported on this device: " +
25-
"API level ${Build.VERSION.SDK_INT} is less than " +
26-
"${JUNIT_FRAMEWORK_MINIMUM_SDK_VERSION}, the minimum requirement. " +
27-
"All Jupiter tests for ${testClass.name} will be disabled.",
28-
)
29-
30-
for (testMethod in testMethods) {
31-
val description = Description.createTestDescription(testClass, testMethod.name)
32-
notifier.fireTestIgnored(description)
27+
when (behaviorForUnsupportedDevices) {
28+
"skip" -> skipTests(notifier)
29+
"fail" -> failExecution(unsupportedDeviceMessage)
30+
else -> {
31+
Log.w(
32+
LOG_TAG,
33+
"Unknown value found for configuration parameter " +
34+
"'${ConfigurationParameters.BEHAVIOR_FOR_UNSUPPORTED_DEVICES}': " +
35+
"$behaviorForUnsupportedDevices. Apply default behavior " +
36+
"and skip tests for this class.",
37+
)
38+
skipTests(notifier)
39+
}
3340
}
3441
}
3542

@@ -39,4 +46,24 @@ internal class DummyJUnitFramework(private val testClass: Class<*>) : Runner() {
3946
it.addChild(Description.createTestDescription(testClass, method.name))
4047
}
4148
}
49+
50+
private val unsupportedDeviceMessage by lazy {
51+
"JUnit Framework is not supported on this device: " +
52+
"API level ${Build.VERSION.SDK_INT} is less than " +
53+
"${JUNIT_FRAMEWORK_MINIMUM_SDK_VERSION}, the minimum requirement. " +
54+
"All Jupiter tests for ${testClass.name} will be disabled."
55+
}
56+
57+
private fun skipTests(notifier: RunNotifier) {
58+
Log.w(LOG_TAG, unsupportedDeviceMessage)
59+
60+
for (testMethod in testMethods) {
61+
val description = Description.createTestDescription(testClass, testMethod.name)
62+
notifier.fireTestIgnored(description)
63+
}
64+
}
65+
66+
private fun failExecution(message: String): Nothing {
67+
throw JUnitException(message)
68+
}
4269
}

instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/JUnitFrameworkRunnerFactory.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@ import org.junit.runner.Runner
1313
*/
1414
internal fun tryCreateJUnitFrameworkRunner(
1515
klass: Class<*>,
16-
paramsSupplier: () -> JUnitFrameworkRunnerParams,
16+
params: JUnitFrameworkRunnerParams,
1717
): Runner? {
1818
val runner =
1919
if (Build.VERSION.SDK_INT >= JUNIT_FRAMEWORK_MINIMUM_SDK_VERSION) {
20-
AndroidJUnitFramework(klass, paramsSupplier)
20+
AndroidJUnitFramework(klass, params)
2121
} else {
22-
DummyJUnitFramework(klass)
22+
DummyJUnitFramework(klass, params)
2323
}
2424

2525
// It's still possible for the runner to not be relevant to the test run,

instrumentation/runner/src/main/kotlin/de/mannodermaus/junit5/internal/runners/JUnitFrameworkRunnerParams.kt

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package de.mannodermaus.junit5.internal.runners
22

33
import android.os.Bundle
44
import androidx.test.platform.app.InstrumentationRegistry
5+
import de.mannodermaus.junit5.internal.ConfigurationParameters
56
import de.mannodermaus.junit5.internal.discovery.GeneratedFilters
67
import de.mannodermaus.junit5.internal.discovery.ParsedSelectors
78
import de.mannodermaus.junit5.internal.discovery.PropertiesParser
@@ -13,7 +14,7 @@ import org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder
1314

1415
internal data class JUnitFrameworkRunnerParams(
1516
private val arguments: Bundle = Bundle(),
16-
private val filters: List<Filter<*>> = emptyList(),
17+
private val filtersSupplier: () -> List<Filter<*>> = { emptyList() },
1718
val environmentVariables: Map<String, String> = emptyMap(),
1819
val systemProperties: Map<String, String> = emptyMap(),
1920
private val configurationParameters: Map<String, String> = emptyMap(),
@@ -25,14 +26,17 @@ internal data class JUnitFrameworkRunnerParams(
2526
fun createDiscoveryRequest(selectors: List<DiscoverySelector>): LauncherDiscoveryRequest {
2627
return LauncherDiscoveryRequestBuilder.request()
2728
.selectors(selectors)
28-
.filters(*this.filters.toTypedArray())
29+
.filters(*this.filtersSupplier().toTypedArray())
2930
.configurationParameters(this.configurationParameters)
3031
.build()
3132
}
3233

3334
val isParallelExecutionEnabled: Boolean
3435
get() = configurationParameters["junit.jupiter.execution.parallel.enabled"] == "true"
3536

37+
val behaviorForUnsupportedDevices: String?
38+
get() = configurationParameters[ConfigurationParameters.BEHAVIOR_FOR_UNSUPPORTED_DEVICES]
39+
3640
val isUsingOrchestrator: Boolean
3741
get() = arguments.getString("orchestratorService") != null
3842

@@ -64,16 +68,19 @@ internal data class JUnitFrameworkRunnerParams(
6468
// which aren't subject to the filtering imposed through adb.
6569
// A special resource file may be looked up at runtime, containing
6670
// the filters to apply by the AndroidJUnit5 runner.
67-
val filters =
71+
// This requires lazy access because it reaches into JUnit internals,
72+
// which may need Java functionality not supported by the current device
73+
val filtersSupplier = {
6874
GeneratedFilters.fromContext(instrumentation.context) +
6975
listOfNotNull(ShardingFilter.fromArguments(arguments))
76+
}
7077

7178
return JUnitFrameworkRunnerParams(
72-
arguments,
73-
filters,
74-
environmentVariables,
75-
systemProperties,
76-
configurationParameters,
79+
arguments = arguments,
80+
filtersSupplier = filtersSupplier,
81+
environmentVariables = environmentVariables,
82+
systemProperties = systemProperties,
83+
configurationParameters = configurationParameters,
7784
)
7885
}
7986
}

0 commit comments

Comments
 (0)