From 120198ec8e7f4fca770d251f24574f37df40780e Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Mon, 9 Sep 2019 13:47:56 -0400 Subject: [PATCH 01/17] added previous library code for Socket IO synching. --- .idea/misc.xml | 2 +- androidtest-library/build.gradle | 4 +- .../android/ExampleInstrumentedTest.java | 27 -- .../android/SocketIOHandlerThread.java | 329 ++++++++++++++++++ .../com/moquality/android/TestConstants.java | 24 ++ .../moquality/android/ExampleUnitTest.java | 17 - app/src/main/AndroidManifest.xml | 26 +- 7 files changed, 358 insertions(+), 71 deletions(-) delete mode 100644 androidtest-library/src/androidTest/java/com/moquality/android/ExampleInstrumentedTest.java create mode 100644 androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java create mode 100644 androidtest-library/src/main/java/com/moquality/android/TestConstants.java delete mode 100644 androidtest-library/src/test/java/com/moquality/android/ExampleUnitTest.java diff --git a/.idea/misc.xml b/.idea/misc.xml index dfd2c79..37a7509 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/androidtest-library/build.gradle b/androidtest-library/build.gradle index 33b7e91..667b5a8 100644 --- a/androidtest-library/build.gradle +++ b/androidtest-library/build.gradle @@ -36,7 +36,9 @@ dependencies { testImplementation 'junit:junit:4.12' implementation 'androidx.test:runner:1.2.0' - + implementation ('io.socket:socket.io-client:1.0.0') { + exclude group: 'org.json', module: 'json' + } androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' diff --git a/androidtest-library/src/androidTest/java/com/moquality/android/ExampleInstrumentedTest.java b/androidtest-library/src/androidTest/java/com/moquality/android/ExampleInstrumentedTest.java deleted file mode 100644 index 0b56536..0000000 --- a/androidtest-library/src/androidTest/java/com/moquality/android/ExampleInstrumentedTest.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.moquality.android; - -import android.content.Context; - -import androidx.test.platform.app.InstrumentationRegistry; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import org.junit.Test; -import org.junit.runner.RunWith; - -import static org.junit.Assert.*; - -/** - * Instrumented test, which will execute on an Android device. - * - * @see Testing documentation - */ -@RunWith(AndroidJUnit4.class) -public class ExampleInstrumentedTest { - @Test - public void useAppContext() { - // Context of the app under test. - Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); - - assertEquals("com.moquality.anroidtest_library.test", appContext.getPackageName()); - } -} diff --git a/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java b/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java new file mode 100644 index 0000000..10b8b37 --- /dev/null +++ b/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java @@ -0,0 +1,329 @@ +package com.moquality.android; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Message; +import android.util.Log; + +import com.moquality.android.TestConstants; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; + +import io.socket.client.IO; +import io.socket.client.Socket; +import io.socket.emitter.Emitter; + +class SocketIOHandlerThread extends HandlerThread { + + private Socket socket; + private Handler mWorkerHandler; + + private static final String TAG = SocketIOHandlerThread.class.getSimpleName(); + private Callback mCallback; + private boolean isAlive = true; + private boolean isConnected = false; + private String deviceId; + private int botMode = TestConstants.DEFAULT_MODE; + + public interface Callback { + void onSocketTaskCompleted(int taskId); + void onSocketEventReceived(String eventName, String method, List classArgs, List stringArgs); + } + + SocketIOHandlerThread(Callback callback, String deviceId) { + super(TAG); + mCallback = callback; + this.deviceId = deviceId; + } + + private Emitter.Listener onConnect = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i(TAG, "Android bot connected "+ deviceId); + isConnected = true; + JSONObject data = new JSONObject(); + try { + data.put("deviceId", deviceId); + } catch (JSONException e) { + Log.i("EVENT_CONNECT", "JSON error occurred"); + } + socket.emit("status", data.toString()); + } + }; + + private Emitter.Listener onConnecting = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("connecting")); + } + }; + + private Emitter.Listener onDisconnect = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "disconnected"); + isConnected = false; + } + }; + + private Emitter.Listener onConnectError = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("connect error", args)); + } + }; + + private Emitter.Listener onMessage = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("message")); + } + }; + + private Emitter.Listener onPing = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "ping"); + } + }; + + private Emitter.Listener onPong = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "pong"); + } + }; + + private Emitter.Listener onReconnect = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("reconnecting")); + } + }; + + private Emitter.Listener onReconnectError = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("reconnect error")); + } + }; + + private Emitter.Listener onReconnecting = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", d("reconnecting")); + } + }; + + private Emitter.Listener onStatus = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "received status command"); + } + }; + + private Emitter.Listener onReturn = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "received return command"); + } + }; + + private Emitter.Listener onCall = new Emitter.Listener() { + @Override + public void call(Object... args) { + Log.i("SOCKET_EVENT", "received call command"); + String command = args[0].toString(); + //if (deviceId.equalsIgnoreCase("device1")) { + // botMode = TestConstants.REFLECT_MODE; + //} + Log.i("call", command + " bot mode = " + botMode); + + try { + JSONObject obj = new JSONObject(command); + + String targetDeviceId = obj.getString("deviceId"); + if(deviceId.equals(targetDeviceId)) { + + String cmd = obj.get("cmd").toString(); + JSONArray cmdArgs = obj.getJSONArray("args"); + + List classArgs = new ArrayList<>(); + List stringArgs = new ArrayList<>(); + for (int i = 0; i < cmdArgs.length(); i++) { + classArgs.add(String.class); + stringArgs.add(cmdArgs.getString(i)); + } + + Log.i(TAG, "cmdArgs" + cmdArgs.toString()); + Log.i(TAG, "Parsed classArgs " + classArgs.toString()); + Log.i(TAG, "Parsed stringArgs " + stringArgs.toString()); + + JSONObject msg = new JSONObject(); + msg.put("id", obj.getInt("id")); + msg.put("type", obj.getString("type")); + msg.put("result", "OK"); + + mCallback.onSocketEventReceived(TestConstants.SOCKET_EVENT_CALL, cmd, classArgs, stringArgs); + socket.emit("return", msg.toString()); + } else { + if(botMode == TestConstants.REFLECT_MODE ){ + Class[] cArg = new Class[1]; + cArg[0] = String.class; + JSONArray cmdArgs = obj.getJSONArray("args"); + List classArgs = new ArrayList<>(); + List stringArgs = new ArrayList<>(); + for (int i = 0; i < cmdArgs.length(); i++) { + classArgs.add(String.class); + stringArgs.add(cmdArgs.getString(i)); + } + + String cmd = "reflect"; + + JSONObject msg = new JSONObject(); + msg.put("id", obj.getInt("id")); + msg.put("type", obj.getString("type")); + msg.put("result", "OK"); + + Log.i(TAG, "REFLECTING MESSAGE ***********************"); + //msg.put("return", "{}"); + mCallback.onSocketEventReceived(TestConstants.SOCKET_EVENT_CALL, cmd, classArgs, stringArgs); + socket.emit("return", msg.toString()); + } else { + + Log.i(TAG, "Ignored message id:" + obj.getInt("id")); + } + } + } catch (JSONException e) { + Log.i("EVENT_CONNECT", "JSON error occured"); + } + } + }; + + void queueTask(int taskId) { + Log.i(TAG, "Task added to the queue"); + mWorkerHandler.obtainMessage(taskId) + .sendToTarget(); + } + + void prepareHandler() { + mWorkerHandler = new Handler(getLooper(), new Handler.Callback() { + @Override + public boolean handleMessage(Message msg) { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + handleRequest(msg.what); + return true; + } + }); + } + + boolean isThreadAlive(){return isAlive;} + + private void handleRequest(final int taskId) { + switch(taskId){ + case TestConstants.SOCKET_IO_START: + try { + Thread.sleep(1000); + } catch (InterruptedException e){ + Log.e("SOCKET_IO_START", "Thread interrupted"); + } + + Log.i("SOCKET_EVENT", "STARTING"); + isAlive = true; + try { + IO.Options opts = new IO.Options(); + opts.forceNew = true; + socket = IO.socket(TestConstants.CHAT_SERVER_URL + TestConstants.SOCKET_IO_NAMESPACE, opts ); + socket.connect(); + } catch (URISyntaxException e) { + Log.i("SOCKET ERROR", String.format("URI %s is not valid", TestConstants.CHAT_SERVER_URL)); + } + + socket.on(Socket.EVENT_CONNECT, onConnect); + socket.on(TestConstants.SOCKET_EVENT_STATUS, onStatus); + socket.on(TestConstants.SOCKET_EVENT_RETURN, onReturn); + socket.on(TestConstants.SOCKET_EVENT_CALL, onCall); + socket.on(Socket.EVENT_DISCONNECT, onDisconnect); + socket.on(Socket.EVENT_CONNECT_ERROR, onConnectError); + socket.on(Socket.EVENT_CONNECT_TIMEOUT, onConnectError); + socket.on(Socket.EVENT_CONNECTING, onConnecting); + socket.on(Socket.EVENT_MESSAGE, onMessage); + socket.on(Socket.EVENT_PING, onPing); + socket.on(Socket.EVENT_PONG, onPong); + socket.on(Socket.EVENT_RECONNECT, onReconnect); + socket.on(Socket.EVENT_RECONNECT_ATTEMPT, onReconnectError); + socket.on(Socket.EVENT_RECONNECTING, onReconnecting); + socket.on(Socket.EVENT_RECONNECT_ERROR,onReconnectError); + socket.on(Socket.EVENT_RECONNECT_FAILED, onReconnectError); + socket.on(Socket.EVENT_RECONNECT_ATTEMPT, onReconnect); + + break; + case TestConstants.DEFAULT_MODE: + Log.i(TAG, "DEFAULT bot mode now set"); + botMode = TestConstants.DEFAULT_MODE; + + break; + case TestConstants.REFLECT_MODE: + botMode = TestConstants.REFLECT_MODE; + Log.i(TAG, "REFLECT bot mode now set"); + + break; + case TestConstants.SOCKET_IO_STOP: + try { + Thread.sleep(1000); + socket.disconnect(); + isAlive= false; + isConnected=false; + socket.off(Socket.EVENT_CONNECT, onConnect); + socket.off(TestConstants.SOCKET_EVENT_STATUS, onStatus); + socket.off(TestConstants.SOCKET_EVENT_RETURN, onReturn); + socket.off(TestConstants.SOCKET_EVENT_CALL, onCall); + socket.off(Socket.EVENT_DISCONNECT, onDisconnect); + socket.off(Socket.EVENT_CONNECT_ERROR, onConnectError); + socket.off(Socket.EVENT_CONNECT_TIMEOUT, onConnectError); + socket.off(Socket.EVENT_CONNECTING, onConnecting); + socket.off(Socket.EVENT_MESSAGE, onMessage); + socket.off(Socket.EVENT_PING, onPing); + socket.off(Socket.EVENT_PONG, onPong); + socket.off(Socket.EVENT_RECONNECT, onReconnect); + socket.off(Socket.EVENT_RECONNECT_ATTEMPT, onReconnectError); + socket.off(Socket.EVENT_RECONNECTING, onReconnecting); + socket.off(Socket.EVENT_RECONNECT_ERROR,onReconnectError); + socket.off(Socket.EVENT_RECONNECT_FAILED, onReconnectError); + socket.off(Socket.EVENT_RECONNECT_ATTEMPT, onReconnect); + } catch (InterruptedException e) { + Log.i("SOCKET_IO_STOP", "Thread interrupted"); + } + + break; + default: + break; + } + } + + private String d(String msg, Object... args){ + StringBuilder sb = new StringBuilder(); + sb.append(msg); + sb.append("\n"); + sb.append(args.getClass().getName()); + sb.append("-"); + sb.append(args); + sb.append("\n"); + for(int i=0; iTesting documentation - */ -public class ExampleUnitTest { - @Test - public void addition_isCorrect() { - assertEquals(4, 2 + 2); - } -} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2da82d2..76589a1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,27 +1,3 @@ - - - - - - - - - - - - - - \ No newline at end of file + package="com.moquality.android"/> From 005509d55fc1b09a57eb33678a98f5e1edbb513e Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Mon, 9 Sep 2019 13:52:09 -0400 Subject: [PATCH 02/17] update to main class --- .../java/com/moquality/android/MoQuality.java | 118 +++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 62dda71..5844831 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -1,22 +1,70 @@ package com.moquality.android; import android.graphics.Bitmap; +import android.os.Looper; import android.util.Log; import androidx.test.runner.screenshot.ScreenCapture; import androidx.test.runner.screenshot.Screenshot; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.List; -public class MoQuality { +import static android.content.ContentValues.TAG; + +public class MoQuality implements SocketIOHandlerThread.Callback { public static String TAG = "MQ"; + private SocketIOHandlerThread mHandlerThread; + + private Object obj; + private Class appTestClass; + public int log(String message) { Log.i(TAG, message); return 0; } + public void register( Object obj, String deviceId) { + this.obj = obj; + this.appTestClass = obj.getClass(); + + // launch Socket IO chat thread + if (Looper.myLooper() == null) { + Looper.prepare(); + } + mHandlerThread = new SocketIOHandlerThread(this, deviceId); + mHandlerThread.start(); + mHandlerThread.prepareHandler(); + //Looper.loop(); + } + + public void shutdown() { + if(mHandlerThread != null){ + mHandlerThread.quit(); + mHandlerThread.interrupt(); + } + } + + public void runSocketIOTest() { + mHandlerThread.queueTask(TestConstants.SOCKET_IO_START); + try { + long threadStartTime = System.currentTimeMillis(); + long executionTimeInMillis = 0; + while (mHandlerThread.isThreadAlive()) { + executionTimeInMillis = System.currentTimeMillis() - threadStartTime; + } + + Log.i("SOCKET_IO THREAD", "Execution time = " + executionTimeInMillis/1000 + " seconds"); + //Thread.sleep(5000); + } catch (Exception e) { + Log.d("SOCKET IO", "Test interrupted"); + } + } + public void takeScreenshot(String name) throws IOException { log("Saving screenshot "+name); ScreenCapture capture = Screenshot.capture(); @@ -29,5 +77,73 @@ public void takeScreenshot(String name) throws IOException { throw e; } } + + @Override + public void onSocketTaskCompleted(int taskId) { + switch (taskId){ + case TestConstants.SOCKET_IO_MSG_RECEIVED: + Log.i(TAG, "Message received in UI"); + break; + case TestConstants.SOCKET_IO_DISCONNECTED: + break; + } + } + + @Override + public void onSocketEventReceived(String eventName, String method, List classArgs, List stringArgs) { + switch (eventName){ + case TestConstants.SOCKET_EVENT_CALL: + Log.i(TAG, "CALL command received for this device."); + callAppTestMethod(method, classArgs, stringArgs); + break; + case TestConstants.SOCKET_EVENT_STATUS: + Log.i(TAG, "STATUS command received for this device."); + break; + case TestConstants.SOCKET_EVENT_RETURN: + Log.i(TAG, "RETURN command received for this device."); + } + } + + private void setMode(String mode) { + + switch (mode) { + case "reflect": + mHandlerThread.queueTask(TestConstants.REFLECT_MODE); + + break; + case "quit": + this.mHandlerThread.quit(); + + break; + default: + Log.i(TAG, "Unhandled mode " + mode); + //mHandlerThread.queueTask(TestConstants.DEFAULT_MODE); + } + } + + private void callAppTestMethod(String method, List classArgs, List stringArgs){ + if (appTestClass!=null) { + Log.i(TAG, "******* METHOD = " + method + " ARGS = " + stringArgs.get(0) ); + if (method.equalsIgnoreCase("setMode")){ + setMode(stringArgs.get(0)); + } else { + try { + Method m = appTestClass.getMethod(method, classArgs.toArray(new Class[0])); + + try { + Log.i(TAG, appTestClass.getSimpleName() + " - method called = " + m.toString()); + String data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); + Log.i(TAG, "return " + data); + } catch (IllegalAccessException e) { + Log.i(TAG, method + " invoke error - Illegal Access Exception"); + } catch (InvocationTargetException e) { + Log.i(TAG, method + " invoke error - Invocation Target Exception"); + } + } catch (NoSuchMethodException e) { + Log.i(TAG, "Method (" + method + ") not found in app test."); + } + } + } + } } From 0febfa42bce7385e92394e431800edd805180d88 Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Tue, 10 Sep 2019 18:47:57 -0400 Subject: [PATCH 03/17] dependency updates --- app/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4a1a7e1..ee2b786 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,15 +37,15 @@ dependencies { // Core library - androidTestImplementation 'androidx.test:core:1.0.0' + androidTestImplementation 'androidx.test:core:1.2.0' // AndroidJUnitRunner and JUnit Rules androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test:rules:1.2.0' // Assertions - androidTestImplementation 'androidx.test.ext:junit:1.0.0' - androidTestImplementation 'androidx.test.ext:truth:1.0.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.ext:truth:1.2.0' androidTestImplementation 'com.google.truth:truth:0.42' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' From 486a1dea53e7bf71cd298c142835c0849179e947 Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Tue, 24 Sep 2019 11:48:54 -0400 Subject: [PATCH 04/17] protecting against null pointer exception crash in the method invocation --- .../java/com/moquality/android/MoQuality.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 5844831..2bea368 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -123,17 +123,32 @@ private void setMode(String mode) { private void callAppTestMethod(String method, List classArgs, List stringArgs){ if (appTestClass!=null) { - Log.i(TAG, "******* METHOD = " + method + " ARGS = " + stringArgs.get(0) ); + if (stringArgs!=null) { + Log.i(TAG, "******* METHOD = " + method + " ARGS = " + stringArgs.get(0)); + } else { + Log.i(TAG, "******* METHOD = " + method); + } if (method.equalsIgnoreCase("setMode")){ - setMode(stringArgs.get(0)); + if (stringArgs!=null) { + setMode(stringArgs.get(0)); + } } else { try { Method m = appTestClass.getMethod(method, classArgs.toArray(new Class[0])); try { Log.i(TAG, appTestClass.getSimpleName() + " - method called = " + m.toString()); - String data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); - Log.i(TAG, "return " + data); + try { + String data = ""; + if (stringArgs != null) { + data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); + } else { + data = m.invoke(obj).toString(); + } + Log.i(TAG, "return " + data); + } catch (NullPointerException e) { + Log.i(TAG, "Error returning data from method invoke()"); + } } catch (IllegalAccessException e) { Log.i(TAG, method + " invoke error - Illegal Access Exception"); } catch (InvocationTargetException e) { From a7c484cbcd8670e6d8ac3b1b6460b84804755890 Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Tue, 24 Sep 2019 12:06:51 -0400 Subject: [PATCH 05/17] added index>0 check --- .../src/main/java/com/moquality/android/MoQuality.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 2bea368..9bee710 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -123,7 +123,7 @@ private void setMode(String mode) { private void callAppTestMethod(String method, List classArgs, List stringArgs){ if (appTestClass!=null) { - if (stringArgs!=null) { + if (stringArgs!=null && stringArgs.size()>0) { Log.i(TAG, "******* METHOD = " + method + " ARGS = " + stringArgs.get(0)); } else { Log.i(TAG, "******* METHOD = " + method); @@ -140,10 +140,10 @@ private void callAppTestMethod(String method, List classArgs, List0) { data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); } else { - data = m.invoke(obj).toString(); + data = m.invoke(obj, "").toString(); } Log.i(TAG, "return " + data); } catch (NullPointerException e) { From d277642556c695eb199f7a95c90a05bff800c9a3 Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Tue, 24 Sep 2019 12:20:41 -0400 Subject: [PATCH 06/17] send invoke with no params --- .../src/main/java/com/moquality/android/MoQuality.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 9bee710..ff8e663 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -143,7 +143,7 @@ private void callAppTestMethod(String method, List classArgs, List0) { data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); } else { - data = m.invoke(obj, "").toString(); + data = m.invoke(obj).toString(); } Log.i(TAG, "return " + data); } catch (NullPointerException e) { From 0f47c2b4040fc4f8935201fb94630a316a45b91d Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Tue, 8 Oct 2019 11:17:22 -0400 Subject: [PATCH 07/17] changes to allow multiple test classes to be registered --- .../java/com/moquality/android/MoQuality.java | 66 +++++++++++-------- .../android/SocketIOHandlerThread.java | 24 ------- .../com/moquality/android/TestConstants.java | 3 +- 3 files changed, 38 insertions(+), 55 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index ff8e663..715a10e 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -10,27 +10,24 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.List; -import static android.content.ContentValues.TAG; - public class MoQuality implements SocketIOHandlerThread.Callback { - public static String TAG = "MQ"; + private static String TAG = "MQ"; private SocketIOHandlerThread mHandlerThread; - private Object obj; - private Class appTestClass; + private ArrayList> appTests = new ArrayList>(); public int log(String message) { Log.i(TAG, message); return 0; } - public void register( Object obj, String deviceId) { - this.obj = obj; - this.appTestClass = obj.getClass(); + public void register(Class test, String deviceId) { + this.appTests.add(test); // launch Socket IO chat thread if (Looper.myLooper() == null) { @@ -39,7 +36,14 @@ public void register( Object obj, String deviceId) { mHandlerThread = new SocketIOHandlerThread(this, deviceId); mHandlerThread.start(); mHandlerThread.prepareHandler(); - //Looper.loop(); + } + + public void unregister(Class test){ + for (Class testClass:appTests) { + if (testClass.getSimpleName().equalsIgnoreCase(test.getSimpleName())){ + appTests.remove(testClass); + } + } } public void shutdown() { @@ -105,7 +109,6 @@ public void onSocketEventReceived(String eventName, String method, List c } private void setMode(String mode) { - switch (mode) { case "reflect": mHandlerThread.queueTask(TestConstants.REFLECT_MODE); @@ -122,7 +125,9 @@ private void setMode(String mode) { } private void callAppTestMethod(String method, List classArgs, List stringArgs){ - if (appTestClass!=null) { + Object obj; + if (appTests!=null) { + if (stringArgs!=null && stringArgs.size()>0) { Log.i(TAG, "******* METHOD = " + method + " ARGS = " + stringArgs.get(0)); } else { @@ -133,29 +138,32 @@ private void callAppTestMethod(String method, List classArgs, List testClass:appTests) { try { - Log.i(TAG, appTestClass.getSimpleName() + " - method called = " + m.toString()); + obj= testClass; + Method m = testClass.getMethod(method, classArgs.toArray(new Class[0])); + try { - String data = ""; - if (stringArgs != null && stringArgs.size()>0) { - data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); - } else { - data = m.invoke(obj).toString(); + Log.i(TAG, testClass.getSimpleName() + " - method called = " + m.toString()); + try { + String data = ""; + if (stringArgs != null && stringArgs.size() > 0) { + data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); + } else { + data = m.invoke(obj, "").toString(); + } + Log.i(TAG, "return " + data); + } catch (NullPointerException e) { + Log.i(TAG, "Error returning data from method invoke()"); } - Log.i(TAG, "return " + data); - } catch (NullPointerException e) { - Log.i(TAG, "Error returning data from method invoke()"); + } catch (IllegalAccessException e) { + Log.i(TAG, method + " invoke error - Illegal Access Exception"); + } catch (InvocationTargetException e) { + Log.i(TAG, method + " invoke error - Invocation Target Exception"); } - } catch (IllegalAccessException e) { - Log.i(TAG, method + " invoke error - Illegal Access Exception"); - } catch (InvocationTargetException e) { - Log.i(TAG, method + " invoke error - Invocation Target Exception"); + } catch (NoSuchMethodException e) { + Log.i(TAG, "Method (" + method + ") not found in app test."); } - } catch (NoSuchMethodException e) { - Log.i(TAG, "Method (" + method + ") not found in app test."); } } } diff --git a/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java b/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java index 10b8b37..c7e50c5 100644 --- a/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java +++ b/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java @@ -5,8 +5,6 @@ import android.os.Message; import android.util.Log; -import com.moquality.android.TestConstants; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -140,14 +138,10 @@ public void call(Object... args) { public void call(Object... args) { Log.i("SOCKET_EVENT", "received call command"); String command = args[0].toString(); - //if (deviceId.equalsIgnoreCase("device1")) { - // botMode = TestConstants.REFLECT_MODE; - //} Log.i("call", command + " bot mode = " + botMode); try { JSONObject obj = new JSONObject(command); - String targetDeviceId = obj.getString("deviceId"); if(deviceId.equals(targetDeviceId)) { @@ -192,7 +186,6 @@ public void call(Object... args) { msg.put("result", "OK"); Log.i(TAG, "REFLECTING MESSAGE ***********************"); - //msg.put("return", "{}"); mCallback.onSocketEventReceived(TestConstants.SOCKET_EVENT_CALL, cmd, classArgs, stringArgs); socket.emit("return", msg.toString()); } else { @@ -216,11 +209,6 @@ void prepareHandler() { mWorkerHandler = new Handler(getLooper(), new Handler.Callback() { @Override public boolean handleMessage(Message msg) { - try { - Thread.sleep(2000); - } catch (InterruptedException e) { - e.printStackTrace(); - } handleRequest(msg.what); return true; } @@ -232,12 +220,6 @@ public boolean handleMessage(Message msg) { private void handleRequest(final int taskId) { switch(taskId){ case TestConstants.SOCKET_IO_START: - try { - Thread.sleep(1000); - } catch (InterruptedException e){ - Log.e("SOCKET_IO_START", "Thread interrupted"); - } - Log.i("SOCKET_EVENT", "STARTING"); isAlive = true; try { @@ -279,8 +261,6 @@ private void handleRequest(final int taskId) { break; case TestConstants.SOCKET_IO_STOP: - try { - Thread.sleep(1000); socket.disconnect(); isAlive= false; isConnected=false; @@ -301,10 +281,6 @@ private void handleRequest(final int taskId) { socket.off(Socket.EVENT_RECONNECT_ERROR,onReconnectError); socket.off(Socket.EVENT_RECONNECT_FAILED, onReconnectError); socket.off(Socket.EVENT_RECONNECT_ATTEMPT, onReconnect); - } catch (InterruptedException e) { - Log.i("SOCKET_IO_STOP", "Thread interrupted"); - } - break; default: break; diff --git a/androidtest-library/src/main/java/com/moquality/android/TestConstants.java b/androidtest-library/src/main/java/com/moquality/android/TestConstants.java index b49f519..01b76b3 100644 --- a/androidtest-library/src/main/java/com/moquality/android/TestConstants.java +++ b/androidtest-library/src/main/java/com/moquality/android/TestConstants.java @@ -18,7 +18,6 @@ public class TestConstants { static final int DEFAULT_MODE = 0; static final int REFLECT_MODE = 1; - static final String CHAT_SERVER_URL = "https://sio.moquality.com"; //http://localhost:4000"; - //static final String CHAT_SERVER_URL = "http://localhost:4000"; + static final String CHAT_SERVER_URL = "https://sio.moquality.com"; static final String SOCKET_IO_NAMESPACE = "/connect/default"; } From b558137fbfc00dd1b15b17bc244b14dda022ad74 Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Tue, 8 Oct 2019 14:14:37 -0400 Subject: [PATCH 08/17] small bug fix for invocation call --- .../src/main/java/com/moquality/android/MoQuality.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 715a10e..4a7e2af 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -125,7 +125,6 @@ private void setMode(String mode) { } private void callAppTestMethod(String method, List classArgs, List stringArgs){ - Object obj; if (appTests!=null) { if (stringArgs!=null && stringArgs.size()>0) { @@ -140,7 +139,6 @@ private void callAppTestMethod(String method, List classArgs, List testClass:appTests) { try { - obj= testClass; Method m = testClass.getMethod(method, classArgs.toArray(new Class[0])); try { @@ -148,9 +146,9 @@ private void callAppTestMethod(String method, List classArgs, List 0) { - data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); + data = m.invoke(testClass, stringArgs.toArray(new String[0])).toString(); } else { - data = m.invoke(obj, "").toString(); + data = m.invoke(testClass, "").toString(); } Log.i(TAG, "return " + data); } catch (NullPointerException e) { From 358595a651d7ad204db7e3154f69409389f3f2b9 Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Tue, 8 Oct 2019 14:45:59 -0400 Subject: [PATCH 09/17] casting test class into object to invoke --- .../src/main/java/com/moquality/android/MoQuality.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 4a7e2af..20c0203 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -19,7 +19,7 @@ public class MoQuality implements SocketIOHandlerThread.Callback { private SocketIOHandlerThread mHandlerThread; - private ArrayList> appTests = new ArrayList>(); + private ArrayList> appTests = new ArrayList<>(); public int log(String message) { Log.i(TAG, message); @@ -63,7 +63,6 @@ public void runSocketIOTest() { } Log.i("SOCKET_IO THREAD", "Execution time = " + executionTimeInMillis/1000 + " seconds"); - //Thread.sleep(5000); } catch (Exception e) { Log.d("SOCKET IO", "Test interrupted"); } @@ -145,10 +144,11 @@ private void callAppTestMethod(String method, List classArgs, List 0) { - data = m.invoke(testClass, stringArgs.toArray(new String[0])).toString(); + data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); } else { - data = m.invoke(testClass, "").toString(); + data = m.invoke(obj, "").toString(); } Log.i(TAG, "return " + data); } catch (NullPointerException e) { From f50777f5b795fc80f2b913c9d636f6bfca07f2f3 Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Tue, 8 Oct 2019 15:08:23 -0400 Subject: [PATCH 10/17] shot in the dark --- .../src/main/java/com/moquality/android/MoQuality.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 20c0203..0b96e2b 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -144,11 +144,10 @@ private void callAppTestMethod(String method, List classArgs, List 0) { - data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); + data = m.invoke(testClass.getClass(), stringArgs.toArray(new String[0])).toString(); } else { - data = m.invoke(obj, "").toString(); + data = m.invoke(testClass.getClass(), "").toString(); } Log.i(TAG, "return " + data); } catch (NullPointerException e) { From b86518b196cd1a22367cd39b37330f0b2325a8a3 Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Tue, 8 Oct 2019 15:57:03 -0400 Subject: [PATCH 11/17] long shot invoke fix by adding class constructors --- .../java/com/moquality/android/MoQuality.java | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 0b96e2b..0b90384 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -1,5 +1,6 @@ package com.moquality.android; +import android.content.Context; import android.graphics.Bitmap; import android.os.Looper; import android.util.Log; @@ -8,6 +9,7 @@ import androidx.test.runner.screenshot.Screenshot; import java.io.IOException; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -17,6 +19,8 @@ public class MoQuality implements SocketIOHandlerThread.Callback { private static String TAG = "MQ"; + private Context context; + private SocketIOHandlerThread mHandlerThread; private ArrayList> appTests = new ArrayList<>(); @@ -26,8 +30,9 @@ public int log(String message) { return 0; } - public void register(Class test, String deviceId) { + public void register(Class test, String deviceId, Context context) { this.appTests.add(test); + this.context = context; // launch Socket IO chat thread if (Looper.myLooper() == null) { @@ -140,14 +145,26 @@ private void callAppTestMethod(String method, List classArgs, List[] constructors = testClass.getDeclaredConstructors(); + Constructor constructor = null; + for (int i = 0; i < constructors.length; i++) { + constructor = constructors[i]; + if (constructor.getGenericParameterTypes().length == 0) + break; + } + constructor.setAccessible(true); + Object invokeClass = constructor.newInstance(context, true); + try { Log.i(TAG, testClass.getSimpleName() + " - method called = " + m.toString()); try { String data = ""; if (stringArgs != null && stringArgs.size() > 0) { - data = m.invoke(testClass.getClass(), stringArgs.toArray(new String[0])).toString(); + data = m.invoke(invokeClass, stringArgs.toArray(new String[0])).toString(); } else { - data = m.invoke(testClass.getClass(), "").toString(); + data = m.invoke(invokeClass, "").toString(); } Log.i(TAG, "return " + data); } catch (NullPointerException e) { @@ -160,6 +177,14 @@ private void callAppTestMethod(String method, List classArgs, List Date: Tue, 8 Oct 2019 16:13:07 -0400 Subject: [PATCH 12/17] removed context requirement --- .../src/main/java/com/moquality/android/MoQuality.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 0b90384..5246780 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -19,8 +19,6 @@ public class MoQuality implements SocketIOHandlerThread.Callback { private static String TAG = "MQ"; - private Context context; - private SocketIOHandlerThread mHandlerThread; private ArrayList> appTests = new ArrayList<>(); @@ -30,9 +28,8 @@ public int log(String message) { return 0; } - public void register(Class test, String deviceId, Context context) { + public void register(Class test, String deviceId) { this.appTests.add(test); - this.context = context; // launch Socket IO chat thread if (Looper.myLooper() == null) { @@ -155,7 +152,7 @@ private void callAppTestMethod(String method, List classArgs, List 0) { - data = m.invoke(invokeClass, stringArgs.toArray(new String[0])).toString(); + data = m.invoke(testClass.newInstance(), stringArgs.toArray(new String[0])).toString(); } else { - data = m.invoke(invokeClass, "").toString(); + data = m.invoke(testClass.newInstance(), "").toString(); } Log.i(TAG, "return " + data); } catch (NullPointerException e) { @@ -176,12 +164,8 @@ private void callAppTestMethod(String method, List classArgs, List Date: Tue, 8 Oct 2019 18:44:10 -0400 Subject: [PATCH 14/17] another class reference change --- .../main/java/com/moquality/android/MoQuality.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index cf34dc3..4c132e6 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -138,18 +138,18 @@ private void callAppTestMethod(String method, List classArgs, List testClass:appTests) { + for (Object testClass:appTests) { try { - Method m = testClass.getMethod(method, classArgs.toArray(new Class[0])); + Method m = testClass.getClass().getMethod(method, classArgs.toArray(new Class[0])); try { - Log.i(TAG, testClass.getSimpleName() + " - method called = " + m.toString()); + Log.i(TAG, testClass.getClass().getSimpleName() + " - method called = " + m.toString()); try { String data = ""; if (stringArgs != null && stringArgs.size() > 0) { - data = m.invoke(testClass.newInstance(), stringArgs.toArray(new String[0])).toString(); + data = m.invoke(testClass, stringArgs.toArray(new String[0])).toString(); } else { - data = m.invoke(testClass.newInstance(), "").toString(); + data = m.invoke(testClass).toString(); } Log.i(TAG, "return " + data); } catch (NullPointerException e) { @@ -161,11 +161,9 @@ private void callAppTestMethod(String method, List classArgs, List Date: Wed, 9 Oct 2019 11:54:12 -0400 Subject: [PATCH 15/17] Change to invoke on an instance of the given class --- .../java/com/moquality/android/MoQuality.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index 4c132e6..fadf847 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -138,23 +138,25 @@ private void callAppTestMethod(String method, List classArgs, List testClass:appTests) { try { Method m = testClass.getClass().getMethod(method, classArgs.toArray(new Class[0])); try { Log.i(TAG, testClass.getClass().getSimpleName() + " - method called = " + m.toString()); - try { + Object obj = testClass.newInstance(); String data = ""; if (stringArgs != null && stringArgs.size() > 0) { - data = m.invoke(testClass, stringArgs.toArray(new String[0])).toString(); + data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); } else { - data = m.invoke(testClass).toString(); + data = m.invoke(obj).toString(); } Log.i(TAG, "return " + data); - } catch (NullPointerException e) { - Log.i(TAG, "Error returning data from method invoke()"); - } + } catch (NullPointerException e) { + Log.i(TAG, "Error returning data from method invoke()"); + } catch (InstantiationException e){ + Log.i(TAG, method + " invoke error - Instantiation Exception"); + } catch (IllegalAccessException e) { Log.i(TAG, method + " invoke error - Illegal Access Exception"); } catch (InvocationTargetException e) { From c80c9fa16fefa8868dac241fb0e9ae94949231c3 Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Wed, 9 Oct 2019 14:53:31 -0400 Subject: [PATCH 16/17] Switched back to using object references --- .../java/com/moquality/android/MoQuality.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index fadf847..c58428c 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -1,6 +1,5 @@ package com.moquality.android; -import android.content.Context; import android.graphics.Bitmap; import android.os.Looper; import android.util.Log; @@ -9,7 +8,6 @@ import androidx.test.runner.screenshot.Screenshot; import java.io.IOException; -import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; @@ -21,14 +19,14 @@ public class MoQuality implements SocketIOHandlerThread.Callback { private SocketIOHandlerThread mHandlerThread; - private ArrayList> appTests = new ArrayList<>(); + private ArrayList appTests = new ArrayList<>(); public int log(String message) { Log.i(TAG, message); return 0; } - public void register(Class test, String deviceId) { + public void register(Object test, String deviceId) { this.appTests.add(test); // launch Socket IO chat thread @@ -41,8 +39,8 @@ public void register(Class test, String deviceId) { } public void unregister(Class test){ - for (Class testClass:appTests) { - if (testClass.getSimpleName().equalsIgnoreCase(test.getSimpleName())){ + for (Object testClass:appTests) { + if (testClass.getClass().getSimpleName().equalsIgnoreCase(test.getSimpleName())){ appTests.remove(testClass); } } @@ -138,25 +136,21 @@ private void callAppTestMethod(String method, List classArgs, List testClass:appTests) { + for (Object testClass:appTests) { try { Method m = testClass.getClass().getMethod(method, classArgs.toArray(new Class[0])); try { Log.i(TAG, testClass.getClass().getSimpleName() + " - method called = " + m.toString()); - Object obj = testClass.newInstance(); String data = ""; if (stringArgs != null && stringArgs.size() > 0) { - data = m.invoke(obj, stringArgs.toArray(new String[0])).toString(); + data = m.invoke(testClass, stringArgs.toArray(new String[0])).toString(); } else { - data = m.invoke(obj).toString(); + data = m.invoke(testClass).toString(); } Log.i(TAG, "return " + data); } catch (NullPointerException e) { Log.i(TAG, "Error returning data from method invoke()"); - } catch (InstantiationException e){ - Log.i(TAG, method + " invoke error - Instantiation Exception"); - } catch (IllegalAccessException e) { Log.i(TAG, method + " invoke error - Illegal Access Exception"); } catch (InvocationTargetException e) { From 857d75da2a1f3bc87b5a85a0cb71cff218b8b449 Mon Sep 17 00:00:00 2001 From: James Noseworthy Date: Fri, 11 Oct 2019 18:31:18 -0400 Subject: [PATCH 17/17] Altered the reflection code plus updated README --- README.md | 120 ++++++++++++++++-- .../java/com/moquality/android/MoQuality.java | 83 +++++++++--- .../android/SocketIOHandlerThread.java | 9 +- 3 files changed, 187 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 783819c..94a5fab 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,123 @@ # android-library +## ADDING MOQUALITY LIBRARY TO YOUR PROJECT'S TEST CLASSES +------------------------------------------------------- +- in your main build.gradle (Project) file, add: -### Run single test +``` +allprojects { + repositories { + ... + + // for MoQuality library + maven {url "https://jitpack.io" } + ... + ] +] ``` -./gradlew cAT -Pandroid.testInstrumentationRunnerArguments.class=com.moquality.android.AppTest + +- under the "dependencies {" section of your build.gradle (Module) file, add: ``` + // MoQuality dependencies + implementation 'com.github.moquality:android-library:master-SNAPSHOT' + testImplementation 'com.github.moquality:android-library:master-SNAPSHOT' + androidTestImplementation 'com.github.moquality:android-library:master-SNAPSHOT' +``` + +- in your test class, declare a private instance of the MoQuality library: -Create screenshots folder ``` -adb shell -cd /sdcard/Pictures -mkdir screenshots +private MoQuality moQBot = new MoQuality(); ``` -Screenshots are in the Pictures/screenshots folder. Command to pull. +Add an initialize method at the top of the same class, + ``` -adb pull /sdcard/Pictures/screenshots . -``` \ No newline at end of file + @Before + public void initialize() { + String deviceId; + + Bundle extras = InstrumentationRegistry.getArguments(); + + deviceId = extras.getString("deviceId"); + + Log.i(TAG, "Arguments = " + extras.toString()); + Log.i(TAG, "deviceId" + deviceId); + + moQBot.register(this, deviceId); + } +``` + +Now add the following MoQuality specific calls, + +``` + @Test + public void runTest() { + moQBot.runSocketIOTest(); + } + + @After + public void shutdown() { + moQBot.shutdown(); + } +``` + +Next write your public test methods as normal. If you wish to capture a screen shot via the library during the test, insert the following code: + +``` + try { + moQBot.takeScreenshot(""); + } catch (IOException e) { + e.printStackTrace(); + } +``` + +Finally create a JSON test script, that calls your test methods. For example, +``` +[ { + "type": "espresso", <--- indicates the type of command, "espresso", "uiautomator", "devices", or "signal" + "msg": + { + "deviceId": "device1", <---- allows you to target a specific device for the test script + "cmd": "openNavDrawer", <---- reference to the exact test method name + "args": [] <--- optional cmd arguments that get passed back to the test class via the library + } + }, + { + "type": "signal", <--- signal allows you to run internal commands on the device outside of the test class + "msg": + { + "deviceId": "device1", + "cmd": "sleep", <--- puts the specified device in a wait state + "args": [ + 3000 <--- number of milliseconds to wait + ] + } + } +] +``` + +----------------------------------------------------------------------------------- + +## Run demo tests with the library and Google I/O scheduler sample project. + +Grab the "iosched-modified" folder from the sample project on Github at +https://github.com/moquality/multidevice +and open the folder in Android Studio. Start up an instance of an emulated device (should list as "emulator-5554" when the adb devices command is run. +Compile the the project and under the Gradle tab window >install options, click the "installDebug", "installStaging", and "installStagingAndroidTest" options. +Once installed, open the I/O scheduler app to check the it is ready to run and then background the app. + +In one terminal window type the following adb command: +``` +> adb -s emulator-5554 shell am instrument -w -e class com.google.samples.apps.iosched.tests.pages.HomePage -e deviceId device1 com.google.samples.apps.iosched.test/androidx.test.runner.AndroidJUnitRunner +``` +to spool up the I/O scheduler app in test mode referencing the HomePage test class + +Now in a second terminal window, cd into the "server" folder of the sample project and type: +``` +> yarn runner +``` + +The Google I/O app on the emulator should now run through the sample tests outlined in the HomePage class while also outputting testing status in the secon terminal window as it proceeds through each step. \ No newline at end of file diff --git a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java index c58428c..6d64bb7 100644 --- a/androidtest-library/src/main/java/com/moquality/android/MoQuality.java +++ b/androidtest-library/src/main/java/com/moquality/android/MoQuality.java @@ -26,9 +26,7 @@ public int log(String message) { return 0; } - public void register(Object test, String deviceId) { - this.appTests.add(test); - + public void init(String deviceId) { // launch Socket IO chat thread if (Looper.myLooper() == null) { Looper.prepare(); @@ -38,9 +36,13 @@ public void register(Object test, String deviceId) { mHandlerThread.prepareHandler(); } - public void unregister(Class test){ + public void register(Object test) { + this.appTests.add(test); + } + + public void unregister(Object test){ for (Object testClass:appTests) { - if (testClass.getClass().getSimpleName().equalsIgnoreCase(test.getSimpleName())){ + if (testClass.getClass().getSimpleName().equalsIgnoreCase(test.getClass().getSimpleName())){ appTests.remove(testClass); } } @@ -107,6 +109,12 @@ public void onSocketEventReceived(String eventName, String method, List c } } + @Override + public void onSocketEventReceived(String eventName, String className, String method, List classArgs, List stringArgs) { + Log.i(TAG, "CALL specific test class for this device."); + callAppTestMethod(className, method, classArgs, stringArgs); + } + private void setMode(String mode) { switch (mode) { case "reflect": @@ -119,7 +127,6 @@ private void setMode(String mode) { break; default: Log.i(TAG, "Unhandled mode " + mode); - //mHandlerThread.queueTask(TestConstants.DEFAULT_MODE); } } @@ -136,19 +143,20 @@ private void callAppTestMethod(String method, List classArgs, List 0) { - data = m.invoke(testClass, stringArgs.toArray(new String[0])).toString(); - } else { - data = m.invoke(testClass).toString(); - } - Log.i(TAG, "return " + data); + String data = ""; + if (stringArgs != null && stringArgs.size() > 0) { + data = m.invoke(testClass, stringArgs.toArray(new String[0])).toString(); + } else { + data = m.invoke(testClass).toString(); + } + Log.i(TAG, "return " + data); } catch (NullPointerException e) { Log.i(TAG, "Error returning data from method invoke()"); } catch (IllegalAccessException e) { @@ -158,12 +166,57 @@ private void callAppTestMethod(String method, List classArgs, List classArgs, List stringArgs){ + if (appTests!=null) { + if (stringArgs!=null && stringArgs.size()>0) { + Log.i(TAG, "******* METHOD = " + method + " ARGS = " + stringArgs.get(0)); + } else { + Log.i(TAG, "******* METHOD = " + method); + } + if (method.equalsIgnoreCase("setMode")){ + if (stringArgs!=null) { + setMode(stringArgs.get(0)); + } + } else { + for (Object testClass : appTests) { + if (className.equalsIgnoreCase((testClass.getClass().getSimpleName()))) { + try { + Method m = testClass.getClass().getMethod(method, classArgs.toArray(new Class[0])); + + try { + Log.i(TAG, testClass.getClass().getSimpleName() + " - method called = " + m.toString()); + String data = ""; + if (stringArgs != null && stringArgs.size() > 0) { + data = m.invoke(testClass, stringArgs.toArray(new String[0])).toString(); + } else { + data = m.invoke(testClass).toString(); + } + Log.i(TAG, "return " + data); + } catch (NullPointerException e) { + Log.i(TAG, "Error returning data from method invoke()"); + } catch (IllegalAccessException e) { + Log.i(TAG, method + " invoke error - Illegal Access Exception"); + } catch (InvocationTargetException e) { + Log.i(TAG, method + " invoke error - Invocation Target Exception"); + } + } catch (NoSuchMethodException e) { + Log.i(TAG, "Method (" + method + ") not found in app test class " + testClass.getClass().getSimpleName()); + } catch (NullPointerException e) { + Log.i(TAG, "Null pointer exception on class constructor."); + } + break; + } + } + } + } + } } diff --git a/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java b/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java index c7e50c5..ea340f0 100644 --- a/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java +++ b/androidtest-library/src/main/java/com/moquality/android/SocketIOHandlerThread.java @@ -32,6 +32,7 @@ class SocketIOHandlerThread extends HandlerThread { public interface Callback { void onSocketTaskCompleted(int taskId); void onSocketEventReceived(String eventName, String method, List classArgs, List stringArgs); + void onSocketEventReceived(String eventName, String className, String method, List classArgs, List stringArgs); } SocketIOHandlerThread(Callback callback, String deviceId) { @@ -145,6 +146,8 @@ public void call(Object... args) { String targetDeviceId = obj.getString("deviceId"); if(deviceId.equals(targetDeviceId)) { + String className = obj.get("class").toString(); + String cmd = obj.get("cmd").toString(); JSONArray cmdArgs = obj.getJSONArray("args"); @@ -164,7 +167,11 @@ public void call(Object... args) { msg.put("type", obj.getString("type")); msg.put("result", "OK"); - mCallback.onSocketEventReceived(TestConstants.SOCKET_EVENT_CALL, cmd, classArgs, stringArgs); + if (className.length()>0) { + mCallback.onSocketEventReceived(TestConstants.SOCKET_EVENT_CALL, className, cmd, classArgs, stringArgs); + } else { + mCallback.onSocketEventReceived(TestConstants.SOCKET_EVENT_CALL, cmd, classArgs, stringArgs); + } socket.emit("return", msg.toString()); } else { if(botMode == TestConstants.REFLECT_MODE ){