Skip to content

Commit

Permalink
Merge pull request #1085 from wix/kotlin-rntimer-idling-resource
Browse files Browse the repository at this point in the history
Introduce kotlin and reimplement RN-timers idling resource
  • Loading branch information
d4vidi authored Jan 14, 2019
2 parents 6d5e00c + dc20527 commit c3ab455
Show file tree
Hide file tree
Showing 18 changed files with 468 additions and 235 deletions.
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Change Log

## [10.0.0]((https://github.com/wix/detox/tree/9.1.2)) (2019-01-13)
[Full Changelog](https://github.com/wix/detox/compare/9.1.2...10.0.0)

**Enhancements**
- Android: Introduce Kotlin (v1.3.0 as the default version).
- Android: Rewrite of the JS-timers idling resource in Kotlin (`ReactNativeTimersIdlingResource`).

**Fixed Bugs**
- Idle timer shadows busy timers at rare cases [\#1115](https://github.com/wix/Detox/issues/1115)

## [9.1.2](https://github.com/wix/detox/tree/9.1.2) (2018-11-17)
[Full Changelog](https://github.com/wix/detox/compare/9.0.7...9.1.2)

Expand Down Expand Up @@ -1191,4 +1201,4 @@



\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*
4 changes: 4 additions & 0 deletions detox/android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
buildscript {
ext.kotlinVersion = '1.3.0'
ext.detoxKotlinVerion = ext.kotlinVersion

repositories {
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion"
}
}

Expand Down
17 changes: 12 additions & 5 deletions detox/android/detox/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

def _ext = rootProject.ext;
def _ext = rootProject.ext

def _compileSdkVersion = _ext.has('compileSdkVersion') ? _ext.compileSdkVersion : 25;
def _buildToolsVersion = _ext.has('buildToolsVersion') ? _ext.buildToolsVersion : '27.0.3';
def _minSdkVersion = _ext.has('minSdkVersion') ? _ext.minSdkVersion : 18;
def _targetSdkVersion = _ext.has('targetSdkVersion') ? _ext.targetSdkVersion : 25;
def _compileSdkVersion = _ext.has('compileSdkVersion') ? _ext.compileSdkVersion : 25
def _buildToolsVersion = _ext.has('buildToolsVersion') ? _ext.buildToolsVersion : '27.0.3'
def _minSdkVersion = _ext.has('minSdkVersion') ? _ext.minSdkVersion : 18
def _targetSdkVersion = _ext.has('targetSdkVersion') ? _ext.targetSdkVersion : 25
def _kotlinVersion = _ext.has('detoxKotlinVersion') ? _ext.detoxKotlinVersion : '1.3.0'

android {
compileSdkVersion _compileSdkVersion
Expand Down Expand Up @@ -64,6 +67,8 @@ android {
}

dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$_kotlinVersion"

minReactNative44Implementation 'com.squareup.okhttp3:okhttp:3.4.1'
minReactNative44Implementation 'com.squareup.okhttp3:okhttp-ws:3.4.1'

Expand All @@ -84,4 +89,6 @@ dependencies {
testImplementation 'junit:junit:4.12'
testImplementation 'org.assertj:assertj-core:3.8.0'
testImplementation 'org.apache.commons:commons-io:1.3.2'
testImplementation 'com.nhaarman:mockito-kotlin:1.4.0'
testImplementation "com.facebook.react:react-native:+"
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package com.wix.detox.espresso

import android.support.test.espresso.IdlingResource
import android.view.Choreographer
import com.facebook.react.bridge.ReactContext
import com.facebook.react.modules.core.Timing
import org.joor.Reflect
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean

const val BUSY_WINDOW_THRESHOLD = 1500

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()
}

class TimingModuleReflected(reactContext: ReactContext) {
private var nativeModule = reactContext.getNativeModule(Timing::class.java)

val timersQueue: PriorityQueue<Any>
get() = Reflect.on(nativeModule).field("mTimers").get()

val timersLock: Object
get() = Reflect.on(nativeModule).field("mTimerGuard").get()

operator fun component1() = timersQueue
operator fun component2() = timersLock
}

class ReactNativeTimersIdlingResource @JvmOverloads constructor(
private val reactContext: ReactContext,
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().apply {
val result = this
if (result) {
callback?.onTransitionToIdle()
} else {
getChoreographer().postFrameCallback(this@ReactNativeTimersIdlingResource)
}
}
}

override fun doFrame(frameTimeNanos: Long) {
callback?.let {
isIdleNow
}
}

public fun pause() {
paused.set(true)
callback?.onTransitionToIdle()
}

public fun resume() {
paused.set(false)
}

private fun checkIdle(): Boolean {
val now = System.nanoTime() / 1000000L
val (timersQueue, timersLock) = TimingModuleReflected(reactContext)

synchronized(timersLock) {
val nextTimer = timersQueue.peek()
nextTimer?.let {
return !isTimerInBusyWindow(it, now) && !hasBusyTimers(timersQueue, now)
}
return true
}
}

private fun isTimerInBusyWindow(timer: Any, now: Long): Boolean {
val timerReflected = TimerReflected(timer)
return when {
timerReflected.isRepeating -> false
timerReflected.targetTime < now -> false
timerReflected.interval > BUSY_WINDOW_THRESHOLD -> false
else -> true
}
}

private fun hasBusyTimers(timersQueue: PriorityQueue<Any>, now: Long): Boolean {
timersQueue.forEach {
if (isTimerInBusyWindow(it, now)) {
return true
}
}
return false
}
}
Loading

0 comments on commit c3ab455

Please sign in to comment.