-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(sandbox): Add a custom robolectric sandbox builder which reusing…
… the class loader (#3)
- Loading branch information
Showing
7 changed files
with
182 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
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
61 changes: 61 additions & 0 deletions
61
...n/kotlin/tech/apter/junit/jupiter/robolectric/internal/JUnit5RobolectricSandboxBuilder.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,61 @@ | ||
package tech.apter.junit.jupiter.robolectric.internal | ||
|
||
import org.robolectric.ApkLoader | ||
import org.robolectric.annotation.SQLiteMode | ||
import org.robolectric.internal.AndroidSandbox | ||
import org.robolectric.internal.AndroidSandbox.SdkSandboxClassLoader | ||
import org.robolectric.internal.ResourcesMode | ||
import org.robolectric.internal.SandboxManager | ||
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, | ||
) : SandboxManager.SandboxBuilder { | ||
private val logger get() = createLogger() | ||
|
||
override fun build( | ||
instrumentationConfig: InstrumentationConfiguration, | ||
runtimeSdk: Sdk, | ||
compileSdk: Sdk, | ||
resourcesMode: ResourcesMode, | ||
sqLiteMode: SQLiteMode.Mode, | ||
): AndroidSandbox { | ||
val sdkSandboxClassLoader = getOrCreateClassLoader(instrumentationConfig, runtimeSdk) | ||
return AndroidSandbox( | ||
runtimeSdk, | ||
compileSdk, | ||
resourcesMode, | ||
apkLoader, | ||
testEnvironmentSpec, | ||
sdkSandboxClassLoader, | ||
shadowProviders, | ||
sqLiteMode, | ||
) | ||
} | ||
|
||
private fun getOrCreateClassLoader( | ||
instrumentationConfig: InstrumentationConfiguration, | ||
runtimeSdk: Sdk, | ||
): SdkSandboxClassLoader { | ||
val key = Key(instrumentationConfig, runtimeSdk) | ||
return classLoaderCache.getOrPut(key) { | ||
logger.debug { "${SdkSandboxClassLoader::class.simpleName} instance created" } | ||
SdkSandboxClassLoader(instrumentationConfig, runtimeSdk, ClassInstrumentor()) | ||
} | ||
} | ||
|
||
private data class Key(private val configuration: InstrumentationConfiguration, private val runtimeSdk: Sdk) | ||
|
||
private companion object { | ||
@JvmStatic | ||
private val classLoaderCache = Collections.synchronizedMap<Key, SdkSandboxClassLoader>(mutableMapOf()) | ||
} | ||
} |
4 changes: 4 additions & 0 deletions
4
...xtension/src/test/kotlin/tech/apter/junit/jupiter/robolectric/RobolectricExtensionTest.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,4 @@ | ||
package tech.apter.junit.jupiter.robolectric | ||
|
||
class RobolectricExtensionTest { | ||
} |
97 changes: 97 additions & 0 deletions
97
...tlin/tech/apter/junit/jupiter/robolectric/internal/JUnit5RobolectricSandboxBuilderTest.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,97 @@ | ||
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.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 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 resourcesMode: ResourcesMode = ResourcesMode.BINARY | ||
val sqLiteMode: SQLiteMode.Mode = SQLiteMode.Mode.NATIVE | ||
|
||
subjectUnderTest { | ||
// When | ||
val sandbox1 = build(instrumentationConfiguration, runtimeSdk, compileSdk, resourcesMode, sqLiteMode) | ||
// And | ||
val sandbox2 = build(instrumentationConfiguration, runtimeSdk, compileSdk, resourcesMode, sqLiteMode) | ||
|
||
// Then | ||
assertIs<AndroidSandbox>(sandbox1) | ||
assertIs<AndroidSandbox>(sandbox2) | ||
assertNotSame(sandbox1, sandbox2) | ||
assertSame(sandbox1.robolectricClassLoader, sandbox2.robolectricClassLoader) | ||
} | ||
} | ||
|
||
@Test | ||
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 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 resourcesMode: ResourcesMode = ResourcesMode.BINARY | ||
val sqLiteMode: SQLiteMode.Mode = SQLiteMode.Mode.NATIVE | ||
|
||
subjectUnderTest { | ||
val sandbox1 = build(instrumentationConfiguration1, runtimeSdk1, compileSdk1, resourcesMode, sqLiteMode) | ||
val sandbox2 = build(instrumentationConfiguration2, runtimeSdk2, compileSdk2, resourcesMode, sqLiteMode) | ||
|
||
assertIs<AndroidSandbox>(sandbox1) | ||
assertIs<AndroidSandbox>(sandbox2) | ||
assertNotSame(sandbox1, sandbox2) | ||
assertNotSame(sandbox1.robolectricClassLoader, sandbox2.robolectricClassLoader) | ||
} | ||
} | ||
|
||
private fun subjectUnderTest( | ||
action: JUnit5RobolectricSandboxBuilder.() -> Unit | ||
): JUnit5RobolectricSandboxBuilder = JUnit5RobolectricSandboxBuilder( | ||
ApkLoader(), | ||
AndroidSandbox.TestEnvironmentSpec(), | ||
ShadowProviders(emptyList()), | ||
).apply { | ||
action() | ||
} | ||
|
||
companion object { | ||
private fun createInstrumentationConfiguration() = | ||
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") | ||
.build() | ||
} | ||
} |