forked from wix/Detox
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request wix#1858 from wix/rn62-timers-idle-res
Introduce a flexible impl strategy for TimersIdlingResource, paving way for RN 62
- Loading branch information
Showing
11 changed files
with
569 additions
and
375 deletions.
There are no files selected for viewing
11 changes: 11 additions & 0 deletions
11
detox/android/detox/src/main/java/com/wix/detox/common/RNAnnotations.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,11 @@ | ||
package com.wix.detox.common | ||
|
||
import kotlin.annotation.AnnotationTarget.* | ||
|
||
/** | ||
* Source-annotation, indicating that some changes need to be made once the associated RN version | ||
* (or higher) becomes the one minimally supported by Detox Android. | ||
*/ | ||
@Target(FUNCTION, CLASS, CONSTRUCTOR, PROPERTY_GETTER, PROPERTY_SETTER, PROPERTY, FIELD, FILE) | ||
@Retention(AnnotationRetention.SOURCE) | ||
annotation class RNDropSupportTodo(val rnMajorVersion: Int, val message: String) |
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
112 changes: 0 additions & 112 deletions
112
...oid/detox/src/main/java/com/wix/detox/reactnative/idlingresources/TimersIdlingResource.kt
This file was deleted.
Oops, something went wrong.
76 changes: 76 additions & 0 deletions
76
...java/com/wix/detox/reactnative/idlingresources/timers/DefaultIdleInterrogationStrategy.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,76 @@ | ||
@file:RNDropSupportTodo(62, "Remove all of this; Use DelegatedIdleInterrogationStrategy, instead.") | ||
|
||
package com.wix.detox.reactnative.idlingresources.timers | ||
|
||
import com.facebook.react.bridge.NativeModule | ||
import com.facebook.react.bridge.ReactContext | ||
import com.wix.detox.common.RNDropSupportTodo | ||
import org.joor.Reflect | ||
import java.util.* | ||
|
||
private const val BUSY_WINDOW_THRESHOLD = 1500 | ||
|
||
private class TimerReflected(timer: Any) { | ||
private var reflected = Reflect.on(timer) | ||
|
||
val isRepeating: Boolean | ||
get() = reflected.field("mRepeat").get() | ||
val interval: Int | ||
get() = reflected.field("mInterval").get() | ||
val targetTime: Long | ||
get() = reflected.field("mTargetTime").get() | ||
} | ||
|
||
private class TimingModuleReflected(private val nativeModule: NativeModule) { | ||
val timersQueue: PriorityQueue<Any> | ||
get() = Reflect.on(nativeModule).field("mTimers").get() | ||
val timersLock: Any | ||
get() = Reflect.on(nativeModule).field("mTimerGuard").get() | ||
|
||
operator fun component1() = timersQueue | ||
operator fun component2() = timersLock | ||
} | ||
|
||
class DefaultIdleInterrogationStrategy | ||
internal constructor(private val timersModule: NativeModule) | ||
: IdleInterrogationStrategy { | ||
|
||
override fun isIdleNow(): Boolean { | ||
val (timersQueue, timersLock) = TimingModuleReflected(timersModule) | ||
synchronized(timersLock) { | ||
val nextTimer = timersQueue.peek() | ||
nextTimer?.let { | ||
return !isTimerInBusyWindow(it) && !hasBusyTimers(timersQueue) | ||
} | ||
return true | ||
} | ||
} | ||
|
||
private fun isTimerInBusyWindow(timer: Any): Boolean { | ||
val timerReflected = TimerReflected(timer) | ||
return when { | ||
timerReflected.isRepeating -> false | ||
timerReflected.interval > BUSY_WINDOW_THRESHOLD -> false | ||
else -> true | ||
} | ||
} | ||
|
||
private fun hasBusyTimers(timersQueue: PriorityQueue<Any>): Boolean { | ||
timersQueue.forEach { | ||
if (isTimerInBusyWindow(it)) { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
companion object { | ||
fun createIfSupported(reactContext: ReactContext): DefaultIdleInterrogationStrategy? = | ||
try { | ||
val timingClass: Class<NativeModule> = Class.forName("com.facebook.react.modules.core.Timing") as Class<NativeModule> | ||
DefaultIdleInterrogationStrategy(reactContext.getNativeModule(timingClass)) | ||
} catch (ex: Exception) { | ||
null | ||
} | ||
} | ||
} |
51 changes: 51 additions & 0 deletions
51
...va/com/wix/detox/reactnative/idlingresources/timers/DelegatedIdleInterrogationStrategy.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,51 @@ | ||
package com.wix.detox.reactnative.idlingresources.timers | ||
|
||
import com.facebook.react.bridge.NativeModule | ||
import com.facebook.react.bridge.ReactContext | ||
import com.wix.detox.common.RNDropSupportTodo | ||
import org.joor.Reflect | ||
|
||
private const val BUSY_WINDOW_THRESHOLD = 1500L | ||
|
||
private class RN62TimingModuleReflected(private val timingModule: NativeModule) { | ||
fun hasActiveTimers(): Boolean = Reflect.on(timingModule).call("hasActiveTimersInRange", BUSY_WINDOW_THRESHOLD).get() | ||
} | ||
|
||
/** | ||
* Delegates the interrogation to the native module itself, added | ||
* [here](https://github.com/facebook/react-native/pull/27539) in the context | ||
* of RN v0.62 (followed by a previous refactor and rename of the class). | ||
*/ | ||
@RNDropSupportTodo(62, """ | ||
When min RN version supported by Detox is 0.62.x (or higher), | ||
| can (and should) remove any usage of reflection here. That | ||
| includes the unit test's stub being used for that reason in particular. | ||
""") | ||
class DelegatedIdleInterrogationStrategy(timingModule: NativeModule): IdleInterrogationStrategy { | ||
private val timingModuleReflected = RN62TimingModuleReflected(timingModule) | ||
|
||
override fun isIdleNow(): Boolean = timingModuleReflected.hasActiveTimers() | ||
|
||
companion object { | ||
fun createIfSupported(reactContext: ReactContext): DelegatedIdleInterrogationStrategy? { | ||
val moduleClass: Class<NativeModule>? | ||
try { | ||
moduleClass = Class.forName("com.facebook.react.modules.core.TimingModule") as Class<NativeModule> | ||
} catch (ex: Exception) { | ||
return null | ||
} | ||
|
||
if (!reactContext.hasNativeModule(moduleClass)) { | ||
return null | ||
} | ||
|
||
try { | ||
moduleClass.getDeclaredMethod("hasActiveTimersInRange", Long::class.java) | ||
} catch (ex: Exception) { | ||
return null | ||
} | ||
|
||
return DelegatedIdleInterrogationStrategy(reactContext.getNativeModule(moduleClass)) | ||
} | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
...c/main/java/com/wix/detox/reactnative/idlingresources/timers/IdleInterrogationStrategy.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,21 @@ | ||
package com.wix.detox.reactnative.idlingresources.timers | ||
|
||
import android.util.Log | ||
import com.facebook.react.bridge.ReactContext | ||
|
||
interface IdleInterrogationStrategy { | ||
fun isIdleNow(): Boolean | ||
} | ||
|
||
fun getInterrogationStrategy(reactContext: ReactContext): IdleInterrogationStrategy? { | ||
DelegatedIdleInterrogationStrategy.createIfSupported(reactContext)?.let { | ||
return it | ||
} | ||
|
||
DefaultIdleInterrogationStrategy.createIfSupported(reactContext)?.let { | ||
return it | ||
} | ||
|
||
Log.e(LOG_TAG, "Failed to determine proper implementation-strategy for timers idling resource") | ||
return null | ||
} |
54 changes: 54 additions & 0 deletions
54
...ox/src/main/java/com/wix/detox/reactnative/idlingresources/timers/TimersIdlingResource.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,54 @@ | ||
package com.wix.detox.reactnative.idlingresources.timers | ||
|
||
import android.view.Choreographer | ||
import androidx.test.espresso.IdlingResource | ||
import java.util.concurrent.atomic.AtomicBoolean | ||
|
||
const val LOG_TAG = "TimersIdlingResource" | ||
|
||
class TimersIdlingResource @JvmOverloads constructor( | ||
private val interrogationStrategy: IdleInterrogationStrategy, | ||
private val getChoreographer: () -> Choreographer = { Choreographer.getInstance() } | ||
) : IdlingResource, Choreographer.FrameCallback { | ||
|
||
private var callback: IdlingResource.ResourceCallback? = null | ||
private var paused = AtomicBoolean(false) | ||
|
||
override fun getName(): String = this.javaClass.name | ||
|
||
override fun registerIdleTransitionCallback(callback: IdlingResource.ResourceCallback?) { | ||
this.callback = callback | ||
getChoreographer().postFrameCallback(this) | ||
} | ||
|
||
override fun isIdleNow(): Boolean { | ||
if (paused.get()) { | ||
return true | ||
} | ||
|
||
return checkIdle().also { result -> | ||
if (result) { | ||
callback?.onTransitionToIdle() | ||
} else { | ||
getChoreographer().postFrameCallback(this@TimersIdlingResource) | ||
} | ||
} | ||
} | ||
|
||
override fun doFrame(frameTimeNanos: Long) { | ||
callback?.let { | ||
isIdleNow | ||
} | ||
} | ||
|
||
fun pause() { | ||
paused.set(true) | ||
callback?.onTransitionToIdle() | ||
} | ||
|
||
fun resume() { | ||
paused.set(false) | ||
} | ||
|
||
private fun checkIdle() = interrogationStrategy.isIdleNow() | ||
} |
Oops, something went wrong.