Skip to content

Commit

Permalink
test: Fix flaky RobolectricTestRunner tests (#7)
Browse files Browse the repository at this point in the history
  • Loading branch information
warnyul authored Mar 11, 2024
1 parent 1801863 commit 8e1b646
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 93 deletions.
19 changes: 18 additions & 1 deletion .idea/codeStyles/Project.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@ junit5 = "5.10.2"
jvmToolchain = "17"
kotlin = "1.9.22"
robolectric = "4.11.1"
robolectricAndroidAll = "14-robolectric-10818077"
# Use when bom also added to the dependencies
sources = "sources"

[libraries]
androidxTestExtJunit = { module = "androidx.test.ext:junit", version.ref = "androidxTestExtJunit" }
guava = { module = "com.google.guava:guava", version = { require = "[32.0.1-jre,]" } }
guavaConstraint = { module = "com.google.guava:guava", version = { require = "[32.0.1-jre,]" } }
junit4 = { module = "junit:junit", version.ref = "junit4" }
junit5Bom = { module = "org.junit:junit-bom", version.ref = "junit5" }
junit5JupiterApi = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "sources" }
Expand All @@ -22,6 +23,7 @@ junit5JupiterParams = { module = "org.junit.jupiter:junit-jupiter-params", versi
junit5PlatformLauncher = { module = "org.junit.platform:junit-platform-launcher", version.ref = "sources" }
kotlinTestJUnit5 = { module = "org.jetbrains.kotlin:kotlin-test-junit5", version.ref = "kotlin" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
robolectricAndroidAll = { module = "org.robolectric:android-all", version.ref = "robolectricAndroidAll" }

[plugins]
androidJUnit5 = { id = "de.mannodermaus.android-junit5", version.ref = "androidJUnit5" }
Expand Down
9 changes: 5 additions & 4 deletions robolectric-extension/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,16 @@ dependencies {
implementation libs.junit4
implementation platform(libs.junit5Bom)
implementation libs.bundles.junit5
implementation(libs.guavaConstraint) {
because 'CVE-2023-2976 7.1 Transitive Files or Directories Accessible to External Parties vulnerability ' +
'with High severity found'
}
testImplementation libs.robolectricAndroidAll
testImplementation libs.junit4
testImplementation libs.androidxTestExtJunit
testImplementation libs.kotlinTestJUnit5
testImplementation libs.junit5JupiterParams
testRuntimeOnly libs.junit5JupiterEngine
implementation(libs.guava) {
because "CVE-2023-2976 7.1 " +
"Transitive Files or Directories Accessible to External Parties vulnerability with High severity found"
}
}

kotlin {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package tech.apter.junit.jupiter.robolectric.internal

import java.util.Collections
import javax.inject.Inject
import org.robolectric.ApkLoader
import org.robolectric.annotation.SQLiteMode
import org.robolectric.internal.AndroidSandbox
Expand All @@ -10,14 +12,13 @@ import org.robolectric.internal.bytecode.ClassInstrumentor
import org.robolectric.internal.bytecode.InstrumentationConfiguration
import org.robolectric.internal.bytecode.ShadowProviders
import org.robolectric.pluginapi.Sdk
import java.util.*
import javax.inject.Inject

internal class JUnit5RobolectricSandboxBuilder @Inject constructor(
private val apkLoader: ApkLoader,
@Suppress("VisibleForTests")
private val testEnvironmentSpec: AndroidSandbox.TestEnvironmentSpec,
private val shadowProviders: ShadowProviders,
private val classInstrumentor: ClassInstrumentor,
) : SandboxManager.SandboxBuilder {
private val logger get() = createLogger()

Expand Down Expand Up @@ -45,14 +46,18 @@ internal class JUnit5RobolectricSandboxBuilder @Inject constructor(
instrumentationConfig: InstrumentationConfiguration,
runtimeSdk: Sdk,
): SdkSandboxClassLoader {
val key = Key(instrumentationConfig, runtimeSdk)
val key = Key(instrumentationConfig, runtimeSdk, classInstrumentor)
return classLoaderCache.getOrPut(key) {
logger.debug { "${SdkSandboxClassLoader::class.simpleName} instance created" }
SdkSandboxClassLoader(instrumentationConfig, runtimeSdk, ClassInstrumentor())
logger.debug { "${SdkSandboxClassLoader::class.simpleName} instance created for $key." }
SdkSandboxClassLoader(instrumentationConfig, runtimeSdk, classInstrumentor)
}
}

private data class Key(private val configuration: InstrumentationConfiguration, private val runtimeSdk: Sdk)
private data class Key(
private val configuration: InstrumentationConfiguration,
private val runtimeSdk: Sdk,
private val classInstrumentor: ClassInstrumentor,
)

private companion object {
@JvmStatic
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
package tech.apter.junit.jupiter.robolectric.internal

import java.lang.reflect.Method
import org.junit.runners.model.FrameworkMethod
import org.robolectric.RobolectricTestRunner
import org.robolectric.internal.SandboxManager.SandboxBuilder
import org.robolectric.internal.SandboxTestRunner
import org.robolectric.internal.bytecode.InstrumentationConfiguration
import org.robolectric.internal.bytecode.Sandbox
import java.lang.reflect.Method
import org.robolectric.util.inject.Injector

internal class JUnit5RobolectricTestRunner(clazz: Class<*>) :
internal class JUnit5RobolectricTestRunner(clazz: Class<*>, injector: Injector = defaultInjectorBuilder().build()) :
RobolectricTestRunner(clazz, injector) {
private val logger get() = createLogger()
fun frameworkMethod(method: Method): FrameworkMethod = children.first { it.name == method.name }
Expand Down Expand Up @@ -45,7 +46,8 @@ internal class JUnit5RobolectricTestRunner(clazz: Class<*>) :

override fun createClassLoaderConfig(method: FrameworkMethod): InstrumentationConfiguration {
return InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
.doNotAcquirePackage("tech.apter.junit.jupiter.robolectric").build()
.doNotAcquireClass(JUnit5RobolectricSandboxBuilder::class.java)
.build()
}

override fun computeTestMethods() = computeJUnit5TestMethods()
Expand Down Expand Up @@ -75,8 +77,7 @@ internal class JUnit5RobolectricTestRunner(clazz: Class<*>) :
}

private companion object {
private val injector = defaultInjector()
private fun defaultInjectorBuilder() = defaultInjector()
.bind(SandboxBuilder::class.java, JUnit5RobolectricSandboxBuilder::class.java)
.build()
}
}
18 changes: 18 additions & 0 deletions robolectric-extension/src/test/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Manifest for tests. Required because the AOSP resource merger requires leaf nodes to provide
the following elements directly:-
<uses-sdk>
<uses-permission>
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.robolectric">
<uses-sdk android:minSdkVersion="19" android:targetSdkVersion="27"/>

<!-- For SettingsTest -->
<uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.GET_TASKS"/>
</manifest>
Original file line number Diff line number Diff line change
@@ -1,37 +1,29 @@
package tech.apter.junit.jupiter.robolectric.internal

import org.junit.jupiter.api.extension.ExtendWith
import org.robolectric.ApkLoader
import org.robolectric.annotation.SQLiteMode
import org.robolectric.internal.AndroidSandbox
import org.robolectric.internal.ResourcesMode
import org.robolectric.internal.SandboxManager.SandboxBuilder
import org.robolectric.internal.bytecode.InstrumentationConfiguration
import org.robolectric.internal.bytecode.ShadowProviders
import org.robolectric.internal.dependency.MavenDependencyResolver
import org.robolectric.pluginapi.Sdk
import org.robolectric.plugins.DefaultSdkProvider
import org.robolectric.versioning.AndroidVersions.T
import org.robolectric.versioning.AndroidVersions.U
import tech.apter.junit.jupiter.robolectric.RobolectricExtension
import org.robolectric.plugins.SdkCollection
import org.robolectric.util.inject.Injector
import tech.apter.junit.jupiter.robolectric.internal.tools.TestUtil
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertIs
import kotlin.test.assertNotSame
import kotlin.test.assertSame

@ExtendWith(RobolectricExtension::class)
@Ignore
class JUnit5RobolectricSandboxBuilderTest {

private val dependencyResolver = MavenDependencyResolver()
private val defaultSdkProvider = DefaultSdkProvider(dependencyResolver)

@Test
fun `Given the same arguments when call build twice then should return two different sandboxes with the same classloader `() {
// Given
val instrumentationConfiguration = createInstrumentationConfiguration()
val runtimeSdk: Sdk = defaultSdkProvider.DefaultSdk(U.SDK_INT, "14", "10818077", "REL", 17)
val compileSdk: Sdk = defaultSdkProvider.DefaultSdk(U.SDK_INT, "14", "10818077", "REL", 17)
val runtimeSdk: Sdk = TestUtil.sdkCollection.getSdk(34)
val compileSdk: Sdk = TestUtil.sdkCollection.getSdk(34)
val resourcesMode: ResourcesMode = ResourcesMode.BINARY
val sqLiteMode: SQLiteMode.Mode = SQLiteMode.Mode.NATIVE

Expand All @@ -53,11 +45,11 @@ class JUnit5RobolectricSandboxBuilderTest {
fun `Given different arguments when call build twice then should return two different sandboxes with different classloaders`() {
// Given
val instrumentationConfiguration1 = createInstrumentationConfiguration()
val runtimeSdk1: Sdk = defaultSdkProvider.DefaultSdk(U.SDK_INT, "14", "10818077", "REL", 17)
val compileSdk1: Sdk = defaultSdkProvider.DefaultSdk(U.SDK_INT, "14", "10818077", "REL", 17)
val runtimeSdk1: Sdk = TestUtil.sdkCollection.getSdk(34)
val compileSdk1: Sdk = TestUtil.sdkCollection.getSdk(34)
val instrumentationConfiguration2 = createInstrumentationConfiguration()
val runtimeSdk2: Sdk = defaultSdkProvider.DefaultSdk(T.SDK_INT, "13", "9030017", "Tiramisu", 9)
val compileSdk2: Sdk = defaultSdkProvider.DefaultSdk(T.SDK_INT, "13", "9030017", "Tiramisu", 9)
val runtimeSdk2: Sdk = TestUtil.sdkCollection.getSdk(33)
val compileSdk2: Sdk = TestUtil.sdkCollection.getSdk(33)
val resourcesMode: ResourcesMode = ResourcesMode.BINARY
val sqLiteMode: SQLiteMode.Mode = SQLiteMode.Mode.NATIVE

Expand All @@ -74,22 +66,25 @@ class JUnit5RobolectricSandboxBuilderTest {

private fun subjectUnderTest(
action: JUnit5RobolectricSandboxBuilder.() -> Unit
): JUnit5RobolectricSandboxBuilder = JUnit5RobolectricSandboxBuilder(
ApkLoader(),
AndroidSandbox.TestEnvironmentSpec(),
ShadowProviders(emptyList()),
).apply(action)
): JUnit5RobolectricSandboxBuilder = Injector.Builder()
.bind(SandboxBuilder::class.java, JUnit5RobolectricSandboxBuilder::class.java)
.bind(SdkCollection::class.java, TestUtil.sdkCollection)
.build()
.getInstance(JUnit5RobolectricSandboxBuilder::class.java)
.apply(action)

companion object {
private fun createInstrumentationConfiguration() =
InstrumentationConfiguration.newBuilder().doNotAcquirePackage("java.")
InstrumentationConfiguration.newBuilder()
.doNotAcquirePackage("java.")
.doNotAcquirePackage("jdk.internal.")
.doNotAcquirePackage("sun.")
.doNotAcquirePackage("org.robolectric.annotation.")
.doNotAcquirePackage("org.robolectric.internal.")
.doNotAcquirePackage("org.robolectric.pluginapi.")
.doNotAcquirePackage("org.robolectric.util.")
.doNotAcquirePackage("org.junit")
.doNotAcquireClass(JUnit5RobolectricSandboxBuilder::class.java)
.build()
}
}
Loading

0 comments on commit 8e1b646

Please sign in to comment.