mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-02 22:41:18 +08:00
Android Instrumentations tests are ready to be run in github/CI open source environment
Reviewed By: mkonicek Differential Revision: D2769217 fb-gh-sync-id: 7469af816241d8b642753cca21f6542b971e9572
This commit is contained in:
committed by
facebook-github-bot-9
parent
040909904c
commit
a99c5160ee
@@ -0,0 +1,212 @@
|
||||
/**
|
||||
* 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;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.facebook.infer.annotation.Assertions;
|
||||
import com.facebook.react.bridge.BaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.CatalystInstanceImpl;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.SoftAssertions;
|
||||
import com.facebook.react.bridge.UiThreadUtil;
|
||||
import com.facebook.react.common.futures.SimpleSettableFuture;
|
||||
import com.facebook.react.modules.core.Timing;
|
||||
|
||||
/**
|
||||
* Use this class for writing integration tests of catalyst. This class will run all JNI call
|
||||
* within separate android looper, thus you don't need to care about starting your own looper.
|
||||
*
|
||||
* Keep in mind that all JS remote method calls and script load calls are asynchronous and you
|
||||
* should not expect them to return results immediately.
|
||||
*
|
||||
* In order to write catalyst integration:
|
||||
* 1) Make {@link ReactIntegrationTestCase} a base class of your test case
|
||||
* 2) Use {@link ReactIntegrationTestCase.ReactTestInstanceBuilder}
|
||||
* instead of {@link com.facebook.react.bridge.CatalystInstanceImpl.Builder} to build catalyst
|
||||
* instance for testing purposes
|
||||
*
|
||||
*/
|
||||
public abstract class ReactIntegrationTestCase extends AndroidTestCase {
|
||||
|
||||
private static final long SETUP_TIMEOUT_MS = 5000;
|
||||
private static final long IDLE_TIMEOUT_MS = 15000;
|
||||
|
||||
private @Nullable CatalystInstanceImpl mInstance;
|
||||
private @Nullable ReactBridgeIdleSignaler mBridgeIdleSignaler;
|
||||
private @Nullable ReactApplicationContext mReactContext;
|
||||
|
||||
@Override
|
||||
public ReactApplicationContext getContext() {
|
||||
if (mReactContext == null) {
|
||||
mReactContext = new ReactApplicationContext(super.getContext());
|
||||
Assertions.assertNotNull(mReactContext);
|
||||
}
|
||||
|
||||
return mReactContext;
|
||||
}
|
||||
|
||||
public void shutDownContext() {
|
||||
if (mInstance != null) {
|
||||
final ReactContext contextToDestroy = mReactContext;
|
||||
mReactContext = null;
|
||||
mInstance = null;
|
||||
|
||||
UiThreadUtil.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (contextToDestroy != null) {
|
||||
contextToDestroy.onDestroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method isn't safe since it doesn't factor in layout-only view removal. Use
|
||||
* {@link #getViewByTestId} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public <T extends View> T getViewAtPath(ViewGroup rootView, int... path) {
|
||||
return ReactTestHelper.getViewAtPath(rootView, path);
|
||||
}
|
||||
|
||||
public <T extends View> T getViewByTestId(ViewGroup rootView, String testID) {
|
||||
return (T) ReactTestHelper.getViewWithReactTestId(rootView, testID);
|
||||
}
|
||||
|
||||
public static class Event {
|
||||
private final CountDownLatch mLatch;
|
||||
|
||||
public Event() {
|
||||
this(1);
|
||||
}
|
||||
|
||||
public Event(int counter) {
|
||||
mLatch = new CountDownLatch(counter);
|
||||
}
|
||||
|
||||
public void occur() {
|
||||
mLatch.countDown();
|
||||
}
|
||||
|
||||
public boolean didOccur() {
|
||||
return mLatch.getCount() == 0;
|
||||
}
|
||||
|
||||
public boolean await(long millis) {
|
||||
try {
|
||||
return mLatch.await(millis, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException ignore) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Timing module needs to be created on the main thread so that it gets the correct Choreographer.
|
||||
*/
|
||||
protected Timing createTimingModule() {
|
||||
final SimpleSettableFuture<Timing> simpleSettableFuture = new SimpleSettableFuture<Timing>();
|
||||
UiThreadUtil.runOnUiThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Timing timing = new Timing(getContext());
|
||||
simpleSettableFuture.set(timing);
|
||||
}
|
||||
});
|
||||
try {
|
||||
return simpleSettableFuture.get(5000, TimeUnit.MILLISECONDS);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public class ReactTestInstanceBuilder extends CatalystInstanceImpl.Builder {
|
||||
|
||||
@Override
|
||||
public CatalystInstanceImpl build() {
|
||||
// Call build in separate looper and wait for it to finish before returning
|
||||
final Event setupEvent = new Event();
|
||||
UiThreadUtil.runOnUiThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mInstance = ReactTestInstanceBuilder.super.build();
|
||||
mBridgeIdleSignaler = new ReactBridgeIdleSignaler();
|
||||
mInstance.addBridgeIdleDebugListener(mBridgeIdleSignaler);
|
||||
getContext().initializeWithInstance(mInstance);
|
||||
setupEvent.occur();
|
||||
}
|
||||
});
|
||||
|
||||
if (!setupEvent.await(SETUP_TIMEOUT_MS)) {
|
||||
throw new RuntimeException(
|
||||
"Instance setup should take less than " + SETUP_TIMEOUT_MS + "ms");
|
||||
}
|
||||
return mInstance;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean waitForBridgeIdle(long millis) {
|
||||
return Assertions.assertNotNull(mBridgeIdleSignaler).waitForIdle(millis);
|
||||
}
|
||||
|
||||
public void waitForIdleSync() {
|
||||
InstrumentationRegistry.getInstrumentation().waitForIdleSync();
|
||||
}
|
||||
|
||||
public void waitForBridgeAndUIIdle() {
|
||||
ReactIdleDetectionUtil.waitForBridgeAndUIIdle(
|
||||
Assertions.assertNotNull(mBridgeIdleSignaler),
|
||||
getContext(),
|
||||
IDLE_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
shutDownContext();
|
||||
}
|
||||
|
||||
protected static void initializeJavaModule(final BaseJavaModule javaModule) {
|
||||
final Semaphore semaphore = new Semaphore(0);
|
||||
UiThreadUtil.runOnUiThread(
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
javaModule.initialize();
|
||||
if (javaModule instanceof LifecycleEventListener) {
|
||||
((LifecycleEventListener) javaModule).onHostResume();
|
||||
}
|
||||
semaphore.release();
|
||||
}
|
||||
});
|
||||
try {
|
||||
SoftAssertions.assertCondition(
|
||||
semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS),
|
||||
"Timed out initializing timing module");
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user