Support multiple DataSource configurations in DataSourceContractTest

PiperOrigin-RevId: 650967939
This commit is contained in:
tonihei 2024-07-10 04:59:30 -07:00 committed by Copybara-Service
parent a202fd0c9c
commit 9d4e43cf55
2 changed files with 435 additions and 426 deletions

View File

@ -25,6 +25,7 @@ import androidx.media3.test.utils.HttpDataSourceTestEnv;
import androidx.test.core.app.ApplicationProvider; import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import org.chromium.net.CronetEngine; import org.chromium.net.CronetEngine;
@ -45,15 +46,16 @@ public class CronetDataSourceContractTest extends DataSourceContractTest {
} }
@Override @Override
protected DataSource createDataSource() { protected List<DataSource> createDataSources() {
@Nullable @Nullable
CronetEngine cronetEngine = CronetEngine cronetEngine =
CronetUtil.buildCronetEngine( CronetUtil.buildCronetEngine(
ApplicationProvider.getApplicationContext(), ApplicationProvider.getApplicationContext(),
/* userAgent= */ "test-agent", /* userAgent= */ "test-agent",
/* preferGMSCoreCronet= */ false); /* preferGooglePlayServices= */ false);
assertThat(cronetEngine).isNotNull(); assertThat(cronetEngine).isNotNull();
return new CronetDataSource.Factory(cronetEngine, executorService).createDataSource(); return ImmutableList.of(
new CronetDataSource.Factory(cronetEngine, executorService).createDataSource());
} }
@Override @Override

View File

