diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/BUCK index 5cf6affe9..6814e3aa3 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/BUCK +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/BUCK @@ -2,7 +2,10 @@ include_defs("//ReactAndroid/DEFS") android_library( name = "testing", - srcs = glob(["**/*.java"]), + srcs = glob( + ["**/*.java"], + excludes = ["idledetection/**/*.java"], + ), visibility = [ "PUBLIC", ], @@ -25,5 +28,6 @@ android_library( react_native_target("java/com/facebook/react/modules/debug:interfaces"), react_native_target("java/com/facebook/react/shell:shell"), react_native_target("java/com/facebook/react/uimanager:uimanager"), + react_native_integration_tests_target("java/com/facebook/react/testing/idledetection:idledetection"), ], ) diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppInstrumentationTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppInstrumentationTestCase.java index aaba651de..eeb8c02e0 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppInstrumentationTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppInstrumentationTestCase.java @@ -20,6 +20,7 @@ import android.view.ViewGroup; import com.facebook.infer.annotation.Assertions; import com.facebook.react.bridge.ReactContext; +import com.facebook.react.testing.idledetection.IdleWaiter; /** * Base class for instrumentation tests that runs React based react application in UI mode @@ -123,7 +124,6 @@ public abstract class ReactAppInstrumentationTestCase extends } }; - getActivity().runOnUiThread(getScreenshotRunnable); try { if (!latch.await(5000, TimeUnit.MILLISECONDS)) { diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java index 022e7826c..11edda17a 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactAppTestActivity.java @@ -28,6 +28,8 @@ import com.facebook.react.bridge.ReactContext; import com.facebook.react.common.LifecycleState; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; import com.facebook.react.shell.MainReactPackage; +import com.facebook.react.testing.idledetection.ReactBridgeIdleSignaler; +import com.facebook.react.testing.idledetection.ReactIdleDetectionUtil; import com.facebook.react.uimanager.UIImplementationProvider; public class ReactAppTestActivity extends FragmentActivity implements diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIdleDetectionUtil.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIdleDetectionUtil.java index 49e221932..af6ca2ebb 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIdleDetectionUtil.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIdleDetectionUtil.java @@ -6,7 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -package com.facebook.react.testing; +package com.facebook.react.testing.idledetection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java index 752c89ea6..77f5492c3 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactIntegrationTestCase.java @@ -32,6 +32,8 @@ import com.facebook.react.common.ApplicationHolder; import com.facebook.react.common.futures.SimpleSettableFuture; import com.facebook.react.devsupport.interfaces.DevSupportManager; import com.facebook.react.modules.core.Timing; +import com.facebook.react.testing.idledetection.ReactBridgeIdleSignaler; +import com.facebook.react.testing.idledetection.ReactIdleDetectionUtil; import com.facebook.soloader.SoLoader; import static org.mockito.Mockito.mock; diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/SingleTouchGestureGenerator.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/SingleTouchGestureGenerator.java index 278facbcb..f0ae877a9 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/SingleTouchGestureGenerator.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/SingleTouchGestureGenerator.java @@ -13,6 +13,8 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; +import com.facebook.react.testing.idledetection.IdleWaiter; + /** * Provides methods for generating touch events and dispatching them directly to a given view. * Events scenarios are based on {@link android.test.TouchUtils} but they get gets dispatched diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/BUCK new file mode 100644 index 000000000..f6b335357 --- /dev/null +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/BUCK @@ -0,0 +1,14 @@ +include_defs("//ReactAndroid/DEFS") + +android_library( + name = "idledetection", + srcs = glob(["**/*.java"]), + visibility = [ + "PUBLIC", + ], + deps = [ + react_native_dep("third-party/java/testing-support-lib:runner"), + react_native_target("java/com/facebook/react/bridge:bridge"), + react_native_target("java/com/facebook/react/modules/core:core"), + ], +) diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/IdleWaiter.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/IdleWaiter.java similarity index 90% rename from ReactAndroid/src/androidTest/java/com/facebook/react/testing/IdleWaiter.java rename to ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/IdleWaiter.java index 98884c103..1b94b7c1f 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/IdleWaiter.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/IdleWaiter.java @@ -6,7 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -package com.facebook.react.testing; +package com.facebook.react.testing.idledetection; /** * Interface for something that knows how to wait for bridge and UI idle. diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactBridgeIdleSignaler.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/ReactBridgeIdleSignaler.java similarity index 97% rename from ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactBridgeIdleSignaler.java rename to ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/ReactBridgeIdleSignaler.java index ffd941f9a..4aaa451e4 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/ReactBridgeIdleSignaler.java +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/ReactBridgeIdleSignaler.java @@ -6,7 +6,7 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -package com.facebook.react.testing; +package com.facebook.react.testing.idledetection; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/ReactIdleDetectionUtil.java b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/ReactIdleDetectionUtil.java new file mode 100644 index 000000000..af6ca2ebb --- /dev/null +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/testing/idledetection/ReactIdleDetectionUtil.java @@ -0,0 +1,125 @@ +/** + * Copyright (c) 2014-present, Facebook, Inc. + * All rights reserved. + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +package com.facebook.react.testing.idledetection; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import android.app.Instrumentation; +import android.os.SystemClock; +import android.support.test.InstrumentationRegistry; + +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.bridge.UiThreadUtil; +import com.facebook.react.modules.core.ChoreographerCompat; + +public class ReactIdleDetectionUtil { + + /** + * Waits for both the UI thread and bridge to be idle. It determines this by waiting for the + * bridge to become idle, then waiting for the UI thread to become idle, then checking if the + * bridge is idle again (if the bridge was idle before and is still idle after running the UI + * thread to idle, then there are no more events to process in either place). + *

