From 8404a4d339af045c4379cba6c040ba26264feceb Mon Sep 17 00:00:00 2001 From: d4vidi Date: Tue, 20 Nov 2018 20:44:03 +0200 Subject: [PATCH] Android: Improve RN-timers idling resource such that it would skip over interval-timers (#1048) --- .../ReactNativeTimersIdlingResource.java | 40 +++++++++----- detox/test/e2e/09.stress-timeouts.test.js | 5 ++ detox/test/src/Screens/TimeoutsScreen.js | 53 +++++++++++-------- 3 files changed, 64 insertions(+), 34 deletions(-) diff --git a/detox/android/detox/src/main/java/com/wix/detox/espresso/ReactNativeTimersIdlingResource.java b/detox/android/detox/src/main/java/com/wix/detox/espresso/ReactNativeTimersIdlingResource.java index ead257410a..013c99b469 100644 --- a/detox/android/detox/src/main/java/com/wix/detox/espresso/ReactNativeTimersIdlingResource.java +++ b/detox/android/detox/src/main/java/com/wix/detox/espresso/ReactNativeTimersIdlingResource.java @@ -90,11 +90,11 @@ public boolean isIdleNow() { return true; } - Object timingModule = Reflect.on(reactContext).call(METHOD_GET_NATIVE_MODULE, timingClass).get(); - Object timerLock = Reflect.on(timingModule).field(LOCK_TIMER).get(); + final Object timingModule = Reflect.on(reactContext).call(METHOD_GET_NATIVE_MODULE, timingClass).get(); + final Object timerLock = Reflect.on(timingModule).field(LOCK_TIMER).get(); synchronized (timerLock) { final PriorityQueue timers = Reflect.on(timingModule).field(FIELD_TIMERS).get(); - final Object nextTimer = timers.peek(); + final Object nextTimer = findNextTimer(timers); if (nextTimer == null) { if (callback != null) { callback.onTransitionToIdle(); @@ -147,22 +147,38 @@ public void resume() { paused.set(false); } + private Object findNextTimer(PriorityQueue timers) { + Object nextTimer = timers.peek(); + if (nextTimer == null) { + return null; + } + + final boolean isRepetitive = Reflect.on(nextTimer).field(TIMER_FIELD_REPETITIVE).get(); + if (!isRepetitive) { + return nextTimer; + } + + Object timer = null; + long targetTime = Long.MAX_VALUE; + for (Object aTimer : timers) { + final boolean timerIsRepetitive = Reflect.on(aTimer).field(TIMER_FIELD_REPETITIVE).get(); + final long timerTargetTime = Reflect.on(aTimer).field(TIMER_FIELD_TARGET_TIME).get(); + if (!timerIsRepetitive && timerTargetTime < targetTime) { + targetTime = timerTargetTime; + timer = aTimer; + } + } + return timer; + } + private boolean isTimerOutsideBusyWindow(Object nextTimer) { final long currentTimeMS = System.nanoTime() / 1000000L; final Reflect nextTimerReflected = Reflect.on(nextTimer); final long targetTimeMS = nextTimerReflected.field(TIMER_FIELD_TARGET_TIME).get(); final int intervalMS = nextTimerReflected.field(TIMER_FIELD_INTERVAL).get(); - final boolean isRepetitive = nextTimerReflected.field(TIMER_FIELD_REPETITIVE).get(); // Log.i(LOG_TAG, "Next timer has duration of: " + intervalMS -// + "; due time is: " + targetTimeMS + ", current is: " + currentTimeMS -// + "; is " + (isRepetitive ? "repeating" : "a one-shot")); - - // Before making any concrete checks, be sure to ignore repeating timers or we'd loop forever. - // TODO: Should we iterate to the first, non-repeating timer? - if (isRepetitive) { - return true; - } +// + "; due time is: " + targetTimeMS + ", current is: " + currentTimeMS); // Core condition is for the timer interval (duration) to be set beyond our window. // Note: we check the interval in an 'absolute' way rather than comparing to the 'current time' diff --git a/detox/test/e2e/09.stress-timeouts.test.js b/detox/test/e2e/09.stress-timeouts.test.js index af2c72a91d..b10d529689 100644 --- a/detox/test/e2e/09.stress-timeouts.test.js +++ b/detox/test/e2e/09.stress-timeouts.test.js @@ -33,4 +33,9 @@ describe('StressTimeouts', () => { await element(by.id('IntervalIgnore')).tap(); await expect(element(by.text('Interval Ignored!!!'))).toBeVisible(); }); + + it('should skip over setInterval', async () => { + await element(by.id('SkipOverInterval')).tap(); + await expect(element(by.text('Interval Skipped-Over!!!'))).toBeVisible(); + }); }); diff --git a/detox/test/src/Screens/TimeoutsScreen.js b/detox/test/src/Screens/TimeoutsScreen.js index 0a25e9dba7..83d731289f 100644 --- a/detox/test/src/Screens/TimeoutsScreen.js +++ b/detox/test/src/Screens/TimeoutsScreen.js @@ -44,6 +44,10 @@ export default class TimeoutsScreen extends Component { Interval Ignore + + Interval Skip-Over + + ); } @@ -59,37 +63,42 @@ export default class TimeoutsScreen extends Component { } onTimeoutButtonPress(greeting, timeout) { - setTimeout(() => { - this.setState({ - greeting: greeting - }); - }, timeout); + setTimeout(() => this.setState({greeting}), timeout); } onTimeoutIgnoreButtonPress(greeting, timeout) { - setTimeout(() => { - console.log('this will happen soon'); - }, timeout); - this.setState({ - greeting: greeting - }); + setTimeout(() => console.log('this will happen soon'), timeout); + this.setState({greeting}); } onImmediateButtonPress(greeting) { - setImmediate(() => { - this.setState({ - greeting: greeting - }); - }); + setImmediate(() => this.setState({greeting})); } onIntervalIgnoreButtonPress(greeting, interval) { - setInterval(() => { - console.log('this is recurring'); - }, interval); - this.setState({ - greeting: greeting - }); + setInterval(() => console.log('this is recurring'), interval); + this.setState({greeting}); } + onIntervalSkipOver(greeting) { + const busyPeriodTimeoutHandler = () => this.setState({greeting}); + const idledTimeoutHandler = (timeMs) => console.log(`Non-busy keeping timer of ${timeMs}ms has expired`); + + const interval = 88; + const busyKeepingTime = 600; + const idledTimeBase = 1500 + 1; + const foreverTimer = 2500; + let intervalId; + + intervalId = setInterval(() => console.log(`this should show every ${interval}ms`), interval); + setTimeout(() => idledTimeoutHandler(idledTimeBase), idledTimeBase); + setTimeout(() => busyPeriodTimeoutHandler(), busyKeepingTime); + setTimeout(() => idledTimeoutHandler(idledTimeBase + 10), idledTimeBase + 10); + setTimeout(() => idledTimeoutHandler(idledTimeBase + 100), idledTimeBase + 100); + setTimeout(() => { + console.log('"Forever" timer expired - though it shouldn\'t have!'); + clearInterval(intervalId); + this.setState({greeting: '???'}); + }, foreverTimer); + } }