@ -17,6 +17,7 @@ package androidx.media3.test.utils;
import static androidx.media3.common.util.Assertions.checkArgument; import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull; import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.castNonNull; import static androidx.media3.common.util.Util.castNonNull;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage; import static com.google.common.truth.Truth.assertWithMessage;
@ -58,9 +59,10 @@ import org.mockito.Mockito;
/** /**
* A collection of contract tests for {@link DataSource} implementations. * A collection of contract tests for {@link DataSource} implementations.
* *
* <p>Subclasses should only include the logic necessary to construct the DataSource and allow it to * <p>Subclasses should only include the logic necessary to construct the DataSource (overriding
* successfully read data. They shouldn't include any new {@link Test @Test} methods - * either {@link #createDataSource()} or {@link #createDataSources()}) and allow it to successfully
* implementation-specific tests should be in a separate class. * read data (overriding {@link #getTestResources()}. They shouldn't include any new {@link
* Test @Test} methods - implementation-specific tests should be in a separate class.
* *
* <p>Most implementations should pass all these tests. If necessary, subclasses can disable tests * <p>Most implementations should pass all these tests. If necessary, subclasses can disable tests
* by overriding the {@link Test @Test} method with a no-op implementation. It's recommended (but * by overriding the {@link Test @Test} method with a no-op implementation. It's recommended (but
@ -72,8 +74,25 @@ public abstract class DataSourceContractTest {
@Rule public final AdditionalFailureInfo additionalFailureInfo = new AdditionalFailureInfo(); @Rule public final AdditionalFailureInfo additionalFailureInfo = new AdditionalFailureInfo();
/** Creates and returns an instance of the {@link DataSource}. */ /**
protected abstract DataSource createDataSource() throws Exception; * Creates and returns an instance of the {@link DataSource}.
*
* <p>Only one of {@link #createDataSource()} and {@link #createDataSources()} should be
* implemented.
*/
protected DataSource createDataSource() throws Exception {
throw new UnsupportedOperationException();
}
/**
* Creates and returns a list of instances of the {@link DataSource}.
*
* <p>Only one of {@link #createDataSource()} and {@link #createDataSources()} should be
* implemented.
*/
protected List<DataSource> createDataSources() throws Exception {
throw new UnsupportedOperationException();
}
/** /**
* Returns the {@link DataSource} that will be included in the {@link TransferListener} callbacks * Returns the {@link DataSource} that will be included in the {@link TransferListener} callbacks
@ -113,238 +132,196 @@ public abstract class DataSourceContractTest {
@Test @Test
public void unboundedDataSpec_readUntilEnd() throws Exception { public void unboundedDataSpec_readUntilEnd() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
try {
long length = dataSource.open(new DataSpec(resource.getUri()));
byte[] data =
unboundedReadsAreIndefinite()
? DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length)
: DataSourceUtil.readToEnd(dataSource);
for (int i = 0; i < resources.size(); i++) { if (length != C.LENGTH_UNSET) {
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); assertThat(length).isEqualTo(resource.getExpectedBytes().length);
TestResource resource = resources.get(i); }
DataSource dataSource = createDataSource(); assertThat(data).isEqualTo(resource.getExpectedBytes());
try { } finally {
long length = dataSource.open(new DataSpec(resource.getUri())); dataSource.close();
byte[] data = }
unboundedReadsAreIndefinite() });
? DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length)
: DataSourceUtil.readToEnd(dataSource);
if (length != C.LENGTH_UNSET) {
assertThat(length).isEqualTo(resource.getExpectedBytes().length);
}
assertThat(data).isEqualTo(resource.getExpectedBytes());
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void dataSpecWithPosition_readUntilEnd() throws Exception { public void dataSpecWithPosition_readUntilEnd() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
try {
long length =
dataSource.open(
new DataSpec.Builder().setUri(resource.getUri()).setPosition(3).build());
byte[] data =
unboundedReadsAreIndefinite()
? DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length - 3)
: DataSourceUtil.readToEnd(dataSource);
for (int i = 0; i < resources.size(); i++) { if (length != C.LENGTH_UNSET) {
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); assertThat(length).isEqualTo(resource.getExpectedBytes().length - 3);
TestResource resource = resources.get(i); }
DataSource dataSource = createDataSource(); byte[] expectedData =
try { Arrays.copyOfRange(
long length = resource.getExpectedBytes(), 3, resource.getExpectedBytes().length);
dataSource.open( assertThat(data).isEqualTo(expectedData);
new DataSpec.Builder().setUri(resource.getUri()).setPosition(3).build()); } finally {
byte[] data = dataSource.close();
unboundedReadsAreIndefinite() }
? DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length - 3) });
: DataSourceUtil.readToEnd(dataSource);
if (length != C.LENGTH_UNSET) {
assertThat(length).isEqualTo(resource.getExpectedBytes().length - 3);
}
byte[] expectedData =
Arrays.copyOfRange(resource.getExpectedBytes(), 3, resource.getExpectedBytes().length);
assertThat(data).isEqualTo(expectedData);
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void dataSpecWithLength_readExpectedRange() throws Exception { public void dataSpecWithLength_readExpectedRange() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
try {
long length =
dataSource.open(
new DataSpec.Builder().setUri(resource.getUri()).setLength(4).build());
byte[] data = DataSourceUtil.readToEnd(dataSource);
for (int i = 0; i < resources.size(); i++) { assertThat(length).isEqualTo(4);
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); byte[] expectedData = Arrays.copyOf(resource.getExpectedBytes(), 4);
TestResource resource = resources.get(i); assertThat(data).isEqualTo(expectedData);
DataSource dataSource = createDataSource(); } finally {
try { dataSource.close();
long length = }
dataSource.open(new DataSpec.Builder().setUri(resource.getUri()).setLength(4).build()); });
byte[] data = DataSourceUtil.readToEnd(dataSource);
assertThat(length).isEqualTo(4);
byte[] expectedData = Arrays.copyOf(resource.getExpectedBytes(), 4);
assertThat(data).isEqualTo(expectedData);
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void dataSpecWithPositionAndLength_readExpectedRange() throws Exception { public void dataSpecWithPositionAndLength_readExpectedRange() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
try {
long length =
dataSource.open(
new DataSpec.Builder()
.setUri(resource.getUri())
.setPosition(2)
.setLength(2)
.build());
byte[] data = DataSourceUtil.readToEnd(dataSource);
for (int i = 0; i < resources.size(); i++) { assertThat(length).isEqualTo(2);
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); byte[] expectedData = Arrays.copyOfRange(resource.getExpectedBytes(), 2, 4);
TestResource resource = resources.get(i); assertThat(data).isEqualTo(expectedData);
DataSource dataSource = createDataSource(); } finally {
try { dataSource.close();
long length = }
dataSource.open( });
new DataSpec.Builder()
.setUri(resource.getUri())
.setPosition(2)
.setLength(2)
.build());
byte[] data = DataSourceUtil.readToEnd(dataSource);
assertThat(length).isEqualTo(2);
byte[] expectedData = Arrays.copyOfRange(resource.getExpectedBytes(), 2, 4);
assertThat(data).isEqualTo(expectedData);
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void dataSpecWithPositionAtEnd_readsZeroBytes() throws Exception { public void dataSpecWithPositionAtEnd_readsZeroBytes() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
int resourceLength = resource.getExpectedBytes().length;
DataSpec dataSpec =
new DataSpec.Builder().setUri(resource.getUri()).setPosition(resourceLength).build();
try {
long length = dataSource.open(dataSpec);
byte[] data =
unboundedReadsAreIndefinite()
? Util.EMPTY_BYTE_ARRAY
: DataSourceUtil.readToEnd(dataSource);
for (int i = 0; i < resources.size(); i++) { // The DataSource.open() contract requires the returned length to equal the length in
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); // the DataSpec if set. This is true even though the DataSource implementation may know
TestResource resource = resources.get(i); // that fewer bytes will be read in this case.
int resourceLength = resource.getExpectedBytes().length; if (length != C.LENGTH_UNSET) {
DataSource dataSource = createDataSource(); assertThat(length).isEqualTo(0);
DataSpec dataSpec = }
new DataSpec.Builder().setUri(resource.getUri()).setPosition(resourceLength).build(); assertThat(data).isEmpty();
try { } finally {
long length = dataSource.open(dataSpec); dataSource.close();
byte[] data = }
unboundedReadsAreIndefinite() });
? Util.EMPTY_BYTE_ARRAY
: DataSourceUtil.readToEnd(dataSource);
// The DataSource.open() contract requires the returned length to equal the length in the
// DataSpec if set. This is true even though the DataSource implementation may know that
// fewer bytes will be read in this case.
if (length != C.LENGTH_UNSET) {
assertThat(length).isEqualTo(0);
}
assertThat(data).isEmpty();
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void dataSpecWithPositionAtEndAndLength_readsZeroBytes() throws Exception { public void dataSpecWithPositionAtEndAndLength_readsZeroBytes() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
int resourceLength = resource.getExpectedBytes().length;
DataSpec dataSpec =
new DataSpec.Builder()
.setUri(resource.getUri())
.setPosition(resourceLength)
.setLength(1)
.build();
try {
long length = dataSource.open(dataSpec);
byte[] data =
unboundedReadsAreIndefinite()
? Util.EMPTY_BYTE_ARRAY
: DataSourceUtil.readToEnd(dataSource);
for (int i = 0; i < resources.size(); i++) { // The DataSource.open() contract requires the returned length to equal the length in
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); // the DataSpec if set. This is true even though the DataSource implementation may know
TestResource resource = resources.get(i); // that fewer bytes will be read in this case.
int resourceLength = resource.getExpectedBytes().length; assertThat(length).isEqualTo(1);
DataSource dataSource = createDataSource(); assertThat(data).isEmpty();
DataSpec dataSpec = } finally {
new DataSpec.Builder() dataSource.close();
.setUri(resource.getUri()) }
.setPosition(resourceLength) });
.setLength(1)
.build();
try {
long length = dataSource.open(dataSpec);
byte[] data =
unboundedReadsAreIndefinite()
? Util.EMPTY_BYTE_ARRAY
: DataSourceUtil.readToEnd(dataSource);
// The DataSource.open() contract requires the returned length to equal the length in the
// DataSpec if set. This is true even though the DataSource implementation may know that
// fewer bytes will be read in this case.
assertThat(length).isEqualTo(1);
assertThat(data).isEmpty();
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void dataSpecWithPositionOutOfRange_throwsPositionOutOfRangeException() throws Exception { public void dataSpecWithPositionOutOfRange_throwsPositionOutOfRangeException() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
int resourceLength = resource.getExpectedBytes().length;
for (int i = 0; i < resources.size(); i++) { DataSpec dataSpec =
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); new DataSpec.Builder()
TestResource resource = resources.get(i); .setUri(resource.getUri())
int resourceLength = resource.getExpectedBytes().length; .setPosition(resourceLength + 1)
DataSource dataSource = createDataSource(); .build();
DataSpec dataSpec = try {
new DataSpec.Builder().setUri(resource.getUri()).setPosition(resourceLength + 1).build(); IOException exception =
try { assertThrows(IOException.class, () -> dataSource.open(dataSpec));
IOException exception = assertThrows(IOException.class, () -> dataSource.open(dataSpec)); assertThat(DataSourceException.isCausedByPositionOutOfRange(exception)).isTrue();
assertThat(DataSourceException.isCausedByPositionOutOfRange(exception)).isTrue(); } finally {
} finally { dataSource.close();
dataSource.close(); }
} });
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void dataSpecWithEndPositionOutOfRange_readsToEnd() throws Exception { public void dataSpecWithEndPositionOutOfRange_readsToEnd() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
int resourceLength = resource.getExpectedBytes().length;
DataSpec dataSpec =
new DataSpec.Builder()
.setUri(resource.getUri())
.setPosition(resourceLength - 1)
.setLength(2)
.build();
try {
long length = dataSource.open(dataSpec);
byte[] data = DataSourceUtil.readExactly(dataSource, /* length= */ 1);
// TODO: Decide what the allowed behavior should be for the next read, and assert it.
for (int i = 0; i < resources.size(); i++) { // The DataSource.open() contract requires the returned length to equal the length in
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); // the DataSpec if set. This is true even though the DataSource implementation may know
TestResource resource = resources.get(i); // that fewer bytes will be read in this case.
int resourceLength = resource.getExpectedBytes().length; assertThat(length).isEqualTo(2);
DataSource dataSource = createDataSource(); byte[] expectedData =
DataSpec dataSpec = Arrays.copyOfRange(resource.getExpectedBytes(), resourceLength - 1, resourceLength);
new DataSpec.Builder() assertThat(data).isEqualTo(expectedData);
.setUri(resource.getUri()) } finally {
.setPosition(resourceLength - 1) dataSource.close();
.setLength(2) }
.build(); });
try {
long length = dataSource.open(dataSpec);
byte[] data = DataSourceUtil.readExactly(dataSource, /* length= */ 1);
// TODO: Decide what the allowed behavior should be for the next read, and assert it.
// The DataSource.open() contract requires the returned length to equal the length in the
// DataSpec if set. This is true even though the DataSource implementation may know that
// fewer bytes will be read in this case.
assertThat(length).isEqualTo(2);
byte[] expectedData =
Arrays.copyOfRange(resource.getExpectedBytes(), resourceLength - 1, resourceLength);
assertThat(data).isEqualTo(expectedData);
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
/** /**
@ -354,293 +331,323 @@ public abstract class DataSourceContractTest {
*/ */
@Test @Test
public void unboundedDataSpecWithGzipFlag_readUntilEnd() throws Exception { public void unboundedDataSpecWithGzipFlag_readUntilEnd() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
try {
long length =
dataSource.open(
new DataSpec.Builder()
.setUri(resource.getUri())
.setFlags(DataSpec.FLAG_ALLOW_GZIP)
.build());
byte[] data =
unboundedReadsAreIndefinite()
? DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length)
: DataSourceUtil.readToEnd(dataSource);
for (int i = 0; i < resources.size(); i++) { if (length != C.LENGTH_UNSET) {
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); assertThat(length).isEqualTo(resource.getExpectedBytes().length);
TestResource resource = resources.get(i); }
DataSource dataSource = createDataSource(); assertThat(data).isEqualTo(resource.getExpectedBytes());
try { } finally {
long length = dataSource.close();
dataSource.open( }
new DataSpec.Builder() });
.setUri(resource.getUri())
.setFlags(DataSpec.FLAG_ALLOW_GZIP)
.build());
byte[] data =
unboundedReadsAreIndefinite()
? DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length)
: DataSourceUtil.readToEnd(dataSource);
if (length != C.LENGTH_UNSET) {
assertThat(length).isEqualTo(resource.getExpectedBytes().length);
}
assertThat(data).isEqualTo(resource.getExpectedBytes());
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void uriSchemeIsCaseInsensitive() throws Exception { public void uriSchemeIsCaseInsensitive() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
@Nullable String scheme = resource.getUri().getScheme();
if (scheme == null) {
// No scheme for which to check case-insensitivity.
return;
}
Uri uri =
resource
.getUri()
.buildUpon()
.scheme(invertAsciiCaseOfEveryOtherCharacter(scheme))
.build();
try {
long length = dataSource.open(new DataSpec.Builder().setUri(uri).build());
byte[] data =
unboundedReadsAreIndefinite()
? DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length)
: DataSourceUtil.readToEnd(dataSource);
for (int i = 0; i < resources.size(); i++) { if (length != C.LENGTH_UNSET) {
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); assertThat(length).isEqualTo(resource.getExpectedBytes().length);
TestResource resource = resources.get(i); }
@Nullable String scheme = resource.getUri().getScheme(); assertThat(data).isEqualTo(resource.getExpectedBytes());
if (scheme == null) { } finally {
// No scheme for which to check case-insensitivity. dataSource.close();
continue; }
} });
DataSource dataSource = createDataSource();
Uri uri =
resource
.getUri()
.buildUpon()
.scheme(invertAsciiCaseOfEveryOtherCharacter(scheme))
.build();
try {
long length = dataSource.open(new DataSpec.Builder().setUri(uri).build());
byte[] data =
unboundedReadsAreIndefinite()
? DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length)
: DataSourceUtil.readToEnd(dataSource);
if (length != C.LENGTH_UNSET) {
assertThat(length).isEqualTo(resource.getExpectedBytes().length);
}
assertThat(data).isEqualTo(resource.getExpectedBytes());
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void resourceNotFound() throws Exception { public void resourceNotFound() throws Exception {
DataSource dataSource = createDataSource(); forAllDataSources(
assertThrows(IOException.class, () -> dataSource.open(new DataSpec(getNotFoundUri()))); dataSource -> {
dataSource.close(); assertThrows(IOException.class, () -> dataSource.open(new DataSpec(getNotFoundUri())));
dataSource.close();
});
} }
@Test @Test
public void transferListenerCallbacks() throws Exception { public void transferListenerCallbacks() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
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;
for (int i = 0; i < resources.size(); i++) { DataSpec dataSpec = new DataSpec.Builder().setUri(resource.getUri()).build();
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); try {
DataSource dataSource = createDataSource(); dataSource.open(dataSpec);
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); // Verify onTransferInitializing() and onTransferStart() have been called exactly from
DataSpec dataSpec = new DataSpec.Builder().setUri(resource.getUri()).build(); // DataSource.open().
try { ArgumentCaptor<DataSpec> dataSpecArgumentCaptor =
dataSource.open(dataSpec); ArgumentCaptor.forClass(DataSpec.class);
ArgumentCaptor<Boolean> 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();
// Verify onTransferInitializing() and onTransferStart() have been called exactly from if (unboundedReadsAreIndefinite()) {
// DataSource.open(). DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length);
ArgumentCaptor<DataSpec> dataSpecArgumentCaptor = ArgumentCaptor.forClass(DataSpec.class); } else {
ArgumentCaptor<Boolean> isNetworkArgumentCaptor = ArgumentCaptor.forClass(Boolean.class); DataSourceUtil.readToEnd(dataSource);
inOrder }
.verify(listener) // Verify sufficient onBytesTransferred() callbacks have been triggered before closing
.onTransferInitializing( // the DataSource.
eq(callbackSource), assertThat(listener.bytesTransferred).isAtLeast(resource.getExpectedBytes().length);
dataSpecArgumentCaptor.capture(),
isNetworkArgumentCaptor.capture());
reportedDataSpec = dataSpecArgumentCaptor.getValue();
reportedNetwork = isNetworkArgumentCaptor.getValue();
inOrder
.verify(listener)
.onTransferStart(callbackSource, castNonNull(reportedDataSpec), reportedNetwork);
inOrder.verifyNoMoreInteractions();
if (unboundedReadsAreIndefinite()) { } finally {
DataSourceUtil.readExactly(dataSource, resource.getExpectedBytes().length); dataSource.close();
} else { inOrder
DataSourceUtil.readToEnd(dataSource); .verify(listener)
} .onTransferEnd(callbackSource, castNonNull(reportedDataSpec), reportedNetwork);
// Verify sufficient onBytesTransferred() callbacks have been triggered before closing the inOrder.verifyNoMoreInteractions();
// DataSource. }
assertThat(listener.bytesTransferred).isAtLeast(resource.getExpectedBytes().length); });
} finally {
dataSource.close();
inOrder
.verify(listener)
.onTransferEnd(callbackSource, castNonNull(reportedDataSpec), reportedNetwork);
inOrder.verifyNoMoreInteractions();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void resourceNotFound_transferListenerCallbacks() throws Exception { public void resourceNotFound_transferListenerCallbacks() throws Exception {
DataSource dataSource = createDataSource(); forAllDataSources(
TransferListener listener = mock(TransferListener.class); dataSource -> {
dataSource.addTransferListener(listener); TransferListener listener = mock(TransferListener.class);
@Nullable DataSource callbackSource = getTransferListenerDataSource(); dataSource.addTransferListener(listener);
if (callbackSource == null) { @Nullable DataSource callbackSource = getTransferListenerDataSource();
callbackSource = dataSource; if (callbackSource == null) {
} callbackSource = dataSource;
}
assertThrows(IOException.class, () -> dataSource.open(new DataSpec(getNotFoundUri()))); assertThrows(IOException.class, () -> dataSource.open(new DataSpec(getNotFoundUri())));
// Verify onTransferInitializing() has been called exactly from DataSource.open(). // Verify onTransferInitializing() has been called exactly from DataSource.open().
verify(listener).onTransferInitializing(eq(callbackSource), any(), anyBoolean()); verify(listener).onTransferInitializing(eq(callbackSource), any(), anyBoolean());
verifyNoMoreInteractions(listener); verifyNoMoreInteractions(listener);
dataSource.close(); dataSource.close();
verifyNoMoreInteractions(listener); verifyNoMoreInteractions(listener);
});
} }
@Test @Test
public void getUri_returnsNonNullValueOnlyWhileOpen() throws Exception { public void getUri_returnsNonNullValueOnlyWhileOpen() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
try {
assertThat(dataSource.getUri()).isNull();
for (int i = 0; i < resources.size(); i++) { dataSource.open(new DataSpec(resource.getUri()));
additionalFailureInfo.setInfo(getFailureLabel(resources, i));
TestResource resource = resources.get(i);
DataSource dataSource = createDataSource();
try {
assertThat(dataSource.getUri()).isNull();
dataSource.open(new DataSpec(resource.getUri())); assertThat(dataSource.getUri()).isNotNull();
} finally {
assertThat(dataSource.getUri()).isNotNull(); dataSource.close();
} finally { }
dataSource.close(); assertThat(dataSource.getUri()).isNull();
} });
assertThat(dataSource.getUri()).isNull();
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void getUri_resourceNotFound_returnsNullIfNotOpened() throws Exception { public void getUri_resourceNotFound_returnsNullIfNotOpened() throws Exception {
DataSource dataSource = createDataSource(); forAllDataSources(
dataSource -> {
assertThat(dataSource.getUri()).isNull();
assertThat(dataSource.getUri()).isNull(); assertThrows(IOException.class, () -> dataSource.open(new DataSpec(getNotFoundUri())));
dataSource.close();
assertThrows(IOException.class, () -> dataSource.open(new DataSpec(getNotFoundUri()))); assertThat(dataSource.getUri()).isNull();
dataSource.close(); });
assertThat(dataSource.getUri()).isNull();
} }
@Test @Test
public void getResponseHeaders_noNullKeysOrValues() throws Exception { public void getResponseHeaders_noNullKeysOrValues() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
try {
dataSource.open(new DataSpec(resource.getUri()));
for (int i = 0; i < resources.size(); i++) { Map<String, List<String>> responseHeaders = dataSource.getResponseHeaders();
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); assertThat(responseHeaders).doesNotContainKey(null);
TestResource resource = resources.get(i); assertThat(responseHeaders.values()).doesNotContain(null);
DataSource dataSource = createDataSource(); for (List<String> value : responseHeaders.values()) {
try { assertThat(value).doesNotContain(null);
dataSource.open(new DataSpec(resource.getUri())); }
} finally {
Map<String, List<String>> responseHeaders = dataSource.getResponseHeaders(); dataSource.close();
assertThat(responseHeaders).doesNotContainKey(null); }
assertThat(responseHeaders.values()).doesNotContain(null); });
for (List<String> value : responseHeaders.values()) {
assertThat(value).doesNotContain(null);
}
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void getResponseHeaders_caseInsensitive() throws Exception { public void getResponseHeaders_caseInsensitive() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
try {
dataSource.open(new DataSpec(resource.getUri()));
for (int i = 0; i < resources.size(); i++) { Map<String, List<String>> responseHeaders = dataSource.getResponseHeaders();
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); for (String key : responseHeaders.keySet()) {
TestResource resource = resources.get(i); String caseFlippedKey = invertAsciiCaseOfEveryOtherCharacter(key);
DataSource dataSource = createDataSource(); assertWithMessage("key='%s', caseFlippedKey='%s'", key, caseFlippedKey)
try { .that(responseHeaders.get(caseFlippedKey))
dataSource.open(new DataSpec(resource.getUri())); .isEqualTo(responseHeaders.get(key));
}
Map<String, List<String>> responseHeaders = dataSource.getResponseHeaders(); } finally {
for (String key : responseHeaders.keySet()) { dataSource.close();
String caseFlippedKey = invertAsciiCaseOfEveryOtherCharacter(key); }
assertWithMessage("key='%s', caseFlippedKey='%s'", key, caseFlippedKey) });
.that(responseHeaders.get(caseFlippedKey))
.isEqualTo(responseHeaders.get(key));
}
} finally {
dataSource.close();
}
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void getResponseHeaders_isEmptyWhileNotOpen() throws Exception { public void getResponseHeaders_isEmptyWhileNotOpen() throws Exception {
ImmutableList<TestResource> resources = getTestResources(); forAllTestResourcesAndDataSources(
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource."); (resource, dataSource) -> {
try {
assertThat(dataSource.getResponseHeaders()).isEmpty();
for (int i = 0; i < resources.size(); i++) { dataSource.open(new DataSpec(resource.getUri()));
additionalFailureInfo.setInfo(getFailureLabel(resources, i)); } finally {
TestResource resource = resources.get(i); dataSource.close();
DataSource dataSource = createDataSource(); }
try { assertThat(dataSource.getResponseHeaders()).isEmpty();
assertThat(dataSource.getResponseHeaders()).isEmpty(); });
dataSource.open(new DataSpec(resource.getUri()));
} finally {
dataSource.close();
}
assertThat(dataSource.getResponseHeaders()).isEmpty();
additionalFailureInfo.setInfo(null);
}
} }
@Test @Test
public void getResponseHeaders_resourceNotFound_isEmptyWhileNotOpen() throws Exception { public void getResponseHeaders_resourceNotFound_isEmptyWhileNotOpen() throws Exception {
DataSource dataSource = createDataSource(); forAllDataSources(
dataSource -> {
assertThat(dataSource.getResponseHeaders()).isEmpty();
assertThat(dataSource.getResponseHeaders()).isEmpty(); assertThrows(IOException.class, () -> dataSource.open(new DataSpec(getNotFoundUri())));
dataSource.close();
assertThrows(IOException.class, () -> dataSource.open(new DataSpec(getNotFoundUri()))); assertThat(dataSource.getResponseHeaders()).isEmpty();
dataSource.close(); });
assertThat(dataSource.getResponseHeaders()).isEmpty();
} }
/** Build a label to make it clear which resource caused a given test failure. */ private interface TestResourceAndDataSourceTest {
private static String getFailureLabel(List<TestResource> resources, int i) { void run(TestResource resource, DataSource dataSource) throws Exception;
}
private interface DataSourceTest {
void run(DataSource dataSource) throws Exception;
}
private void forAllTestResourcesAndDataSources(TestResourceAndDataSourceTest test)
throws Exception {
ImmutableList<TestResource> resources = getTestResources();
Assertions.checkArgument(!resources.isEmpty(), "Must provide at least one test resource.");
for (int i = 0; i < resources.size(); i++) {
List<DataSource> dataSources = createDataSourcesInternal();
for (int j = 0; j < dataSources.size(); j++) {
additionalFailureInfo.setInfo(getFailureLabel(resources, i, dataSources, j));
test.run(resources.get(i), dataSources.get(j));
additionalFailureInfo.setInfo(null);
}
}
}
private void forAllDataSources(DataSourceTest test) throws Exception {
List<DataSource> dataSources = createDataSourcesInternal();
for (int i = 0; i < dataSources.size(); i++) {
additionalFailureInfo.setInfo(getDataSourceLabel(dataSources, i));
test.run(dataSources.get(i));
additionalFailureInfo.setInfo(null);
}
}
private List<DataSource> createDataSourcesInternal() throws Exception {
try {
List<DataSource> dataSources = createDataSources();
checkState(!dataSources.isEmpty(), "Must provide at least on DataSource");
assertThrows(UnsupportedOperationException.class, this::createDataSource);
return dataSources;
} catch (UnsupportedOperationException e) {
// Expected if createDataSources is not implemented.
return ImmutableList.of(createDataSource());
}
}
/** Build a label to make it clear which resource and data source caused a given test failure. */
private static String getFailureLabel(
List<TestResource> resources,
int resourceIndex,
List<DataSource> dataSources,
int dataSourceIndex) {
String resourceLabel = getResourceLabel(resources, resourceIndex);
String dataSourceLabel = getDataSourceLabel(dataSources, dataSourceIndex);
if (resourceLabel.isEmpty()) {
return dataSourceLabel;
} else if (dataSourceLabel.isEmpty()) {
return resourceLabel;
} else {
return dataSourceLabel + ", " + resourceLabel;
}
}
private static String getResourceLabel(List<TestResource> resources, int resourceIndex) {
if (resources.size() == 1) { if (resources.size() == 1) {
return ""; return "";
} else if (resources.get(i).getName() != null) { } else if (resources.get(resourceIndex).getName() != null) {
return "resource name: " + resources.get(i).getName(); return "resource name: " + resources.get(resourceIndex).getName();
} else { } else {
return String.format("resource[%s]", i); return String.format("resource[%s]", resourceIndex);
} }
} }
private static String getDataSourceLabel(List<DataSource> dataSources, int dataSourceIndex) {
if (dataSources.size() == 1) {
return "";
}
return String.format("dataSource[%s]", dataSourceIndex);
}
private static String invertAsciiCaseOfEveryOtherCharacter(String input) { private static String invertAsciiCaseOfEveryOtherCharacter(String input) {
StringBuilder result = new StringBuilder(); StringBuilder result = new StringBuilder();
for (int i = 0; i < input.length(); i++) { for (int i = 0; i < input.length(); i++) {