Skip to content

Commit

Permalink
Android support. Phase I. (#148)
Browse files Browse the repository at this point in the history
* Android support. Phase I.
  • Loading branch information
simonracz authored and rotemmiz committed Jun 12, 2017
1 parent 192e9b4 commit 520d6a3
Show file tree
Hide file tree
Showing 24 changed files with 2,096 additions and 156 deletions.
2 changes: 2 additions & 0 deletions detox/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ ios/EarlGrey/Tests/UnitTests/TestRig/Resources
ios/EarlGrey/Tests/FunctionalTests/TestRig/Resources
Detox.framework
Detox.framework.tar
detox-debug.aar
detox-release.aar

lib

Expand Down
20 changes: 12 additions & 8 deletions detox/android/detox/build.gradle
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
apply plugin: 'com.android.library'

android {
compileSdkVersion 25
buildToolsVersion "25.0.1"
compileSdkVersion 23
buildToolsVersion "23.0.1"
defaultConfig {
minSdkVersion 16
targetSdkVersion 25
targetSdkVersion 22
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
Expand All @@ -16,16 +16,20 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
publishNonDefault true
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile project(':invoke')
compile 'com.android.support:appcompat-v7:25.0.1'
compile 'com.squareup.okhttp3:okhttp:3.5.0'
//compile project(':invoke')
compile "com.android.support:appcompat-v7:23.0.1"
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.squareup.okhttp3:okhttp-ws:3.4.1'
testCompile 'junit:junit:4.12'
compile('com.android.support.test.espresso:espresso-core:2.2.2')
compile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.google.code.findbugs'
})
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
}
}
108 changes: 108 additions & 0 deletions detox/android/detox/src/main/java/com/wix/detox/Delegator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package com.wix.detox;

import org.joor.Reflect;
import org.joor.ReflectException;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
* Created by simonracz on 29/05/2017.
*/

/**
* <p>
* Helper class for InvocationHandlers, which delegates equals, hashCode and toString
* calls to Object.
* </p>
*
* <p>
* Copied from here
* <a href="https://docs.oracle.com/javase/8/docs/technotes/guides/reflection/proxy.html">Delegator</a>
* </p>
*/
public class Delegator implements InvocationHandler {

private static Method hashCodeMethod;
private static Method equalsMethod;
private static Method toStringMethod;
static {
try {
hashCodeMethod = Object.class.getMethod("hashCode", null);
equalsMethod =
Object.class.getMethod("equals", new Class[] { Object.class });
toStringMethod = Object.class.getMethod("toString", null);
} catch (NoSuchMethodException e) {
throw new NoSuchMethodError(e.getMessage());
}
}

private Class[] interfaces;
private Object[] delegates;

public Delegator(Class[] interfaces, Object[] delegates) {
this.interfaces = (Class[]) interfaces.clone();
this.delegates = (Object[]) delegates.clone();
}

public Object invoke(Object proxy, Method m, Object[] args)
throws Throwable
{
Class declaringClass = m.getDeclaringClass();

if (declaringClass == Object.class) {
if (m.equals(hashCodeMethod)) {
return proxyHashCode(proxy);
} else if (m.equals(equalsMethod)) {
return proxyEquals(proxy, args[0]);
} else if (m.equals(toStringMethod)) {
return proxyToString(proxy);
} else {
throw new InternalError(
"unexpected Object method dispatched: " + m);
}
} else {
for (int i = 0; i < interfaces.length; i++) {
if (declaringClass.isAssignableFrom(interfaces[i])) {
try {
return Reflect.on(delegates[i]).call(m.getName(), args).get();
} catch (ReflectException e) {
throw e.getCause();
}
}
}

return invokeNotDelegated(proxy, m, args);
}
}

// Simple workaround for a deeply rooted issue regarding Proxy classes
public Object invokeAsString(String methodName) throws ReflectException {
return Reflect.on(delegates[0]).call(methodName).get();
}

// Simple workaround for a deeply rooted issue regarding Proxy classes
public Object invokeAsString(String methodName, Object[] args) throws ReflectException {
return Reflect.on(delegates[0]).call(methodName, args).get();
}

protected Object invokeNotDelegated(Object proxy, Method m,
Object[] args)
throws Throwable
{
throw new InternalError("unexpected method dispatched: " + m);
}

protected Integer proxyHashCode(Object proxy) {
return new Integer(System.identityHashCode(proxy));
}

protected Boolean proxyEquals(Object proxy, Object other) {
return (proxy == other ? Boolean.TRUE : Boolean.FALSE);
}

protected String proxyToString(Object proxy) {
return proxy.getClass().getName() + '@' +
Integer.toHexString(proxy.hashCode());
}
}
124 changes: 124 additions & 0 deletions detox/android/detox/src/main/java/com/wix/detox/Detox.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.wix.detox;

