Commit cc97bcb4 by tonihei Committed by Andrew Lewis

Move runUntil method to TestUtil as it's used by multiple tests.

We started using this method from other tests unrelated to
TestExoPlayer, so the method is better placed inside a generic Util
class.

PiperOrigin-RevId: 316675067
parent b7233c28
......@@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.source;
import static com.google.android.exoplayer2.testutil.TestUtil.runMainLooperUntil;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
......@@ -24,7 +25,6 @@ import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.mp4.Mp4Extractor;
import com.google.android.exoplayer2.testutil.TestExoPlayer;
import com.google.android.exoplayer2.upstream.AssetDataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
......@@ -72,7 +72,7 @@ public final class ProgressiveMediaPeriodTest {
}
},
/* positionUs= */ 0);
TestExoPlayer.runUntil(prepareCallbackCalled::get);
runMainLooperUntil(prepareCallbackCalled::get);
mediaPeriod.release();
assertThat(sourceInfoRefreshCalledBeforeOnPrepared.get()).isTrue();
......
......@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.testutil;
import static com.google.android.exoplayer2.testutil.TestUtil.runMainLooperUntil;
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
......@@ -36,11 +37,8 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Supplier;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoListener;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
......@@ -52,30 +50,6 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
*/
public class TestExoPlayer {
/**
* The default timeout applied when calling one of the {@code runUntil} methods. This timeout
* should be sufficient for any condition using a Robolectric test.
*/
public static final long DEFAULT_TIMEOUT_MS = 10_000;
/** Reflectively call Robolectric ShadowLooper#runOneTask. */
private static final Object shadowLooper;
private static final Method runOneTaskMethod;
static {
try {
Class<?> clazz = Class.forName("org.robolectric.Shadows");
Method shadowOfMethod =
Assertions.checkNotNull(clazz.getDeclaredMethod("shadowOf", Looper.class));
shadowLooper =
Assertions.checkNotNull(shadowOfMethod.invoke(new Object(), Looper.getMainLooper()));
runOneTaskMethod = shadowLooper.getClass().getDeclaredMethod("runOneTask");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/** A builder of {@link SimpleExoPlayer} instances for testing. */
public static class Builder {
......@@ -317,7 +291,8 @@ public class TestExoPlayer {
*
* @param player The {@link Player}.
* @param expectedState The expected {@link Player.State}.
* @throws TimeoutException If the {@link #DEFAULT_TIMEOUT_MS default timeout} is exceeded.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public static void runUntilPlaybackState(Player player, @Player.State int expectedState)
throws TimeoutException {
......@@ -336,7 +311,7 @@ public class TestExoPlayer {
}
};
player.addListener(listener);
runUntil(receivedExpectedState::get);
runMainLooperUntil(receivedExpectedState::get);
player.removeListener(listener);
}
......@@ -346,7 +321,8 @@ public class TestExoPlayer {
*
* @param player The {@link Player}.
* @param expectedPlayWhenReady The expected value for {@link Player#getPlayWhenReady()}.
* @throws TimeoutException If the {@link #DEFAULT_TIMEOUT_MS default timeout} is exceeded.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public static void runUntilPlayWhenReady(Player player, boolean expectedPlayWhenReady)
throws TimeoutException {
......@@ -366,7 +342,7 @@ public class TestExoPlayer {
}
};
player.addListener(listener);
runUntil(receivedExpectedPlayWhenReady::get);
runMainLooperUntil(receivedExpectedPlayWhenReady::get);
}
/**
......@@ -375,7 +351,8 @@ public class TestExoPlayer {
*
* @param player The {@link Player}.
* @param expectedTimeline The expected {@link Timeline}.
* @throws TimeoutException If the {@link #DEFAULT_TIMEOUT_MS default timeout} is exceeded.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public static void runUntilTimelineChanged(Player player, Timeline expectedTimeline)
throws TimeoutException {
......@@ -395,7 +372,7 @@ public class TestExoPlayer {
}
};
player.addListener(listener);
runUntil(receivedExpectedTimeline::get);
runMainLooperUntil(receivedExpectedTimeline::get);
}
/**
......@@ -403,7 +380,8 @@ public class TestExoPlayer {
*
* @param player The {@link Player}.
* @return The new {@link Timeline}.
* @throws TimeoutException If the {@link #DEFAULT_TIMEOUT_MS default timeout} is exceeded.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public static Timeline runUntilTimelineChanged(Player player) throws TimeoutException {
verifyMainTestThread(player);
......@@ -417,7 +395,7 @@ public class TestExoPlayer {
}
};
player.addListener(listener);
runUntil(() -> receivedTimeline.get() != null);
runMainLooperUntil(() -> receivedTimeline.get() != null);
return receivedTimeline.get();
}
......@@ -428,7 +406,8 @@ public class TestExoPlayer {
*
* @param player The {@link Player}.
* @param expectedReason The expected {@link Player.DiscontinuityReason}.
* @throws TimeoutException If the {@link #DEFAULT_TIMEOUT_MS default timeout} is exceeded.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public static void runUntilPositionDiscontinuity(
Player player, @Player.DiscontinuityReason int expectedReason) throws TimeoutException {
......@@ -444,7 +423,7 @@ public class TestExoPlayer {
}
};
player.addListener(listener);
runUntil(receivedCallback::get);
runMainLooperUntil(receivedCallback::get);
}
/**
......@@ -452,7 +431,8 @@ public class TestExoPlayer {
*
* @param player The {@link Player}.
* @return The raised {@link ExoPlaybackException}.
* @throws TimeoutException If the {@link #DEFAULT_TIMEOUT_MS default timeout} is exceeded.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public static ExoPlaybackException runUntilError(Player player) throws TimeoutException {
verifyMainTestThread(player);
......@@ -466,7 +446,7 @@ public class TestExoPlayer {
}
};
player.addListener(listener);
runUntil(() -> receivedError.get() != null);
runMainLooperUntil(() -> receivedError.get() != null);
return receivedError.get();
}
......@@ -475,7 +455,8 @@ public class TestExoPlayer {
* callback has been called.
*
* @param player The {@link Player}.
* @throws TimeoutException If the {@link #DEFAULT_TIMEOUT_MS default timeout} is exceeded.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public static void runUntilRenderedFirstFrame(SimpleExoPlayer player) throws TimeoutException {
verifyMainTestThread(player);
......@@ -489,7 +470,7 @@ public class TestExoPlayer {
}
};
player.addVideoListener(listener);
runUntil(receivedCallback::get);
runMainLooperUntil(receivedCallback::get);
}
/**
......@@ -497,7 +478,8 @@ public class TestExoPlayer {
* commands on the internal playback thread.
*
* @param player The {@link Player}.
* @throws TimeoutException If the {@link #DEFAULT_TIMEOUT_MS default timeout} is exceeded.
* @throws TimeoutException If the {@link TestUtil#DEFAULT_TIMEOUT_MS default timeout} is
* exceeded.
*/
public static void runUntilPendingCommandsAreFullyHandled(ExoPlayer player)
throws TimeoutException {
......@@ -510,41 +492,7 @@ public class TestExoPlayer {
.createMessage((type, data) -> receivedMessageCallback.set(true))
.setHandler(Util.createHandler())
.send();
runUntil(receivedMessageCallback::get);
}
/**
* Runs tasks of the main {@link Looper} until the {@code condition} returns {@code true}.
*
* @param condition The condition.
* @throws TimeoutException If the {@link #DEFAULT_TIMEOUT_MS} is exceeded.
*/
public static void runUntil(Supplier<Boolean> condition) throws TimeoutException {
runUntil(condition, DEFAULT_TIMEOUT_MS, Clock.DEFAULT);
}
/**
* Runs tasks of the main {@link Looper} until the {@code condition} returns {@code true}.
*
* @param condition The condition.
* @param timeoutMs The timeout in milliseconds.
* @param clock The {@link Clock} to measure the timeout.
* @throws TimeoutException If the {@code timeoutMs timeout} is exceeded.
*/
public static void runUntil(Supplier<Boolean> condition, long timeoutMs, Clock clock)
throws TimeoutException {
verifyMainTestThread();
try {
long timeoutTimeMs = clock.currentTimeMillis() + timeoutMs;
while (!condition.get()) {
if (clock.currentTimeMillis() >= timeoutTimeMs) {
throw new TimeoutException();
}
runOneTaskMethod.invoke(shadowLooper);
}
} catch (IllegalAccessException | InvocationTargetException e) {
throw new IllegalStateException(e);
}
runMainLooperUntil(receivedMessageCallback::get);
}
private static void verifyMainTestThread(Player player) {
......@@ -553,10 +501,4 @@ public class TestExoPlayer {
throw new IllegalStateException();
}
}
private static void verifyMainTestThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException();
}
}
}
......@@ -26,6 +26,7 @@ import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.media.MediaCodec;
import android.net.Uri;
import android.os.Looper;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.database.DatabaseProvider;
import com.google.android.exoplayer2.database.DefaultDatabaseProvider;
......@@ -40,21 +41,38 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Supplier;
import com.google.android.exoplayer2.util.SystemClock;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Random;
import java.util.concurrent.TimeoutException;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Utility methods for tests.
*/
public class TestUtil {
/**
* The default timeout applied when calling {@link #runMainLooperUntil(Supplier)}. This timeout
* should be sufficient for any condition using a Robolectric test.
*/
public static final long DEFAULT_TIMEOUT_MS = 10_000;
/** Reflectively loaded Robolectric ShadowLooper#runOneTask. */
private static @MonotonicNonNull Object shadowLooper;
private static @MonotonicNonNull Method runOneTaskMethod;
private TestUtil() {}
/**
......@@ -484,4 +502,64 @@ public class TestUtil {
}
});
}
/**
* Runs tasks of the main Robolectric {@link Looper} until the {@code condition} returns {@code
* true}.
*
* <p>Must be called on the main test thread.
*
* @param condition The condition.
* @throws TimeoutException If the {@link #DEFAULT_TIMEOUT_MS} is exceeded.
*/
public static void runMainLooperUntil(Supplier<Boolean> condition) throws TimeoutException {
runMainLooperUntil(condition, DEFAULT_TIMEOUT_MS, Clock.DEFAULT);
}
/**
* Runs tasks of the main Robolectric {@link Looper} until the {@code condition} returns {@code
* true}.
*
* @param condition The condition.
* @param timeoutMs The timeout in milliseconds.
* @param clock The {@link Clock} to measure the timeout.
* @throws TimeoutException If the {@code timeoutMs timeout} is exceeded.
*/
public static void runMainLooperUntil(Supplier<Boolean> condition, long timeoutMs, Clock clock)
throws TimeoutException {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw new IllegalStateException();
}
maybeInitShadowLooperAndRunOneTaskMethod();
try {
long timeoutTimeMs = clock.currentTimeMillis() + timeoutMs;
while (!condition.get()) {
if (clock.currentTimeMillis() >= timeoutTimeMs) {
throw new TimeoutException();
}
runOneTaskMethod.invoke(shadowLooper);
}
} catch (IllegalAccessException e) {
throw new IllegalStateException(e);
} catch (InvocationTargetException e) {
throw new IllegalStateException(e.getCause());
}
}
@EnsuresNonNull({"shadowLooper", "runOneTaskMethod"})
private static void maybeInitShadowLooperAndRunOneTaskMethod() {
if (shadowLooper != null && runOneTaskMethod != null) {
return;
}
try {
Class<?> clazz = Class.forName("org.robolectric.Shadows");
Method shadowOfMethod =
Assertions.checkNotNull(clazz.getDeclaredMethod("shadowOf", Looper.class));
shadowLooper =
Assertions.checkNotNull(shadowOfMethod.invoke(new Object(), Looper.getMainLooper()));
runOneTaskMethod = shadowLooper.getClass().getDeclaredMethod("runOneTask");
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
}
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.testutil;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.Supplier;
import java.util.concurrent.TimeoutException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.LooperMode;
/** Unit test for {@link TestExoPlayer}. */
@RunWith(AndroidJUnit4.class)
@LooperMode(LooperMode.Mode.PAUSED)
public final class TestExoPlayerTest {
@Test
public void runUntil_withConditionAlreadyTrue_returnsImmediately() throws Exception {
Clock mockClock = mock(Clock.class);
TestExoPlayer.runUntil(() -> true, /* timeoutMs= */ 0, mockClock);
verify(mockClock, atMost(1)).currentTimeMillis();
}
@Test
public void runUntil_withConditionThatNeverBecomesTrue_timesOut() {
Clock mockClock = mock(Clock.class);
when(mockClock.currentTimeMillis()).thenReturn(0L, 41L, 42L);
assertThrows(
TimeoutException.class,
() -> TestExoPlayer.runUntil(() -> false, /* timeoutMs= */ 42, mockClock));
verify(mockClock, times(3)).currentTimeMillis();
}
@SuppressWarnings("unchecked")
@Test
public void runUntil_whenConditionBecomesTrueAfterDelay_returnsWhenConditionBecomesTrue()
throws Exception {
Supplier<Boolean> mockCondition = mock(Supplier.class);
when(mockCondition.get())
.thenReturn(false)
.thenReturn(false)
.thenReturn(false)
.thenReturn(false)
.thenReturn(true);
TestExoPlayer.runUntil(mockCondition, /* timeoutMs= */ 5674, mock(Clock.class));
verify(mockCondition, times(5)).get();
}
}
......@@ -16,9 +16,18 @@
package com.google.android.exoplayer2.testutil;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.atMost;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Supplier;
import java.util.concurrent.TimeoutException;
import org.junit.Test;
import org.junit.runner.RunWith;
......@@ -43,4 +52,43 @@ public class TestUtilTest {
long endTimeMs = System.currentTimeMillis();
assertThat(endTimeMs - startTimeMs).isAtLeast(500);
}
@Test
public void runMainLooperUntil_withConditionAlreadyTrue_returnsImmediately() throws Exception {
Clock mockClock = mock(Clock.class);
TestUtil.runMainLooperUntil(() -> true, /* timeoutMs= */ 0, mockClock);
verify(mockClock, atMost(1)).currentTimeMillis();
}
@Test
public void runMainLooperUntil_withConditionThatNeverBecomesTrue_timesOut() {
Clock mockClock = mock(Clock.class);
when(mockClock.currentTimeMillis()).thenReturn(0L, 41L, 42L);
assertThrows(
TimeoutException.class,
() -> TestUtil.runMainLooperUntil(() -> false, /* timeoutMs= */ 42, mockClock));
verify(mockClock, times(3)).currentTimeMillis();
}
@SuppressWarnings("unchecked")
@Test
public void
runMainLooperUntil_whenConditionBecomesTrueAfterDelay_returnsWhenConditionBecomesTrue()
throws Exception {
Supplier<Boolean> mockCondition = mock(Supplier.class);
when(mockCondition.get())
.thenReturn(false)
.thenReturn(false)
.thenReturn(false)
.thenReturn(false)
.thenReturn(true);
TestUtil.runMainLooperUntil(mockCondition, /* timeoutMs= */ 5674, mock(Clock.class));
verify(mockCondition, times(5)).get();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment