From 086d8f3a8e3c89499ae0c1778bb0a6015d8d35c4 Mon Sep 17 00:00:00 2001 From: christosts Date: Fri, 12 Feb 2021 15:57:41 +0000 Subject: [PATCH] Contract test for TransferListener callbacks PiperOrigin-RevId: 357190780 --- extensions/cronet/build.gradle | 1 + .../upstream/CacheDataSourceContractTest.java | 11 ++- .../testutil/DataSourceContractTest.java | 96 +++++++++++++++++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/extensions/cronet/build.gradle b/extensions/cronet/build.gradle index f50304fb94..e12f2f050a 100644 --- a/extensions/cronet/build.gradle +++ b/extensions/cronet/build.gradle @@ -28,6 +28,7 @@ dependencies { androidTestImplementation 'androidx.test:rules:' + androidxTestRulesVersion androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion androidTestImplementation 'androidx.multidex:multidex:' + androidxMultidexVersion + androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:' + dexmakerVersion // Instrumentation tests assume that an app-packaged version of cronet is // available. androidTestImplementation 'org.chromium.net:cronet-embedded:72.3626.96' diff --git a/library/core/src/test/java/com/google/android/exoplayer2/upstream/CacheDataSourceContractTest.java b/library/core/src/test/java/com/google/android/exoplayer2/upstream/CacheDataSourceContractTest.java index b75ff45f13..2c7e94ef8c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/upstream/CacheDataSourceContractTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/upstream/CacheDataSourceContractTest.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2.upstream; import android.net.Uri; +import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.testutil.DataSourceContractTest; @@ -42,12 +43,14 @@ public class CacheDataSourceContractTest extends DataSourceContractTest { @Rule public final TemporaryFolder tempFolder = new TemporaryFolder(); private Uri simpleUri; + private FileDataSource fileDataSource; @Before public void setUp() throws IOException { File file = tempFolder.newFile(); Files.write(Paths.get(file.getAbsolutePath()), DATA); simpleUri = Uri.fromFile(file); + fileDataSource = new FileDataSource(); } @Override @@ -71,6 +74,12 @@ public class CacheDataSourceContractTest extends DataSourceContractTest { Util.createTempDirectory(ApplicationProvider.getApplicationContext(), "ExoPlayerTest"); SimpleCache cache = new SimpleCache(tempFolder, new NoOpCacheEvictor(), TestUtil.getInMemoryDatabaseProvider()); - return new CacheDataSource(cache, new FileDataSource()); + return new CacheDataSource(cache, fileDataSource); + } + + @Override + @Nullable + protected DataSource getTransferListenerDataSource() { + return fileDataSource; } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java index 15677ce41e..f8856f60a5 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/DataSourceContractTest.java @@ -17,8 +17,11 @@ package com.google.android.exoplayer2.testutil; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; +import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; import android.net.Uri; import androidx.annotation.Nullable; @@ -26,6 +29,7 @@ import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSpec; +import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; @@ -36,6 +40,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mockito; /** * A collection of contract tests for {@link DataSource} implementations. @@ -59,6 +66,15 @@ public abstract class DataSourceContractTest { /** Creates and returns an instance of the {@link DataSource}. */ protected abstract DataSource createDataSource() throws Exception; + /** + * Returns the {@link DataSource} that will be included in the {@link TransferListener} callbacks + * if different from the {@link DataSource} under test, otherwise null. + */ + @Nullable + protected DataSource getTransferListenerDataSource() { + return null; + } + /** * Returns {@link TestResource} instances. * @@ -241,6 +257,66 @@ public abstract class DataSourceContractTest { } } + @Test + public void transferListenerCallbacks() throws Exception { + ImmutableList resources = getTestResources(); + Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); + + for (int i = 0; i < resources.size(); i++) { + additionalFailureInfo.setInfo(getFailureLabel(resources, i)); + DataSource dataSource = createDataSource(); + FakeTransferListener listener = spy(new FakeTransferListener()); + dataSource.addTransferListener(listener); + InOrder inOrder = Mockito.inOrder(listener); + @Nullable DataSource callbackSource = getTransferListenerDataSource(); + if (callbackSource == null) { + callbackSource = dataSource; + } + DataSpec reportedDataSpec = null; + boolean reportedNetwork = false; + + TestResource resource = resources.get(i); + DataSpec dataSpec = new DataSpec.Builder().setUri(resource.getUri()).build(); + try { + dataSource.open(dataSpec); + + // Verify onTransferInitializing() and onTransferStart() have been called exactly after + // DataSource.open(). + ArgumentCaptor dataSpecArgumentCaptor = ArgumentCaptor.forClass(DataSpec.class); + ArgumentCaptor isNetworkArgumentCaptor = ArgumentCaptor.forClass(Boolean.class); + inOrder + .verify(listener) + .onTransferInitializing( + eq(callbackSource), + dataSpecArgumentCaptor.capture(), + isNetworkArgumentCaptor.capture()); + reportedDataSpec = dataSpecArgumentCaptor.getValue(); + reportedNetwork = isNetworkArgumentCaptor.getValue(); + inOrder + .verify(listener) + .onTransferStart(callbackSource, castNonNull(reportedDataSpec), reportedNetwork); + inOrder.verifyNoMoreInteractions(); + + if (resource.isEndOfInputExpected()) { + Util.readToEnd(dataSource); + } else { + Util.readExactly(dataSource, resource.getExpectedBytes().length); + } + // Verify sufficient onBytesTransferred() callbacks have been triggered before closing the + // DataSource. + assertThat(listener.bytesTransferred).isEqualTo(resource.getExpectedBytes().length); + + } finally { + dataSource.close(); + inOrder + .verify(listener) + .onTransferEnd(callbackSource, castNonNull(reportedDataSpec), reportedNetwork); + inOrder.verifyNoMoreInteractions(); + } + additionalFailureInfo.setInfo(null); + } + } + /** Build a label to make it clear which resource caused a given test failure. */ private static String getFailureLabel(List resources, int i) { if (resources.size() == 1) { @@ -344,4 +420,24 @@ public abstract class DataSourceContractTest { } } } + + /** A {@link TransferListener} that only keeps track of the transferred bytes. */ + public static class FakeTransferListener implements TransferListener { + private int bytesTransferred; + + @Override + public void onTransferInitializing(DataSource source, DataSpec dataSpec, boolean isNetwork) {} + + @Override + public void onTransferStart(DataSource source, DataSpec dataSpec, boolean isNetwork) {} + + @Override + public void onBytesTransferred( + DataSource source, DataSpec dataSpec, boolean isNetwork, int bytesTransferred) { + this.bytesTransferred += bytesTransferred; + } + + @Override + public void onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork) {} + } }