From 22764e6cdcf45ca5930676f6e95f9ab2f82bc78d Mon Sep 17 00:00:00 2001 From: Emily Janzer Date: Tue, 7 Jan 2020 15:36:37 -0800 Subject: [PATCH] Add an API for Detox to check if there are any timers expiring in a certain range (#27539) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/27539 Detox currently relies on reflection to inspect the private timers queue in the Timing module to check if React Native is idle or not. This broke when I renamed TimingModule and moved the queue to JavaTimerManager in D17260848 and D17282187. A better solution to this problem is for us to expose a public API for checking the timers queue from TimingModule, so that Detox doesn't need to access private fields. Using similar logic to Detox's TimersIdlingResource: https://github.com/wix/Detox/blob/4f81a77baee4e4542a693922bcbde621d9d8c1a5/detox/android/detox/src/main/java/com/wix/detox/reactnative/idlingresources/TimersIdlingResource.kt#L95 Changelog: [Android] [Added] Added an API for checking if there are busy timers to TimingModule Reviewed By: makovkastar Differential Revision: D19128786 fbshipit-source-id: 835ae214eba58879c8343255bba680a81801ce03 --- .../react/modules/core/JavaTimerManager.java | 19 +++++++++++++++++++ .../react/modules/core/TimingModule.java | 6 ++++++ .../test/java/com/facebook/react/modules/BUCK | 1 + .../modules/timing/TimingModuleTest.java | 14 ++++++++++++++ 4 files changed, 40 insertions(+) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java index 1766d089a1c..37548d4c46a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/JavaTimerManager.java @@ -373,4 +373,23 @@ public class JavaTimerManager { } }); } + + /** + * Returns a bool representing whether there are any active timers that will be fired within a + * certain period of time. Disregards repeating timers (setInterval). Used for testing to + * determine if RN is idle. + * + * @param rangeMs The time range, in ms, to check + * @return True if there are pending timers within the given range; false otherwise + */ + /* package */ boolean hasActiveTimersInRange(long rangeMs) { + synchronized (mTimerGuard) { + for (Timer timer : mTimers) { + if (!timer.mRepeat && timer.mInterval < rangeMs) { + return true; + } + } + } + return false; + } } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.java index dbbbf843b38..8ac7f07606a 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/core/TimingModule.java @@ -11,6 +11,7 @@ import com.facebook.fbreact.specs.NativeTimingSpec; import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.WritableArray; +import com.facebook.react.common.annotations.VisibleForTesting; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.jstasks.HeadlessJsTaskContext; import com.facebook.react.jstasks.HeadlessJsTaskEventListener; @@ -134,4 +135,9 @@ public final class TimingModule extends NativeTimingSpec headlessJsTaskContext.removeTaskEventListener(this); mJavaTimerManager.onInstanceDestroy(); } + + @VisibleForTesting + public boolean hasActiveTimersInRange(long rangeMs) { + return mJavaTimerManager.hasActiveTimersInRange(rangeMs); + } } diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK b/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK index 4cd9e7446e9..3b5337ebbb6 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/BUCK @@ -28,6 +28,7 @@ rn_robolectric_test( react_native_target("java/com/facebook/react/common/network:network"), react_native_target("java/com/facebook/react/devsupport:interfaces"), react_native_target("java/com/facebook/react/jstasks:jstasks"), + react_native_target("java/com/facebook/react/module/annotations:annotations"), react_native_target("java/com/facebook/react/modules/blob:blob"), react_native_target("java/com/facebook/react/modules/camera:camera"), react_native_target("java/com/facebook/react/modules/clipboard:clipboard"), diff --git a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java index 1656a6a23ab..7a98a0f2ba8 100644 --- a/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java +++ b/ReactAndroid/src/test/java/com/facebook/react/modules/timing/TimingModuleTest.java @@ -7,6 +7,7 @@ package com.facebook.react.modules.timing; +import static org.fest.assertions.api.Assertions.assertThat; import static org.mockito.Mockito.*; import com.facebook.react.bridge.Arguments; @@ -254,6 +255,19 @@ public class TimingModuleTest { verify(mJSTimersMock).callIdleCallbacks(SystemClock.currentTimeMillis()); } + @Test + public void testActiveTimersInRange() { + mTimingModule.onHostResume(); + assertThat(mTimingModule.hasActiveTimersInRange(100)).isFalse(); + + mTimingModule.createTimer(41, 1, 0, true); + assertThat(mTimingModule.hasActiveTimersInRange(100)).isFalse(); // Repeating + + mTimingModule.createTimer(42, 150, 0, false); + assertThat(mTimingModule.hasActiveTimersInRange(100)).isFalse(); // Out of range + assertThat(mTimingModule.hasActiveTimersInRange(200)).isTrue(); // In range + } + private static class PostFrameIdleCallbackHandler implements Answer { private ChoreographerCompat.FrameCallback mFrameCallback;