From a318e56d155db6ec1b8047edc49655eaeb891750 Mon Sep 17 00:00:00 2001 From: tonihei Date: Tue, 26 Jan 2021 16:58:55 +0000 Subject: [PATCH] Fix FakeClock remove messages behaviour. We currently only remove messages that have already been sent to the actual Handler, not the pending ones that are only kept in the FakeClock. Fix this by also removing matching messages from the FakeClock list. PiperOrigin-RevId: 353877049 --- .../exoplayer2/testutil/FakeClock.java | 25 +++++- .../exoplayer2/testutil/FakeClockTest.java | 82 +++++++++++++++++++ 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java index 0c21c1cfae..4dea57f087 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/FakeClock.java @@ -108,6 +108,27 @@ public class FakeClock implements Clock { maybeTriggerMessages(); } + private synchronized void removePendingHandlerMessages(ClockHandler handler, int what) { + for (int i = handlerMessages.size() - 1; i >= 0; i--) { + HandlerMessage message = handlerMessages.get(i); + if (message.handler.equals(handler) && message.what == what) { + handlerMessages.remove(i); + } + } + handler.handler.removeMessages(what); + } + + private synchronized void removePendingHandlerMessages( + ClockHandler handler, @Nullable Object token) { + for (int i = handlerMessages.size() - 1; i >= 0; i--) { + HandlerMessage message = handlerMessages.get(i); + if (message.handler.equals(handler) && (token == null || message.obj == token)) { + handlerMessages.remove(i); + } + } + handler.handler.removeCallbacksAndMessages(token); + } + private synchronized boolean hasPendingMessage(ClockHandler handler, int what) { for (int i = 0; i < handlerMessages.size(); i++) { HandlerMessage message = handlerMessages.get(i); @@ -261,12 +282,12 @@ public class FakeClock implements Clock { @Override public void removeMessages(int what) { - handler.removeMessages(what); + removePendingHandlerMessages(/* handler= */ this, what); } @Override public void removeCallbacksAndMessages(@Nullable Object token) { - handler.removeCallbacksAndMessages(token); + removePendingHandlerMessages(/* handler= */ this, token); } @Override diff --git a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java index 18a0f04116..c6b39d7f27 100644 --- a/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java +++ b/testutils/src/test/java/com/google/android/exoplayer2/testutil/FakeClockTest.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer2.testutil; import static com.google.common.truth.Truth.assertThat; import static org.robolectric.Shadows.shadowOf; +import android.os.ConditionVariable; import android.os.Handler; import android.os.HandlerThread; import android.os.Message; @@ -186,6 +187,87 @@ public final class FakeClockTest { assertTestRunnableStates(new boolean[] {true, true, true, true, true}, testRunnables); } + @Test + public void createHandler_removeMessages_removesMessages() { + HandlerThread handlerThread = new HandlerThread("FakeClockTest"); + handlerThread.start(); + FakeClock fakeClock = new FakeClock(/* initialTimeMs= */ 0); + TestCallback callback = new TestCallback(); + HandlerWrapper handler = fakeClock.createHandler(handlerThread.getLooper(), callback); + TestCallback otherCallback = new TestCallback(); + HandlerWrapper otherHandler = fakeClock.createHandler(handlerThread.getLooper(), otherCallback); + + // Block any further execution on the HandlerThread until we had a chance to remove messages. + ConditionVariable startCondition = new ConditionVariable(); + handler.post(startCondition::block); + TestRunnable testRunnable1 = new TestRunnable(); + TestRunnable testRunnable2 = new TestRunnable(); + Object messageToken = new Object(); + handler.obtainMessage(/* what= */ 1, /* obj= */ messageToken).sendToTarget(); + handler.sendEmptyMessageDelayed(/* what= */ 2, /* delayMs= */ 50); + handler.post(testRunnable1); + handler.postDelayed(testRunnable2, /* delayMs= */ 25); + handler.sendEmptyMessage(/* what= */ 3); + otherHandler.sendEmptyMessage(/* what= */ 2); + + handler.removeMessages(/* what= */ 2); + handler.removeCallbacksAndMessages(messageToken); + + startCondition.open(); + fakeClock.advanceTime(50); + shadowOf(handlerThread.getLooper()).idle(); + + assertThat(callback.messages) + .containsExactly( + new MessageData(/* what= */ 3, /* arg1= */ 0, /* arg2= */ 0, /* obj=*/ null)); + assertThat(testRunnable1.hasRun).isTrue(); + assertThat(testRunnable2.hasRun).isTrue(); + + // Assert that message with same "what" on other handler wasn't removed. + assertThat(otherCallback.messages) + .containsExactly( + new MessageData(/* what= */ 2, /* arg1= */ 0, /* arg2= */ 0, /* obj=*/ null)); + } + + @Test + public void createHandler_removeAllMessages_removesAllMessages() { + HandlerThread handlerThread = new HandlerThread("FakeClockTest"); + handlerThread.start(); + FakeClock fakeClock = new FakeClock(/* initialTimeMs= */ 0); + TestCallback callback = new TestCallback(); + HandlerWrapper handler = fakeClock.createHandler(handlerThread.getLooper(), callback); + TestCallback otherCallback = new TestCallback(); + HandlerWrapper otherHandler = fakeClock.createHandler(handlerThread.getLooper(), otherCallback); + + // Block any further execution on the HandlerThread until we had a chance to remove messages. + ConditionVariable startCondition = new ConditionVariable(); + handler.post(startCondition::block); + TestRunnable testRunnable1 = new TestRunnable(); + TestRunnable testRunnable2 = new TestRunnable(); + Object messageToken = new Object(); + handler.obtainMessage(/* what= */ 1, /* obj= */ messageToken).sendToTarget(); + handler.sendEmptyMessageDelayed(/* what= */ 2, /* delayMs= */ 50); + handler.post(testRunnable1); + handler.postDelayed(testRunnable2, /* delayMs= */ 25); + handler.sendEmptyMessage(/* what= */ 3); + otherHandler.sendEmptyMessage(/* what= */ 1); + + handler.removeCallbacksAndMessages(/* token= */ null); + + startCondition.open(); + fakeClock.advanceTime(50); + shadowOf(handlerThread.getLooper()).idle(); + + assertThat(callback.messages).isEmpty(); + assertThat(testRunnable1.hasRun).isFalse(); + assertThat(testRunnable2.hasRun).isFalse(); + + // Assert that message on other handler wasn't removed. + assertThat(otherCallback.messages) + .containsExactly( + new MessageData(/* what= */ 1, /* arg1= */ 0, /* arg2= */ 0, /* obj=*/ null)); + } + private static void assertTestRunnableStates(boolean[] states, TestRunnable[] testRunnables) { for (int i = 0; i < testRunnables.length; i++) { assertThat(testRunnables[i].hasRun).isEqualTo(states[i]);