+ * Also waits for any Choreographer callbacks to run after the initial sync since things like UI + * events are initiated from Choreographer callbacks. + */ + public static void waitForBridgeAndUIIdle( + ReactBridgeIdleSignaler idleSignaler, + final ReactContext reactContext, + long timeoutMs) { + UiThreadUtil.assertNotOnUiThread(); + + long startTime = SystemClock.uptimeMillis(); + waitInner(idleSignaler, timeoutMs); + + long timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime)); + waitForChoreographer(timeToWait); + waitForJSIdle(reactContext); + + timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime)); + waitInner(idleSignaler, timeToWait); + timeToWait = Math.max(1, timeoutMs - (SystemClock.uptimeMillis() - startTime)); + waitForChoreographer(timeToWait); + } + + private static void waitForChoreographer(long timeToWait) { + final int waitFrameCount = 2; + final CountDownLatch latch = new CountDownLatch(1); + UiThreadUtil.runOnUiThread( + new Runnable() { + @Override + public void run() { + ChoreographerCompat.getInstance().postFrameCallback( + new ChoreographerCompat.FrameCallback() { + + private int frameCount = 0; + + @Override + public void doFrame(long frameTimeNanos) { + frameCount++; + if (frameCount == waitFrameCount) { + latch.countDown(); + } else { + ChoreographerCompat.getInstance().postFrameCallback(this); + } + } + }); + } + }); + try { + if (!latch.await(timeToWait, TimeUnit.MILLISECONDS)) { + throw new RuntimeException("Timed out waiting for Choreographer"); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void waitForJSIdle(ReactContext reactContext) { + if (!reactContext.hasActiveCatalystInstance()) { + return; + } + final CountDownLatch latch = new CountDownLatch(1); + + reactContext.runOnJSQueueThread( + new Runnable() { + @Override + public void run() { + latch.countDown(); + } + }); + + try { + if (!latch.await(5000, TimeUnit.MILLISECONDS)) { + throw new RuntimeException("Timed out waiting for JS thread"); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private static void waitInner(ReactBridgeIdleSignaler idleSignaler, long timeToWait) { + // TODO gets broken in gradle, do we need it? + Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); + long startTime = SystemClock.uptimeMillis(); + boolean bridgeWasIdle = false; + while (SystemClock.uptimeMillis() - startTime < timeToWait) { + boolean bridgeIsIdle = idleSignaler.isBridgeIdle(); + if (bridgeIsIdle && bridgeWasIdle) { + return; + } + bridgeWasIdle = bridgeIsIdle; + long newTimeToWait = Math.max(1, timeToWait - (SystemClock.uptimeMillis() - startTime)); + idleSignaler.waitForIdle(newTimeToWait); + instrumentation.waitForIdleSync(); + } + throw new RuntimeException("Timed out waiting for bridge and UI idle!"); + } +} diff --git a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK index 8188c87c6..6e07e5fd2 100644 --- a/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK +++ b/ReactAndroid/src/androidTest/java/com/facebook/react/tests/BUCK @@ -5,6 +5,7 @@ deps = [ react_native_dep("third-party/java/jsr-305:jsr-305"), react_native_dep("third-party/java/junit:junit"), react_native_integration_tests_target("java/com/facebook/react/testing:testing"), + react_native_integration_tests_target("java/com/facebook/react/testing/idledetection:idledetection"), react_native_target("java/com/facebook/react:react"), react_native_target("java/com/facebook/react/bridge:bridge"), react_native_target("java/com/facebook/react/common:common"),