Make FakeDataSource able to serve multiple fake files

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=150748155
This commit is contained in:
eguven 2017-03-21 07:05:53 -07:00 committed by Oliver Woodman
parent c32533cad8
commit 065d3dc523
5 changed files with 151 additions and 62 deletions

View File

@ -269,9 +269,8 @@ public class DefaultExtractorInputTest extends TestCase {
public void testSkipFullyLarge() throws Exception { public void testSkipFullyLarge() throws Exception {
// Tests skipping an amount of data that's larger than any internal scratch space. // Tests skipping an amount of data that's larger than any internal scratch space.
int largeSkipSize = 1024 * 1024; int largeSkipSize = 1024 * 1024;
FakeDataSource.Builder builder = new FakeDataSource.Builder(); FakeDataSource testDataSource = new FakeDataSource();
builder.appendReadData(new byte[largeSkipSize]); testDataSource.getDataSet().newDefaultData().appendReadData(new byte[largeSkipSize]);
FakeDataSource testDataSource = builder.build();
testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); testDataSource.open(new DataSpec(Uri.parse(TEST_URI)));
DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET); DefaultExtractorInput input = new DefaultExtractorInput(testDataSource, 0, C.LENGTH_UNSET);
@ -397,29 +396,29 @@ public class DefaultExtractorInputTest extends TestCase {
} }
private static FakeDataSource buildDataSource() throws Exception { private static FakeDataSource buildDataSource() throws Exception {
FakeDataSource.Builder builder = new FakeDataSource.Builder(); FakeDataSource testDataSource = new FakeDataSource();
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3)); testDataSource.getDataSet().newDefaultData()
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6)); .appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 3))
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); .appendReadData(Arrays.copyOfRange(TEST_DATA, 3, 6))
FakeDataSource testDataSource = builder.build(); .appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9));
testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); testDataSource.open(new DataSpec(Uri.parse(TEST_URI)));
return testDataSource; return testDataSource;
} }
private static FakeDataSource buildFailingDataSource() throws Exception { private static FakeDataSource buildFailingDataSource() throws Exception {
FakeDataSource.Builder builder = new FakeDataSource.Builder(); FakeDataSource testDataSource = new FakeDataSource();
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 6)); testDataSource.getDataSet().newDefaultData()
builder.appendReadError(new IOException()); .appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 6))
builder.appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9)); .appendReadError(new IOException())
FakeDataSource testDataSource = builder.build(); .appendReadData(Arrays.copyOfRange(TEST_DATA, 6, 9));
testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); testDataSource.open(new DataSpec(Uri.parse(TEST_URI)));
return testDataSource; return testDataSource;
} }
private static FakeDataSource buildLargeDataSource() throws Exception { private static FakeDataSource buildLargeDataSource() throws Exception {
FakeDataSource.Builder builder = new FakeDataSource.Builder(); FakeDataSource testDataSource = new FakeDataSource();
builder.appendReadData(new byte[LARGE_TEST_DATA_LENGTH]); testDataSource.getDataSet().newDefaultData()
FakeDataSource testDataSource = builder.build(); .appendReadData(new byte[LARGE_TEST_DATA_LENGTH]);
testDataSource.open(new DataSpec(Uri.parse(TEST_URI))); testDataSource.open(new DataSpec(Uri.parse(TEST_URI)));
return testDataSource; return testDataSource;
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.upstream; package com.google.android.exoplayer2.upstream;
import android.net.Uri;
import android.test.MoreAsserts; import android.test.MoreAsserts;
import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
@ -88,12 +89,13 @@ public class DataSourceInputStreamTest extends TestCase {
} }
private static DataSourceInputStream buildTestInputStream() { private static DataSourceInputStream buildTestInputStream() {
FakeDataSource.Builder fakeDataSourceBuilder = new FakeDataSource.Builder() FakeDataSource fakeDataSource = new FakeDataSource();
fakeDataSource.getDataSet().newDefaultData()
.appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 5)) .appendReadData(Arrays.copyOfRange(TEST_DATA, 0, 5))
.appendReadData(Arrays.copyOfRange(TEST_DATA, 5, 10)) .appendReadData(Arrays.copyOfRange(TEST_DATA, 5, 10))
.appendReadData(Arrays.copyOfRange(TEST_DATA, 10, 15)) .appendReadData(Arrays.copyOfRange(TEST_DATA, 10, 15))
.appendReadData(Arrays.copyOfRange(TEST_DATA, 15, TEST_DATA.length)); .appendReadData(Arrays.copyOfRange(TEST_DATA, 15, TEST_DATA.length));
return new DataSourceInputStream(fakeDataSourceBuilder.build(), new DataSpec(null)); return new DataSourceInputStream(fakeDataSource, new DataSpec(Uri.EMPTY));
} }
} }

