From c8e950537d17c2240e237568e1bf2a6a96de0764 Mon Sep 17 00:00:00 2001 From: eguven Date: Wed, 14 Feb 2018 05:43:47 -0800 Subject: [PATCH] Run DownloadManager on a custom thread while testing ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=185674707 --- .../offline/DownloadManagerTest.java | 23 +++--- .../exoplayer2/offline/DownloadManager.java | 7 +- .../DynamicConcatenatingMediaSourceTest.java | 37 +--------- .../dash/offline/DownloadManagerDashTest.java | 35 +++++---- .../dash/offline/DownloadServiceDashTest.java | 57 +++++++++------ .../dash/offline/TestDownloadListener.java | 10 +-- .../exoplayer2/testutil/DummyMainThread.java | 72 +++++++++++++++++++ 7 files changed, 156 insertions(+), 85 deletions(-) create mode 100644 testutils/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java diff --git a/library/core/src/androidTest/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java b/library/core/src/androidTest/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java index 85af6c32cd..10940eec49 100644 --- a/library/core/src/androidTest/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java +++ b/library/core/src/androidTest/java/com/google/android/exoplayer2/offline/DownloadManagerTest.java @@ -24,6 +24,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.offline.DownloadManager.DownloadListener; import com.google.android.exoplayer2.offline.DownloadManager.DownloadState; import com.google.android.exoplayer2.offline.DownloadManager.DownloadState.State; +import com.google.android.exoplayer2.testutil.DummyMainThread; import com.google.android.exoplayer2.testutil.MockitoUtil; import com.google.android.exoplayer2.upstream.DummyDataSource; import com.google.android.exoplayer2.upstream.cache.Cache; @@ -53,12 +54,13 @@ public class DownloadManagerTest extends InstrumentationTestCase { private DownloadManager downloadManager; private File actionFile; private TestDownloadListener testDownloadListener; + private DummyMainThread dummyMainThread; @Override public void setUp() throws Exception { super.setUp(); MockitoUtil.setUpMockito(this); - + dummyMainThread = new DummyMainThread(); actionFile = Util.createTempFile(getInstrumentation().getContext(), "ExoPlayerTest"); testDownloadListener = new TestDownloadListener(); setUpDownloadManager(100); @@ -68,6 +70,7 @@ public class DownloadManagerTest extends InstrumentationTestCase { public void tearDown() throws Exception { releaseDownloadManager(); actionFile.delete(); + dummyMainThread.release(); super.tearDown(); } @@ -76,7 +79,7 @@ public class DownloadManagerTest extends InstrumentationTestCase { releaseDownloadManager(); } try { - runTestOnUiThread( + runOnMainThread( new Runnable() { @Override public void run() { @@ -98,7 +101,7 @@ public class DownloadManagerTest extends InstrumentationTestCase { private void releaseDownloadManager() throws Exception { try { - runTestOnUiThread( + runOnMainThread( new Runnable() { @Override public void run() { @@ -345,7 +348,7 @@ public class DownloadManagerTest extends InstrumentationTestCase { remove2Action.post().assertStarted(); download2Action.post().assertDoesNotStart(); - runTestOnUiThread( + runOnMainThread( new Runnable() { @Override public void run() { @@ -368,7 +371,7 @@ public class DownloadManagerTest extends InstrumentationTestCase { // New download actions can be added but they don't start. download3Action.post().assertDoesNotStart(); - runTestOnUiThread( + runOnMainThread( new Runnable() { @Override public void run() { @@ -393,7 +396,7 @@ public class DownloadManagerTest extends InstrumentationTestCase { // download3Action doesn't start as DM was configured to run two downloads in parallel. download3Action.post().assertDoesNotStart(); - runTestOnUiThread( + runOnMainThread( new Runnable() { @Override public void run() { @@ -404,7 +407,7 @@ public class DownloadManagerTest extends InstrumentationTestCase { // download1Action doesn't stop yet as it ignores interrupts. download2Action.assertStopped(); - runTestOnUiThread( + runOnMainThread( new Runnable() { @Override public void run() { @@ -462,6 +465,10 @@ public class DownloadManagerTest extends InstrumentationTestCase { return new FakeDownloadAction(mediaId, true); } + private void runOnMainThread(final Runnable r) throws Throwable { + dummyMainThread.runOnMainThread(r); + } + private static final class TestDownloadListener implements DownloadListener { private ConditionVariable downloadFinishedCondition; @@ -544,7 +551,7 @@ public class DownloadManagerTest extends InstrumentationTestCase { } private FakeDownloadAction post() throws Throwable { - runTestOnUiThread( + runOnMainThread( new Runnable() { @Override public void run() { diff --git a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java index 046f0d7593..33a5d8b900 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/offline/DownloadManager.java @@ -115,7 +115,12 @@ public final class DownloadManager { tasks = new ArrayList<>(); activeDownloadTasks = new ArrayList<>(); - handler = new Handler(Looper.getMainLooper()); + + Looper looper = Looper.myLooper(); + if (looper == null) { + looper = Looper.getMainLooper(); + } + handler = new Handler(looper); fileIOThread = new HandlerThread("DownloadManager file i/o"); fileIOThread.start(); diff --git a/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java b/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java index a0847bf9ff..a58a427b67 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/source/DynamicConcatenatingMediaSourceTest.java @@ -20,13 +20,12 @@ import static org.junit.Assert.fail; import static org.mockito.Mockito.verify; import android.os.ConditionVariable; -import android.os.Handler; -import android.os.HandlerThread; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.RobolectricUtil; import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; +import com.google.android.exoplayer2.testutil.DummyMainThread; import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeShuffleOrder; import com.google.android.exoplayer2.testutil.FakeTimeline; @@ -779,40 +778,6 @@ public final class DynamicConcatenatingMediaSourceTest { return new FakeTimeline(new TimelineWindowDefinition(index + 1, (index + 1) * 111)); } - private static final class DummyMainThread { - - private final HandlerThread thread; - private final Handler handler; - - private DummyMainThread() { - thread = new HandlerThread("DummyMainThread"); - thread.start(); - handler = new Handler(thread.getLooper()); - } - - /** - * Runs the provided {@link Runnable} on the main thread, blocking until execution completes. - * - * @param runnable The {@link Runnable} to run. - */ - public void runOnMainThread(final Runnable runnable) { - final ConditionVariable finishedCondition = new ConditionVariable(); - handler.post( - new Runnable() { - @Override - public void run() { - runnable.run(); - finishedCondition.open(); - } - }); - assertThat(finishedCondition.block(MediaSourceTestRunner.TIMEOUT_MS)).isTrue(); - } - - public void release() { - thread.quit(); - } - } - private static final class TimelineGrabber implements Runnable { private final MediaSourceTestRunner testRunner; diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java index 996dad3d2b..0d3c6ed0f7 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java +++ b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DownloadManagerDashTest.java @@ -28,6 +28,7 @@ import android.test.UiThreadTest; import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey; +import com.google.android.exoplayer2.testutil.DummyMainThread; import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.MockitoUtil; @@ -53,11 +54,13 @@ public class DownloadManagerDashTest extends InstrumentationTestCase { private RepresentationKey fakeRepresentationKey2; private TestDownloadListener downloadListener; private File actionFile; + private DummyMainThread dummyMainThread; @UiThreadTest @Override public void setUp() throws Exception { super.setUp(); + dummyMainThread = new DummyMainThread(); Context context = getInstrumentation().getContext(); tempFolder = Util.createTempDirectory(context, "ExoPlayerTest"); File cacheFolder = new File(tempFolder, "cache"); @@ -85,6 +88,7 @@ public class DownloadManagerDashTest extends InstrumentationTestCase { public void tearDown() throws Exception { downloadManager.release(); Util.recursiveDelete(tempFolder); + dummyMainThread.release(); super.tearDown(); } @@ -111,7 +115,7 @@ public class DownloadManagerDashTest extends InstrumentationTestCase { // Run DM accessing code on UI/main thread as it should be. Also not to block handling of loaded // actions. - runTestOnUiThread( + dummyMainThread.runOnMainThread( new Runnable() { @Override public void run() { @@ -226,18 +230,25 @@ public class DownloadManagerDashTest extends InstrumentationTestCase { } private void createDownloadManager() { - Factory fakeDataSourceFactory = new FakeDataSource.Factory(null).setFakeDataSet(fakeDataSet); - downloadManager = - new DownloadManager( - new DownloaderConstructorHelper(cache, fakeDataSourceFactory), - 1, - 3, - actionFile.getAbsolutePath(), - DashDownloadAction.DESERIALIZER); + dummyMainThread.runOnMainThread( + new Runnable() { + @Override + public void run() { + Factory fakeDataSourceFactory = + new FakeDataSource.Factory(null).setFakeDataSet(fakeDataSet); + downloadManager = + new DownloadManager( + new DownloaderConstructorHelper(cache, fakeDataSourceFactory), + 1, + 3, + actionFile.getAbsolutePath(), + DashDownloadAction.DESERIALIZER); - downloadListener = new TestDownloadListener(downloadManager, this); - downloadManager.addListener(downloadListener); - downloadManager.startDownloads(); + downloadListener = new TestDownloadListener(downloadManager, dummyMainThread); + downloadManager.addListener(downloadListener); + downloadManager.startDownloads(); + } + }); } } diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java index 8da54ff89e..13c5a5c42f 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java +++ b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/DownloadServiceDashTest.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloadService; import com.google.android.exoplayer2.offline.DownloaderConstructorHelper; import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey; +import com.google.android.exoplayer2.testutil.DummyMainThread; import com.google.android.exoplayer2.testutil.FakeDataSet; import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.TestUtil; @@ -38,6 +39,7 @@ import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.scheduler.Requirements; import com.google.android.exoplayer2.util.scheduler.Scheduler; import java.io.File; +import java.io.IOException; /** * Unit tests for {@link DownloadService}. @@ -53,10 +55,12 @@ public class DownloadServiceDashTest extends InstrumentationTestCase { private DownloadService dashDownloadService; private ConditionVariable pauseDownloadCondition; private TestDownloadListener testDownloadListener; + private DummyMainThread dummyMainThread; @Override public void setUp() throws Exception { super.setUp(); + dummyMainThread = new DummyMainThread(); tempFolder = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest"); cache = new SimpleCache(tempFolder, new NoOpCacheEvictor()); @@ -84,31 +88,36 @@ public class DownloadServiceDashTest extends InstrumentationTestCase { .setRandomData("text_segment_1", 1) .setRandomData("text_segment_2", 2) .setRandomData("text_segment_3", 3); - DataSource.Factory fakeDataSourceFactory = new FakeDataSource.Factory(null) - .setFakeDataSet(fakeDataSet); + final DataSource.Factory fakeDataSourceFactory = + new FakeDataSource.Factory(null).setFakeDataSet(fakeDataSet); fakeRepresentationKey1 = new RepresentationKey(0, 0, 0); fakeRepresentationKey2 = new RepresentationKey(0, 1, 0); context = getInstrumentation().getContext(); - File actionFile = Util.createTempFile(context, "ExoPlayerTest"); - actionFile.delete(); - final DownloadManager dashDownloadManager = - new DownloadManager( - new DownloaderConstructorHelper(cache, fakeDataSourceFactory), - 1, - 3, - actionFile.getAbsolutePath(), - DashDownloadAction.DESERIALIZER); - testDownloadListener = new TestDownloadListener(dashDownloadManager, this); - dashDownloadManager.addListener(testDownloadListener); - dashDownloadManager.startDownloads(); - try { - runTestOnUiThread( + dummyMainThread.runOnMainThread( new Runnable() { @Override public void run() { + File actionFile = null; + try { + actionFile = Util.createTempFile(context, "ExoPlayerTest"); + } catch (IOException e) { + throw new RuntimeException(e); + } + actionFile.delete(); + final DownloadManager dashDownloadManager = + new DownloadManager( + new DownloaderConstructorHelper(cache, fakeDataSourceFactory), + 1, + 3, + actionFile.getAbsolutePath(), + DashDownloadAction.DESERIALIZER); + testDownloadListener = new TestDownloadListener(dashDownloadManager, dummyMainThread); + dashDownloadManager.addListener(testDownloadListener); + dashDownloadManager.startDownloads(); + dashDownloadService = new DownloadService(101010) { @@ -143,16 +152,18 @@ public class DownloadServiceDashTest extends InstrumentationTestCase { @Override public void tearDown() throws Exception { try { - runTestOnUiThread(new Runnable() { - @Override - public void run() { - dashDownloadService.onDestroy(); - } - }); + dummyMainThread.runOnMainThread( + new Runnable() { + @Override + public void run() { + dashDownloadService.onDestroy(); + } + }); } catch (Throwable throwable) { throw new Exception(throwable); } Util.recursiveDelete(tempFolder); + dummyMainThread.release(); super.tearDown(); } @@ -197,7 +208,7 @@ public class DownloadServiceDashTest extends InstrumentationTestCase { } private void callDownloadServiceOnStart(final DashDownloadAction action) throws Throwable { - runTestOnUiThread( + dummyMainThread.runOnMainThread( new Runnable() { @Override public void run() { diff --git a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/TestDownloadListener.java b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/TestDownloadListener.java index 2e6688fe07..6fb89697c7 100644 --- a/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/TestDownloadListener.java +++ b/library/dash/src/androidTest/java/com/google/android/exoplayer2/source/dash/offline/TestDownloadListener.java @@ -17,10 +17,10 @@ package com.google.android.exoplayer2.source.dash.offline; import static com.google.common.truth.Truth.assertThat; -import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.offline.DownloadManager; import com.google.android.exoplayer2.offline.DownloadManager.DownloadListener; import com.google.android.exoplayer2.offline.DownloadManager.DownloadState; +import com.google.android.exoplayer2.testutil.DummyMainThread; /** A {@link DownloadListener} for testing. */ /*package*/ final class TestDownloadListener implements DownloadListener { @@ -28,13 +28,13 @@ import com.google.android.exoplayer2.offline.DownloadManager.DownloadState; private static final int TIMEOUT = 1000; private final DownloadManager downloadManager; - private final InstrumentationTestCase testCase; + private final DummyMainThread dummyMainThread; private final android.os.ConditionVariable downloadFinishedCondition; private Throwable downloadError; - public TestDownloadListener(DownloadManager downloadManager, InstrumentationTestCase testCase) { + public TestDownloadListener(DownloadManager downloadManager, DummyMainThread dummyMainThread) { this.downloadManager = downloadManager; - this.testCase = testCase; + this.dummyMainThread = dummyMainThread; this.downloadFinishedCondition = new android.os.ConditionVariable(); } @@ -55,7 +55,7 @@ import com.google.android.exoplayer2.offline.DownloadManager.DownloadState; * error. */ public void blockUntilTasksCompleteAndThrowAnyDownloadError() throws Throwable { - testCase.runTestOnUiThread( + dummyMainThread.runOnMainThread( new Runnable() { @Override public void run() { diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java new file mode 100644 index 0000000000..6ef3dd0b0d --- /dev/null +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DummyMainThread.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2018 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 com.google.common.truth.Truth.assertThat; + +import android.os.ConditionVariable; +import android.os.Handler; +import android.os.HandlerThread; + +/** Helper class to simulate main/UI thread in tests. */ +public final class DummyMainThread { + + /** Default timeout value used for {@link #runOnMainThread(Runnable)}. */ + public static final int TIMEOUT_MS = 10000; + + private final HandlerThread thread; + private final Handler handler; + + public DummyMainThread() { + thread = new HandlerThread("DummyMainThread"); + thread.start(); + handler = new Handler(thread.getLooper()); + } + + /** + * Runs the provided {@link Runnable} on the main thread, blocking until execution completes or + * until {@link #TIMEOUT_MS} milliseconds have passed. + * + * @param runnable The {@link Runnable} to run. + */ + public void runOnMainThread(final Runnable runnable) { + runOnMainThread(TIMEOUT_MS, runnable); + } + + /** + * Runs the provided {@link Runnable} on the main thread, blocking until execution completes or + * until timeout milliseconds have passed. + * + * @param timeoutMs the maximum time to wait in milliseconds. + * @param runnable The {@link Runnable} to run. + */ + public void runOnMainThread(int timeoutMs, final Runnable runnable) { + final ConditionVariable finishedCondition = new ConditionVariable(); + handler.post( + new Runnable() { + @Override + public void run() { + runnable.run(); + finishedCondition.open(); + } + }); + assertThat(finishedCondition.block(timeoutMs)).isTrue(); + } + + public void release() { + thread.quit(); + } +}