-
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.
Add new way for reflective access to static final fields in unit test…
…s of instrumentation projects (#297) The Field class no longer has a `modifiers` property, so removing the FINAL modifier doesn't work like it used to with Java 8. Utilize a solution from other libraries to work around the issue with `com.misc.Unsafe`, encapsulated in a non-Android reflection module
- Loading branch information
1 parent
729d9f1
commit 486d154
Showing
5 changed files
with
96 additions
and
50 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ include(":compose") | |
include(":runner") | ||
include(":sample") | ||
include(":testutil") | ||
include(":testutil-reflect") |
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,3 @@ | ||
plugins { | ||
kotlin("jvm") | ||
} |
64 changes: 64 additions & 0 deletions
64
...n/testutil-reflect/src/main/kotlin/de/mannodermaus/junit5/testutil/reflect/Reflections.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,64 @@ | ||
@file:Suppress("removal") | ||
|
||
package de.mannodermaus.junit5.testutil.reflect | ||
|
||
import sun.misc.Unsafe | ||
import java.lang.reflect.Field | ||
import java.lang.reflect.Modifier | ||
import java.security.AccessController | ||
import java.security.PrivilegedAction | ||
|
||
/** | ||
* Adapted from Paparazzi: | ||
* https://github.com/cashapp/paparazzi/blob/137f5ca5f3a9949336012298a7c2838fc669c01a/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Reflections.kt | ||
*/ | ||
public fun Class<*>.getFieldReflectively(fieldName: String): Field = | ||
try { | ||
this.getDeclaredField(fieldName).also { it.isAccessible = true } | ||
} catch (e: NoSuchFieldException) { | ||
throw RuntimeException("Field '$fieldName' was not found in class $name.") | ||
} | ||
|
||
public fun Field.setStaticValue(value: Any?) { | ||
try { | ||
this.isAccessible = true | ||
val isFinalModifierPresent = this.modifiers and Modifier.FINAL == Modifier.FINAL | ||
if (isFinalModifierPresent) { | ||
AccessController.doPrivileged<Any?>( | ||
PrivilegedAction { | ||
try { | ||
val unsafe = | ||
Unsafe::class.java.getFieldReflectively("theUnsafe").get(null) as Unsafe | ||
val offset = unsafe.staticFieldOffset(this) | ||
val base = unsafe.staticFieldBase(this) | ||
unsafe.setFieldValue(this, base, offset, value) | ||
null | ||
} catch (t: Throwable) { | ||
throw RuntimeException(t) | ||
} | ||
} | ||
) | ||
} else { | ||
this.set(null, value) | ||
} | ||
} catch (ex: SecurityException) { | ||
throw RuntimeException(ex) | ||
} catch (ex: IllegalAccessException) { | ||
throw RuntimeException(ex) | ||
} catch (ex: IllegalArgumentException) { | ||
throw RuntimeException(ex) | ||
} | ||
} | ||
|
||
private fun Unsafe.setFieldValue(field: Field, base: Any, offset: Long, value: Any?) = | ||
when (field.type) { | ||
Integer.TYPE -> this.putInt(base, offset, (value as Int)) | ||
java.lang.Short.TYPE -> this.putShort(base, offset, (value as Short)) | ||
java.lang.Long.TYPE -> this.putLong(base, offset, (value as Long)) | ||
java.lang.Byte.TYPE -> this.putByte(base, offset, (value as Byte)) | ||
java.lang.Boolean.TYPE -> this.putBoolean(base, offset, (value as Boolean)) | ||
java.lang.Float.TYPE -> this.putFloat(base, offset, (value as Float)) | ||
java.lang.Double.TYPE -> this.putDouble(base, offset, (value as Double)) | ||
Character.TYPE -> this.putChar(base, offset, (value as Char)) | ||
else -> this.putObject(base, offset, value) | ||
} |
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