View File

@ -20,6 +20,7 @@ import android.test.InstrumentationTestCase;
import android.test.MoreAsserts; import android.test.MoreAsserts;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.FakeDataSource; import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.FakeDataSource.FakeData;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.FileDataSource; import com.google.android.exoplayer2.upstream.FileDataSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -197,12 +198,12 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
private CacheDataSource createCacheDataSource(boolean setReadException, private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength, @CacheDataSource.Flags int flags, boolean simulateUnknownLength, @CacheDataSource.Flags int flags,
CacheDataSink cacheWriteDataSink) { CacheDataSink cacheWriteDataSink) {
FakeDataSource.Builder builder = new FakeDataSource.Builder(); FakeDataSource upstream = new FakeDataSource();
FakeData fakeData = upstream.getDataSet().newDefaultData()
.setSimulateUnknownLength(simulateUnknownLength).appendReadData(TEST_DATA);
if (setReadException) { if (setReadException) {
builder.appendReadError(new IOException("Shouldn't read from upstream")); fakeData.appendReadError(new IOException("Shouldn't read from upstream"));
} }
FakeDataSource upstream =
builder.setSimulateUnknownLength(simulateUnknownLength).appendReadData(TEST_DATA).build();
return new CacheDataSource(simpleCache, upstream, new FileDataSource(), cacheWriteDataSink, return new CacheDataSource(simpleCache, upstream, new FileDataSource(), cacheWriteDataSink,
flags, null); flags, null);
} }

View File

