Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce kotlin and reimplement RN-timers idling resource #1085

Merged
merged 4 commits into from
Jan 14, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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