Skip to content

Commit 680f4ec

Browse files
authored
Introduce a more sensible impl of debug-sync (Android (wix#2725)
Merging because the main downstream build is green. Others failed for reasons related to Macstadium network.
1 parent b434949 commit 680f4ec

File tree

19 files changed

+271
-131
lines changed

19 files changed

+271
-131
lines changed

detox/android/detox/src/full/java/com/wix/detox/DetoxActionHandlers.kt

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,8 @@ import androidx.test.espresso.IdlingResource
66
import com.wix.detox.common.extractRootCause
77
import com.wix.detox.instruments.DetoxInstrumentsException
88
import com.wix.detox.instruments.DetoxInstrumentsManager
9+
import com.wix.detox.reactnative.idlingresources.DescriptiveIdlingResource
910
import com.wix.invoke.MethodInvocation
10-
import org.json.JSONArray
11-
import org.json.JSONException
1211
import org.json.JSONObject
1312
import java.lang.reflect.InvocationTargetException
1413

@@ -101,20 +100,27 @@ class QueryStatusActionHandler(
101100
override fun handle(params: String, messageId: Long) {
102101
val data = mutableMapOf<String, Any>()
103102
val busyResources = testEngineFacade.getBusyIdlingResources()
104-
var status = ""
105103

106-
if (busyResources.isEmpty()) {
107-
status = "The app is idle."
108-
} else {
109-
status = "Busy idling resources:\n"
110-
for (res in busyResources) {
111-
status += "\t- ${res.name}\n"
112-
}
113-
}
114-
115-
data["status"] = status
104+
data["status"] = "App synchronization debug: " +
105+
if (busyResources.isEmpty()) {
106+
"The app appears to be idle!"
107+
} else {
108+
val summary = busyResources.joinToString("\n") { "\t - ${formatResource(it)}" }
109+
"The app is busy, due to: \n$summary"
110+
}
116111
wsClient.sendAction("currentStatusResult", data, messageId)
117112
}
113+
114+
private fun formatResource(resource: IdlingResource): String =
115+
if (resource is DescriptiveIdlingResource) {
116+
resource.getDescription()
117+
} else if (resource.javaClass.name.contains("LooperIdlingResource") && resource.name.contains("mqt_js")) {
118+
"Javascript code execution"
119+
} else if (resource.javaClass.name.contains("LooperIdlingResource") && resource.name.contains("mqt_native")) {
120+
"Javascript code execution (native)"
121+
} else {
122+
"Resource ${resource.name} being busy"
123+
}
118124
}
119125

120126
class InstrumentsRecordingStateActionHandler(

detox/android/detox/src/full/java/com/wix/detox/TestEngineFacade.kt

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ package com.wix.detox
33
import android.content.Context
44
import android.util.Log
55
import androidx.test.espresso.Espresso
6-
import androidx.test.espresso.IdlingResource
76
import com.wix.detox.common.DetoxLog.Companion.LOG_TAG
8-
import com.wix.detox.espresso.EspressoDetox
97
import com.wix.detox.espresso.UiAutomatorHelper
8+
import com.wix.detox.espresso.registry.IRStatusInquirer
109
import com.wix.detox.reactnative.ReactNativeExtension
1110

1211
class TestEngineFacade {
@@ -15,7 +14,9 @@ class TestEngineFacade {
1514
null
1615
}
1716
fun syncIdle() = UiAutomatorHelper.espressoSync() // TODO Check whether this can be replaced with #awaitIdle()
18-
fun getBusyIdlingResources() = EspressoDetox.getBusyEspressoResources() as List<IdlingResource>
17+
fun getBusyIdlingResources() = IRStatusInquirer.INSTANCE.getAllBusyResources()
18+
19+
// TODO Refactor RN related stuff away
1920
fun reloadReactNative(appContext: Context) = ReactNativeExtension.reloadReactNative(appContext)
2021
fun resetReactNative() = ReactNativeExtension.clearAllSynchronization()
2122
}

detox/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java

Lines changed: 2 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,21 @@
44
import android.content.Context;
55
import android.content.ContextWrapper;
66
import android.content.pm.ActivityInfo;
7-
import android.os.Handler;
87
import android.util.Log;
98
import android.view.View;
109
import android.view.ViewGroup;
1110

11+
import com.wix.detox.common.UIThread;
1212
import com.wix.detox.reactnative.ReactNativeExtension;
1313
import com.wix.detox.reactnative.idlingresources.NetworkIdlingResource;
1414

1515
import org.hamcrest.Matcher;
16-
import org.joor.Reflect;
17-
import org.joor.ReflectException;
1816

1917
import java.util.ArrayList;
2018

21-
import androidx.test.espresso.Espresso;
22-
import androidx.test.espresso.IdlingResource;
2319
import androidx.test.espresso.UiController;
2420
import androidx.test.espresso.ViewAction;
2521
import androidx.test.espresso.ViewInteraction;
26-
import androidx.test.platform.app.InstrumentationRegistry;
2722

2823
import static androidx.test.espresso.Espresso.onView;
2924
import static androidx.test.espresso.matcher.ViewMatchers.isRoot;
@@ -113,71 +108,12 @@ public static void setSynchronization(boolean enabled) {
113108
}
114109

115110
public static void setURLBlacklist(final ArrayList<String> urls) {
116-
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
111+
UIThread.postSync(new Runnable() {
117112
@Override
118113
public void run() {
119114
NetworkIdlingResource.setURLBlacklist(urls);
120115
}
121116
});
122117
}
123-
124-
public static ArrayList<IdlingResource> getBusyEspressoResources() {
125-
// We do this in this complicated way for two reasons
126-
// 1. we want to use postAtFrontOfQueue()
127-
// 2. we want it to be synchronous
128-
final ArrayList<IdlingResource> busyResources = new ArrayList<>();
129-
final Handler handler = new Handler(InstrumentationRegistry.getInstrumentation().getTargetContext().getMainLooper());
130-
final SyncRunnable sr = new SyncRunnable(new Runnable() {
131-
@Override
132-
public void run() {
133-
// The following snippet works only in Espresso 3.0
134-
try {
135-
ArrayList<Object> idlingStates = Reflect.on(Espresso.class)
136-
.field("baseRegistry")
137-
.field("idlingStates")
138-
.get();
139-
for (int i = 0; i < idlingStates.size(); ++i) {
140-
if (!(boolean)Reflect.on(idlingStates.get(i)).field("idle").get()) {
141-
busyResources.add((IdlingResource)Reflect.on(idlingStates.get(i)).field("resource").get());
142-
}
143-
}
144-
} catch (ReflectException e) {
145-
Log.d(LOG_TAG, "Couldn't get busy resources", e);
146-
}
147-
}
148-
});
149-
handler.postAtFrontOfQueue(sr);
150-
sr.waitForComplete();
151-
return busyResources;
152-
}
153-
154-
155-
private static final class SyncRunnable implements Runnable {
156-
private final Runnable mTarget;
157-
private boolean mComplete;
158-
159-
public SyncRunnable(Runnable target) {
160-
mTarget = target;
161-
}
162-
163-
public void run() {
164-
mTarget.run();
165-
synchronized (this) {
166-
mComplete = true;
167-
notifyAll();
168-
}
169-
}
170-
171-
public void waitForComplete() {
172-
synchronized (this) {
173-
while (!mComplete) {
174-
try {
175-
wait();
176-
} catch (InterruptedException e) {
177-
}
178-
}
179-
}
180-
}
181-
}
182118
}
183119

detox/android/detox/src/full/java/com/wix/detox/espresso/UiAutomatorHelper.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import android.util.Log;
77
import android.view.Choreographer;
88

9+
import com.wix.detox.common.UIThread;
910
import com.wix.detox.espresso.common.utils.UiControllerUtils;
1011

1112
import org.joor.Reflect;
@@ -33,7 +34,7 @@ public static void espressoSync() {
3334
// I want to invoke Espresso's sync mechanism manually.
3435
// This turned out to be amazingly difficult. This below is the
3536
// nicest solution I could come up with.
36-
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
37+
UIThread.runSync(new Runnable() {
3738
@Override
3839
public void run() {
3940
try {
@@ -54,7 +55,7 @@ public static void espressoSync(final long millis) {
5455
// I want to invoke Espresso's sync mechanism manually.
5556
// This turned out to be amazingly difficult. This below is the
5657
// nicest solution I could come up with.
57-
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
58+
UIThread.runSync(new Runnable() {
5859
@Override
5960
public void run() {
6061
try {
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.wix.detox.espresso.registry
2+
3+
import androidx.test.espresso.Espresso
4+
import androidx.test.espresso.IdlingResource
5+
import androidx.test.espresso.base.IdlingResourceRegistry
6+
import com.wix.detox.common.UIThread
7+
import org.joor.Reflect
8+
import java.util.concurrent.Callable
9+
10+
class IRStatusInquirer(private val registry: IdlingResourceRegistry) {
11+
fun getAllBusyResources(): List<IdlingResource> {
12+
return UIThread.postFirstSync(Callable<List<IdlingResource>> {
13+
registry.resources.filter { resource ->
14+
!resource.isIdleNow
15+
}
16+
})
17+
}
18+
19+
companion object {
20+
val INSTANCE = IRStatusInquirer(getRegistryDefault())
21+
}
22+
}
23+
24+
private fun getRegistryDefault() = Reflect.on(Espresso::class.java).get<IdlingResourceRegistry>("baseRegistry")

detox/android/detox/src/full/java/com/wix/detox/reactnative/ReactNativeIdlingResources.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ private class MQThreadReflected(private val queue: Any?, private val queueName:
4646
class ReactNativeIdlingResources constructor(
4747
private val reactContext: ReactContext,
4848
internal var networkSyncEnabled: Boolean = true)
49+
4950
{
5051

5152
companion object {
@@ -63,7 +64,7 @@ class ReactNativeIdlingResources constructor(
6364
private var networkIdlingResource: NetworkIdlingResource? = null
6465

6566
fun registerAll() {
66-
Log.i(LOG_TAG, "Setting up Espresso Idling Resources for React Native.")
67+
Log.i(LOG_TAG, "Setting up Espresso Idling Resources for React Native")
6768

6869
unregisterAll()
6970

detox/android/detox/src/full/java/com/wix/detox/reactnative/helpers/RNHelpers.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@ object RNHelpers {
1414
if (reactContext.hasNativeModule(moduleClass)) {
1515
reactContext.getNativeModule(moduleClass)
1616
} else {
17-
Log.d(LOG_TAG, "Native module not resolved: no registered module")
17+
Log.d(LOG_TAG, "Native RN module resolution (class $className): no registered module found")
1818
null
1919
}
20-
} catch (ex: Exception) {
21-
Log.d(LOG_TAG, "Native module not resolved: no such class")
20+
} catch (ex: ClassNotFoundException) {
21+
Log.d(LOG_TAG, "Native RN module resolution (class $className): no such class")
2222
null
2323
}
2424
}

detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/AnimatedModuleIdlingResource.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
import com.wix.detox.reactnative.ReactNativeInfo;
77

8+
import org.jetbrains.annotations.NotNull;
89
import org.joor.Reflect;
910
import org.joor.ReflectException;
1011

1112
import androidx.annotation.NonNull;
12-
import androidx.test.espresso.IdlingResource;
1313

1414
/**
1515
* Created by simonracz on 25/08/2017.
@@ -29,7 +29,7 @@
2929
*
3030
* @see <a href="https://github.com/facebook/react-native/blob/259eac8c30b536abddab7925f4c51f0bf7ced58d/ReactAndroid/src/main/java/com/facebook/react/animated/NativeAnimatedModule.java#L143">AnimatedModule</a>
3131
*/
32-
public class AnimatedModuleIdlingResource implements IdlingResource, Choreographer.FrameCallback {
32+
public class AnimatedModuleIdlingResource implements DescriptiveIdlingResource, Choreographer.FrameCallback {
3333
private static final String LOG_TAG = "Detox";
3434

3535
private final static String CLASS_ANIMATED_MODULE = "com.facebook.react.animated.NativeAnimatedModule";
@@ -63,6 +63,12 @@ public String getName() {
6363
return AnimatedModuleIdlingResource.class.getName();
6464
}
6565

66+
@NotNull
67+
@Override
68+
public String getDescription() {
69+
return "Animations running on screen";
70+
}
71+
6672
@Override
6773
public boolean isIdleNow() {
6874
Class<?> animModuleClass = null;

detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/AsyncStorageIdlingResource.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ open class AsyncStorageIdlingResource
2828
@JvmOverloads constructor(
2929
module: NativeModule,
3030
sexecutorReflectedGenFn: SExecutorReflectedGenFnType = defaultSExecutorReflectedGenFn)
31-
: IdlingResource {
31+
: DescriptiveIdlingResource {
3232

3333
open val logTag: String
3434
get() = LOG_TAG
@@ -50,6 +50,7 @@ open class AsyncStorageIdlingResource
5050
}
5151

5252
override fun getName(): String = javaClass.name
53+
override fun getDescription() = "Disk I/O activity"
5354
override fun isIdleNow(): Boolean =
5455
checkIdle().also { idle ->
5556
if (!idle) {

detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/BridgeIdlingResource.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
66
import com.facebook.react.bridge.ReactContext;
77

8+
import org.jetbrains.annotations.NotNull;
9+
810
import java.util.concurrent.atomic.AtomicBoolean;
911

1012
/**
@@ -38,6 +40,12 @@ public String getName() {
3840
return BridgeIdlingResource.class.getName();
3941
}
4042

43+
@NotNull
44+
@Override
45+
public String getDescription() {
46+
return "Activity on the React-Native bridge";
47+
}
48+
4149
@Override
4250
protected boolean checkIdle() {
4351
boolean ret = idleNow.get();
@@ -64,6 +72,7 @@ public void onTransitionToBridgeBusy() {
6472
// Log.i(LOG_TAG, "JS Bridge transitions to busy.");
6573
}
6674

75+
@Override
6776
public void onBridgeDestroyed() {
6877
}
6978

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.wix.detox.reactnative.idlingresources
2+
3+
import androidx.test.espresso.IdlingResource
4+
5+
interface DescriptiveIdlingResource: IdlingResource {
6+
fun getDescription(): String
7+
}

detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/DetoxBaseIdlingResource.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
import java.util.concurrent.atomic.AtomicBoolean;
44

5-
import androidx.test.espresso.IdlingResource;
6-
7-
public abstract class DetoxBaseIdlingResource implements IdlingResource {
5+
public abstract class DetoxBaseIdlingResource implements DescriptiveIdlingResource {
86
AtomicBoolean paused = new AtomicBoolean(false);
97

108
public void pause() {

detox/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/NetworkIdlingResource.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import com.facebook.react.bridge.ReactContext;
77

8+
import org.jetbrains.annotations.NotNull;
9+
810
import java.util.ArrayList;
911
import java.util.List;
1012
import java.util.regex.Pattern;
@@ -64,6 +66,12 @@ public String getName() {
6466
return NetworkIdlingResource.class.getName();
6567
}
6668

69+
@NotNull
70+
@Override
71+
public String getDescription() {
72+
return "In-flight network activity";
73+
}
74+
6775
@Override
6876
protected boolean checkIdle() {
6977
boolean idle = true;

0 commit comments

Comments
 (0)