@ -139,7 +139,9 @@ public class CacheDataSourceTest2 extends AndroidTestCase {
} }
private static FakeDataSource buildFakeUpstreamSource() { private static FakeDataSource buildFakeUpstreamSource() {
return new FakeDataSource.Builder().appendReadData(DATA).build(); FakeDataSource fakeDataSource = new FakeDataSource();
fakeDataSource.getDataSet().newDefaultData().appendReadData(DATA);
return fakeDataSource;
} }
private static CacheDataSource buildCacheDataSource(Context context, DataSource upstreamSource, private static CacheDataSource buildCacheDataSource(Context context, DataSource upstreamSource,

View File

@ -23,43 +23,79 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
/** /**
* A fake {@link DataSource} capable of simulating various scenarios. * A fake {@link DataSource} capable of simulating various scenarios. It uses a {@link FakeDataSet}
* <p> * instance which determines the response to data access calls.
* The data that will be read from the source can be constructed by calling *
* {@link Builder#appendReadData(byte[])}. Calls to {@link #read(byte[], int, int)} will not span * <p>Multiple fake data can be defined by {@link FakeDataSet#setData(String, byte[])} and {@link
* the boundaries between arrays passed to successive calls, and hence the boundaries control the * FakeDataSet#newData(String)} methods. It's also possible to define a default data by {@link
* FakeDataSet#newDefaultData()}.
*
* <p>{@link FakeDataSet#newData(String)} and {@link FakeDataSet#newDefaultData()} return a {@link
* FakeData} instance which can be used to define specific results during {@link #read(byte[], int,
* int)} calls.
*
* <p>The data that will be read from the source can be constructed by calling {@link
* FakeData#appendReadData(byte[])} Calls to {@link #read(byte[], int, int)} will not span the
* boundaries between arrays passed to successive calls, and hence the boundaries control the
* positions at which read requests to the source may only be partially satisfied. * positions at which read requests to the source may only be partially satisfied.
* <p> *
* Errors can be inserted by calling {@link Builder#appendReadError(IOException)}. An inserted error * <p>Errors can be inserted by calling {@link FakeData#appendReadError(IOException)}. An inserted
* will be thrown from the first call to {@link #read(byte[], int, int)} that attempts to read from * error will be thrown from the first call to {@link #read(byte[], int, int)} that attempts to read
* the corresponding position, and from all subsequent calls to {@link #read(byte[], int, int)} * from the corresponding position, and from all subsequent calls to {@link #read(byte[], int, int)}
* until the source is closed. If the source is closed and re-opened having encountered an error, * until the source is closed. If the source is closed and re-opened having encountered an error,
* that error will not be thrown again. * that error will not be thrown again.
*
* <p>Example usage:
*
* <pre>
* // Create a FakeDataSource then add default data and two FakeData
* // "test_file" throws an IOException when tried to be read until closed and reopened.
* FakeDataSource fakeDataSource = new FakeDataSource();
* fakeDataSource.getDataSet()
* .newDefaultData()
* .appendReadData(defaultData)
* .endData()
* .setData("http:///1", data1)
* .newData("test_file")
* .appendReadError(new IOException())
* .appendReadData(data2);
* // No need to call endData at the end
* </pre>
*/ */
public final class FakeDataSource implements DataSource { public final class FakeDataSource implements DataSource {
private final ArrayList<Segment> segments; private final FakeDataSet fakeDataSet;
private final ArrayList<DataSpec> openedDataSpecs; private final ArrayList<DataSpec> openedDataSpecs;
private final boolean simulateUnknownLength;
private final long totalLength;
private Uri uri; private Uri uri;
private boolean opened; private boolean opened;
private FakeData fakeData;
private int currentSegmentIndex; private int currentSegmentIndex;
private long bytesRemaining; private long bytesRemaining;
private FakeDataSource(boolean simulateUnknownLength, ArrayList<Segment> segments) { public static Factory newFactory(final FakeDataSet fakeDataSet) {
this.simulateUnknownLength = simulateUnknownLength; return new Factory() {
this.segments = segments; @Override
long totalLength = 0; public DataSource createDataSource() {
for (Segment segment : segments) { return new FakeDataSource(fakeDataSet);
totalLength += segment.length; }
} };
this.totalLength = totalLength; }
openedDataSpecs = new ArrayList<>();
public FakeDataSource() {
this(new FakeDataSet());
}
public FakeDataSource(FakeDataSet fakeDataSet) {
this.fakeDataSet = fakeDataSet;
this.openedDataSpecs = new ArrayList<>();
}
public FakeDataSet getDataSet() {
return fakeDataSet;
} }
@Override @Override
@ -69,6 +105,21 @@ public final class FakeDataSource implements DataSource {
opened = true; opened = true;
uri = dataSpec.uri; uri = dataSpec.uri;
openedDataSpecs.add(dataSpec); openedDataSpecs.add(dataSpec);
fakeData = fakeDataSet.getData(uri.toString());
if (fakeData == null) {
throw new IOException("Data not found: " + dataSpec.uri);
}
long totalLength = 0;
for (Segment segment : fakeData.segments) {
totalLength += segment.length;
}
if (totalLength == 0) {
throw new IOException("Data is empty: " + dataSpec.uri);
}
// If the source knows that the request is unsatisfiable then fail. // If the source knows that the request is unsatisfiable then fail.
if (dataSpec.position >= totalLength || (dataSpec.length != C.LENGTH_UNSET if (dataSpec.position >= totalLength || (dataSpec.length != C.LENGTH_UNSET
&& (dataSpec.position + dataSpec.length > totalLength))) { && (dataSpec.position + dataSpec.length > totalLength))) {
@ -78,7 +129,7 @@ public final class FakeDataSource implements DataSource {
boolean findingCurrentSegmentIndex = true; boolean findingCurrentSegmentIndex = true;
currentSegmentIndex = 0; currentSegmentIndex = 0;
int scannedLength = 0; int scannedLength = 0;
for (Segment segment : segments) { for (Segment segment : fakeData.segments) {
segment.bytesRead = segment.bytesRead =
(int) Math.min(Math.max(0, dataSpec.position - scannedLength), segment.length); (int) Math.min(Math.max(0, dataSpec.position - scannedLength), segment.length);
scannedLength += segment.length; scannedLength += segment.length;
@ -91,7 +142,7 @@ public final class FakeDataSource implements DataSource {
// Configure bytesRemaining, and return. // Configure bytesRemaining, and return.
if (dataSpec.length == C.LENGTH_UNSET) { if (dataSpec.length == C.LENGTH_UNSET) {
bytesRemaining = totalLength - dataSpec.position; bytesRemaining = totalLength - dataSpec.position;
return simulateUnknownLength ? C.LENGTH_UNSET : bytesRemaining; return fakeData.simulateUnknownLength ? C.LENGTH_UNSET : bytesRemaining;
} else { } else {
bytesRemaining = dataSpec.length; bytesRemaining = dataSpec.length;
return bytesRemaining; return bytesRemaining;
@ -102,10 +153,10 @@ public final class FakeDataSource implements DataSource {
public int read(byte[] buffer, int offset, int readLength) throws IOException { public int read(byte[] buffer, int offset, int readLength) throws IOException {
Assertions.checkState(opened); Assertions.checkState(opened);
while (true) { while (true) {
if (currentSegmentIndex == segments.size() || bytesRemaining == 0) { if (currentSegmentIndex == fakeData.segments.size() || bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
} }
Segment current = segments.get(currentSegmentIndex); Segment current = fakeData.segments.get(currentSegmentIndex);
if (current.isErrorSegment()) { if (current.isErrorSegment()) {
if (!current.exceptionCleared) { if (!current.exceptionCleared) {
current.exceptionThrown = true; current.exceptionThrown = true;
@ -140,12 +191,13 @@ public final class FakeDataSource implements DataSource {
Assertions.checkState(opened); Assertions.checkState(opened);
opened = false; opened = false;
uri = null; uri = null;
if (currentSegmentIndex < segments.size()) { if (currentSegmentIndex < fakeData.segments.size()) {
Segment current = segments.get(currentSegmentIndex); Segment current = fakeData.segments.get(currentSegmentIndex);
if (current.isErrorSegment() && current.exceptionThrown) { if (current.isErrorSegment() && current.exceptionThrown) {
current.exceptionCleared = true; current.exceptionCleared = true;
} }
} }
fakeData = null;
} }
/** /**
@ -181,16 +233,21 @@ public final class FakeDataSource implements DataSource {
} }
/** /** Container of fake data to be served by a {@link FakeDataSource}. */
* Builder of {@link FakeDataSource} instances. public static final class FakeData {
*/
public static final class Builder {
private final ArrayList<Segment> segments; private final ArrayList<Segment> segments;
private final FakeDataSet dataSet;
private boolean simulateUnknownLength; private boolean simulateUnknownLength;
public Builder() { public FakeData(FakeDataSet dataSet) {
segments = new ArrayList<>(); this.segments = new ArrayList<>();
this.dataSet = dataSet;
}
/** Returns the {@link FakeDataSet} this FakeData belongs to. */
public FakeDataSet endData() {
return dataSet;
} }
/** /**
@ -199,7 +256,7 @@ public final class FakeDataSource implements DataSource {
* the {@link DataSpec#length} of the argument, including the case where the length is equal to * the {@link DataSpec#length} of the argument, including the case where the length is equal to
* {@link C#LENGTH_UNSET}. * {@link C#LENGTH_UNSET}.
*/ */
public Builder setSimulateUnknownLength(boolean simulateUnknownLength) { public FakeData setSimulateUnknownLength(boolean simulateUnknownLength) {
this.simulateUnknownLength = simulateUnknownLength; this.simulateUnknownLength = simulateUnknownLength;
return this; return this;
} }
@ -207,7 +264,7 @@ public final class FakeDataSource implements DataSource {
/** /**
* Appends to the underlying data. * Appends to the underlying data.
*/ */
public Builder appendReadData(byte[] data) { public FakeData appendReadData(byte[] data) {
Assertions.checkState(data != null && data.length > 0); Assertions.checkState(data != null && data.length > 0);
segments.add(new Segment(data, null)); segments.add(new Segment(data, null));
return this; return this;
@ -216,13 +273,41 @@ public final class FakeDataSource implements DataSource {
/** /**
* Appends an error in the underlying data. * Appends an error in the underlying data.
*/ */
public Builder appendReadError(IOException exception) { public FakeData appendReadError(IOException exception) {
segments.add(new Segment(null, exception)); segments.add(new Segment(null, exception));
return this; return this;
} }
}
public FakeDataSource build() { /** A set of {@link FakeData} instances. */
return new FakeDataSource(simulateUnknownLength, segments); public static final class FakeDataSet {
private FakeData defaultData;
private final HashMap<String, FakeData> dataMap;
public FakeDataSet() {
dataMap = new HashMap<>();
}
public FakeData newDefaultData() {
defaultData = new FakeData(this);
return defaultData;
}
public FakeData newData(String uri) {
FakeData data = new FakeData(this);
dataMap.put(uri, data);
return data;
}
public FakeDataSet setData(String uri, byte[] data) {
newData(uri).appendReadData(data);
return this;
}
public FakeData getData(String uri) {
FakeData data = dataMap.get(uri);
return data != null ? data : defaultData;
} }
} }