Skip to content

Commit 8091cbd

Browse files
brettchabotcopybara-androidxtest
authored andcommitted
Fix Espresso.onIdle for non-main Loopers on Baklava.
LooperIdlingResourceInterrogationHandler, which is used for non main Loopers, requires calling peekAtQueueState from the main Looper thread. The previous Interrogator implementation, when used on Baklava, didn't effectively support this because TestLooperManager.acquire needs to be called from the Looper's thread. This commit refactors Interrogator so it be acquired and stored on the Looper's current thread. PiperOrigin-RevId: 714149090
1 parent e7098ca commit 8091cbd

File tree

6 files changed

+70
-29
lines changed

6 files changed

+70
-29
lines changed

espresso/core/java/androidx/test/espresso/base/IdlingResourceRegistry.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,9 @@ public Boolean call() {
216216
boolean found = false;
217217
for (int i = 0; i < idlingStates.size(); i++) {
218218
if (idlingStates.get(i).resource.getName().equals(resource.getName())) {
219+
if (idlingStates.get(i).resource instanceof LooperIdlingResourceInterrogationHandler) {
220+
((LooperIdlingResourceInterrogationHandler) idlingStates.get(i).resource).release();
221+
}
219222
idlingStates.get(i).closeSpan();
220223
idlingStates.remove(i);
221224
found = true;

espresso/core/java/androidx/test/espresso/base/Interrogator.java

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -92,18 +92,30 @@ interface InterrogationHandler<R> extends QueueInterrogationHandler<R> {
9292
public String getMessage();
9393
}
9494

95+
private final TestLooperManagerCompat testLooperManager;
96+
97+
static Interrogator acquire(Looper looper) {
98+
return new Interrogator(TestLooperManagerCompat.acquire(looper));
99+
}
100+
101+
private Interrogator(TestLooperManagerCompat testLooperManager) {
102+
this.testLooperManager = testLooperManager;
103+
}
104+
105+
void release() {
106+
testLooperManager.release();
107+
}
95108

96109
/**
97110
* Loops the main thread and informs the interrogation handler at interesting points in the exec
98111
* state.
99112
*
100113
* @param handler an interrogation handler that controls whether to continue looping or not.
101114
*/
102-
static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
115+
<T> T loopAndInterrogate(InterrogationHandler<T> handler) {
103116
checkSanity();
104117
interrogating.set(Boolean.TRUE);
105118
boolean stillInterested = true;
106-
TestLooperManagerCompat testLooperManager = TestLooperManagerCompat.acquire(Looper.myLooper());
107119

108120
// We may have an identity when we're called - we want to restore it at the end of the fn.
109121
final long entryIdentity = Binder.clearCallingIdentity();
@@ -112,7 +124,7 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
112124
final long threadIdentity = Binder.clearCallingIdentity();
113125
while (stillInterested) {
114126
// run until the observer is no longer interested.
115-
stillInterested = interrogateQueueState(testLooperManager, handler);
127+
stillInterested = interrogateQueueState(handler);
116128
if (stillInterested) {
117129
Message m = testLooperManager.next();
118130

@@ -149,7 +161,6 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
149161
} finally {
150162
Binder.restoreCallingIdentity(entryIdentity);
151163
interrogating.set(Boolean.FALSE);
152-
testLooperManager.release();
153164
}
154165
return handler.get();
155166
}
@@ -168,37 +179,25 @@ static <R> R loopAndInterrogate(InterrogationHandler<R> handler) {
168179
* queueEmpty(), taskDueSoon(), taskDueLong() or barrierUp(). once and only once.
169180
* @return the result of handler.get()
170181
*/
171-
static <R> R peekAtQueueState(
172-
TestLooperManagerCompat testLooperManager, QueueInterrogationHandler<R> handler) {
173-
checkNotNull(testLooperManager);
182+
<T> T peekAtQueueState(QueueInterrogationHandler<T> handler) {
174183
checkNotNull(handler);
175184
checkState(
176-
!interrogateQueueState(testLooperManager, handler),
185+
!interrogateQueueState(handler),
177186
"It is expected that %s would stop interrogation after a single peak at the queue.",
178187
handler);
179188
return handler.get();
180189
}
181190

182-
static <R> R peekAtQueueState(Looper looper, QueueInterrogationHandler<R> handler) {
183-
TestLooperManagerCompat testLooperManager = TestLooperManagerCompat.acquire(looper);
184-
try {
185-
return peekAtQueueState(testLooperManager, handler);
186-
} finally {
187-
testLooperManager.release();
188-
}
189-
}
190-
191-
private static boolean interrogateQueueState(
192-
TestLooperManagerCompat testLooperManager, QueueInterrogationHandler<?> handler) {
191+
private boolean interrogateQueueState(QueueInterrogationHandler<?> handler) {
193192
synchronized (testLooperManager.getQueue()) {
193+
if (testLooperManager.isBlockedOnSyncBarrier()) {
194+
if (Log.isLoggable(TAG, Log.DEBUG)) {
195+
Log.d(TAG, "barrier is up");
196+
}
197+
return handler.barrierUp();
198+
}
194199
Long headWhen = testLooperManager.peekWhen();
195200
if (headWhen == null) {
196-
if (testLooperManager.isBlockedOnSyncBarrier()) {
197-
if (Log.isLoggable(TAG, Log.DEBUG)) {
198-
Log.d(TAG, "barrier is up");
199-
}
200-
return handler.barrierUp();
201-
}
202201
return handler.queueEmpty();
203202
}
204203

@@ -215,7 +214,7 @@ private static boolean interrogateQueueState(
215214
}
216215
}
217216

218-
private static void checkSanity() {
217+
private void checkSanity() {
219218
checkState(Looper.myLooper() != null, "Calling non-looper thread!");
220219
checkState(Boolean.FALSE.equals(interrogating.get()), "Already interrogating!");
221220
}

espresso/core/java/androidx/test/espresso/base/LooperIdlingResourceInterrogationHandler.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ public boolean barrierUp() {
7171
private volatile Looper looper = null;
7272
private volatile boolean idle = true;
7373

74+
private volatile Interrogator interrogator = null;
75+
7476
// written on main - read on looper
7577
private volatile IdlingResource.ResourceCallback cb = null;
7678

@@ -97,8 +99,9 @@ static LooperIdlingResourceInterrogationHandler forLooper(Looper l) {
9799
@Override
98100
public void run() {
99101
ir.looper = Looper.myLooper();
102+
ir.interrogator = Interrogator.acquire(ir.looper);
100103
ir.started = true;
101-
Interrogator.loopAndInterrogate(ir);
104+
ir.interrogator.loopAndInterrogate(ir);
102105
}
103106
});
104107

@@ -115,6 +118,7 @@ public String getMessage() {
115118

116119
@Override
117120
public void quitting() {
121+
interrogator.release();
118122
transitionToIdle();
119123
}
120124

@@ -162,7 +166,7 @@ public boolean isIdleNow() {
162166
// make sure nothing has arrived in the queue while the looper thread is waiting to pull a
163167
// new task out of it. There can be some delay between a new message entering the queue and
164168
// the looper thread pulling it out and processing it.
165-
return Boolean.FALSE.equals(Interrogator.peekAtQueueState(looper, queueHasNewTasks));
169+
return Boolean.FALSE.equals(interrogator.peekAtQueueState(queueHasNewTasks));
166170
}
167171
return false;
168172
}
@@ -183,4 +187,8 @@ private void transitionToIdle() {
183187
cb.onTransitionToIdle();
184188
}
185189
}
190+
191+
public void release() {
192+
interrogator.release();
193+
}
186194
}

espresso/core/java/androidx/test/espresso/base/TestLooperManagerCompat.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import android.os.MessageQueue;
1010
import android.os.TestLooperManager;
1111
import androidx.annotation.Nullable;
12+
import androidx.test.internal.util.Checks;
1213
import androidx.test.platform.app.InstrumentationRegistry;
1314
import java.lang.reflect.Field;
1415
import java.lang.reflect.Method;
@@ -77,6 +78,7 @@ private TestLooperManagerCompat(TestLooperManager testLooperManager) {
7778
static TestLooperManagerCompat acquire(Looper looper) {
7879
if (peekWhenMethod != null) {
7980
// running on a newer Android version that has the supported TestLooperManagerCompat changes
81+
Checks.checkState(looper.isCurrentThread());
8082
TestLooperManager testLooperManager =
8183
InstrumentationRegistry.getInstrumentation().acquireLooperManager(looper);
8284
return new TestLooperManagerCompat(testLooperManager);

espresso/core/java/androidx/test/espresso/base/UiControllerImpl.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ private enum InterrogationStatus {
156156
private IdleNotifier<Runnable> asyncIdle;
157157
private IdleNotifier<Runnable> compatIdle;
158158
private Provider<IdleNotifier<IdleNotificationCallback>> dynamicIdleProvider;
159+
private Interrogator interrogator;
159160

160161
@VisibleForTesting
161162
@Inject
@@ -507,7 +508,7 @@ private IdleNotifier<IdleNotificationCallback> loopUntil(
507508
start + masterIdlePolicy.getIdleTimeoutUnit().toMillis(masterIdlePolicy.getIdleTimeout());
508509
interrogation = new MainThreadInterrogation(conditions, conditionSet, end);
509510

510-
InterrogationStatus result = Interrogator.loopAndInterrogate(interrogation);
511+
InterrogationStatus result = getInterrogator().loopAndInterrogate(interrogation);
511512
if (InterrogationStatus.COMPLETED == result) {
512513
// did not time out, all conditions happy.
513514
return dynamicIdle;
@@ -585,6 +586,13 @@ private IdleNotifier<IdleNotificationCallback> loopUntil(
585586
return dynamicIdle;
586587
}
587588

589+
private Interrogator getInterrogator() {
590+
if (interrogator == null) {
591+
interrogator = Interrogator.acquire(mainLooper);
592+
}
593+
return interrogator;
594+
}
595+
588596
@Override
589597
public void interruptEspressoTasks() {
590598
controllerHandler.post(

espresso/core/javatests/androidx/test/espresso/base/EspressoIdleTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@
44
import static com.google.common.truth.Truth.assertThat;
55

66
import android.os.Handler;
7+
import android.os.HandlerThread;
8+
import android.os.Looper;
79
import androidx.test.espresso.Espresso;
10+
import androidx.test.espresso.IdlingRegistry;
811
import androidx.test.ext.junit.runners.AndroidJUnit4;
912
import java.util.concurrent.atomic.AtomicBoolean;
1013
import org.junit.Test;
@@ -43,4 +46,22 @@ public void onIdle_afterPostLongDelay() {
4346
Espresso.onIdle();
4447
assertThat(wasRun.get()).isFalse();
4548
}
49+
50+
@Test
51+
public void onIdle_afterPost_backgroundLooper() {
52+
HandlerThread ht = new HandlerThread("onIdle_afterPost_backgroundLooper");
53+
ht.start();
54+
Looper looper = ht.getLooper();
55+
56+
try {
57+
IdlingRegistry.getInstance().registerLooperAsIdlingResource(looper);
58+
AtomicBoolean wasRun = new AtomicBoolean(false);
59+
new Handler(looper).post(() -> wasRun.set(true));
60+
Espresso.onIdle();
61+
assertThat(wasRun.get()).isTrue();
62+
} finally {
63+
IdlingRegistry.getInstance().unregisterLooperAsIdlingResource(looper);
64+
ht.quit();
65+
}
66+
}
4667
}

0 commit comments

Comments
 (0)