import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;

/**
* <p>Static class.</p>
*
* <p>To start Detox tests, call runTests() from a JUnit test.
* This test must use AndroidJUnitTestRunner or a subclass of it, as Detox uses Espresso internally.
* All non-standard async code must be wrapped in an Espresso
* <a href="https://google.github.io/android-testing-support-library/docs/espresso/idling-resource/">IdlingResource</a>.</p>
*
* Example usage
* <pre>{@code
*@literal @runWith(AndroidJUnit4.class)
*@literal @LargeTest
* public class DetoxTest {
* @literal @Rule
* //The Activity that controls React Native.
* public ActivityTestRule<MainActivity> mActivityRule = new ActivityTestRule(MainActivity.class);
*
* @literal @Before
* public void setUpCustomEspressoIdlingResources() {
* // set up your own custom Espresso resources here
* }
*
* @literal @Test
* public void runDetoxTests() {
* Detox.runTests();
* }
* }}</pre>
*
* <p>Two required parameters are detoxServer and detoxSessionId. These
* must be provided either by Gradle.
* <br>
*
* <pre>{@code
* android {
* defaultConfig {
* testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
* testInstrumentationRunnerArguments = [
* 'detoxServer': 'ws://10.0.2.2:8001',
* 'detoxSessionId': '1'
* ]
* }
* }}
* </pre>
*
* Or through command line, e.g <br>
* <blockquote>{@code adb shell am instrument -w -e detoxServer ws://localhost:8001 -e detoxSessionId
* 1 com.example/android.support.test.runner.AndroidJUnitRunner}</blockquote></p>
*
* <p>These are automatically set using,
* <blockquote>{@code detox test}</blockquote></p>
*
* <p>If not set, then Detox tests are no ops. So it's safe to mix it with other tests.</p>
*/
public final class Detox {
private Detox() {
// static class
}

/**
* <p>
* Call this method from a JUnit test to invoke detox tests.
* </p>
*
* <p>
* In case you have a non-standard React Native application, consider using
* {@link Detox#runTests(Object)}.
* </p>
*/
public static void runTests() {
Object appContext = InstrumentationRegistry.getTargetContext().getApplicationContext();
runTests(appContext);
}

/**
* <p>
* Call this method only if you have a React Native application and it
* doesn't implement ReactApplication.
* </p>
*
* Call {@link Detox#runTests()} in every other case.
*
* <p>
* The only requirement is that the passed in object must have
* a method with the signature
* <blockquote>{@code ReactNativeHost getReactNativeHost();}</blockquote>
* </p>
*
* @param reactActivityDelegate an object that has a {@code getReactNativeHost()} method
*/
public static void runTests(@NonNull final Object reactActivityDelegate) {
// Kicks off another thread and attaches a Looper to that.
// The goal is to keep the test thread intact,
// as Loopers can't run on a thread twice.
Thread t = new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();
Handler handler = new Handler();
handler.post(new Runnable() {
@Override
public void run() {
DetoxManager detoxManager = new DetoxManager(reactActivityDelegate);
detoxManager.start();
}
});
Looper.loop();
}
}, "com.wix.detox.manager");
t.start();
try {
t.join();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Got interrupted", e);
}
}
}
Loading

0 comments on commit 520d6a3

Please sign in to comment.