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

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.upstream;
import android.net.Uri;
import android.test.MoreAsserts;
import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.TestUtil;
@ -88,12 +89,13 @@ public class DataSourceInputStreamTest extends TestCase {
}
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, 5, 10))
.appendReadData(Arrays.copyOfRange(TEST_DATA, 10, 15))
.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 com.google.android.exoplayer2.C;
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.FileDataSource;
import com.google.android.exoplayer2.util.Util;
@ -197,12 +198,12 @@ public class CacheDataSourceTest extends InstrumentationTestCase {
private CacheDataSource createCacheDataSource(boolean setReadException,
boolean simulateUnknownLength, @CacheDataSource.Flags int flags,
CacheDataSink cacheWriteDataSink) {
FakeDataSource.Builder builder = new FakeDataSource.Builder();
FakeDataSource upstream = new FakeDataSource();
FakeData fakeData = upstream.getDataSet().newDefaultData()
.setSimulateUnknownLength(simulateUnknownLength).appendReadData(TEST_DATA);
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,
flags, null);
}

View File

@ -139,7 +139,9 @@ public class CacheDataSourceTest2 extends AndroidTestCase {
}
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,

View File

@ -23,43 +23,79 @@ import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
/**
* A fake {@link DataSource} capable of simulating various scenarios.
* <p>
* 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
* the boundaries between arrays passed to successive calls, and hence the boundaries control the
* A fake {@link DataSource} capable of simulating various scenarios. It uses a {@link FakeDataSet}
* instance which determines the response to data access calls.
*
* <p>Multiple fake data can be defined by {@link FakeDataSet#setData(String, byte[])} and {@link
* 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.
* <p>
* Errors can be inserted by calling {@link Builder#appendReadError(IOException)}. An inserted error
* will be thrown from the first call to {@link #read(byte[], int, int)} that attempts to read from
* the corresponding position, and from all subsequent calls to {@link #read(byte[], int, int)}
*
* <p>Errors can be inserted by calling {@link FakeData#appendReadError(IOException)}. An inserted
* error will be thrown from the first call to {@link #read(byte[], int, int)} that attempts to read
* 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,
* 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 {
private final ArrayList<Segment> segments;
private final FakeDataSet fakeDataSet;
private final ArrayList<DataSpec> openedDataSpecs;
private final boolean simulateUnknownLength;
private final long totalLength;
private Uri uri;
private boolean opened;
private FakeData fakeData;
private int currentSegmentIndex;
private long bytesRemaining;
private FakeDataSource(boolean simulateUnknownLength, ArrayList<Segment> segments) {
this.simulateUnknownLength = simulateUnknownLength;
this.segments = segments;
long totalLength = 0;
for (Segment segment : segments) {
totalLength += segment.length;
}
this.totalLength = totalLength;
openedDataSpecs = new ArrayList<>();
public static Factory newFactory(final FakeDataSet fakeDataSet) {
return new Factory() {
@Override
public DataSource createDataSource() {
return new FakeDataSource(fakeDataSet);
}
};
}
public FakeDataSource() {
this(new FakeDataSet());
}
public FakeDataSource(FakeDataSet fakeDataSet) {
this.fakeDataSet = fakeDataSet;
this.openedDataSpecs = new ArrayList<>();
}
public FakeDataSet getDataSet() {
return fakeDataSet;
}
@Override
@ -69,6 +105,21 @@ public final class FakeDataSource implements DataSource {
opened = true;
uri = dataSpec.uri;
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 (dataSpec.position >= totalLength || (dataSpec.length != C.LENGTH_UNSET
&& (dataSpec.position + dataSpec.length > totalLength))) {
@ -78,7 +129,7 @@ public final class FakeDataSource implements DataSource {
boolean findingCurrentSegmentIndex = true;
currentSegmentIndex = 0;
int scannedLength = 0;
for (Segment segment : segments) {
for (Segment segment : fakeData.segments) {
segment.bytesRead =
(int) Math.min(Math.max(0, dataSpec.position - scannedLength), segment.length);
scannedLength += segment.length;
@ -91,7 +142,7 @@ public final class FakeDataSource implements DataSource {
// Configure bytesRemaining, and return.
if (dataSpec.length == C.LENGTH_UNSET) {
bytesRemaining = totalLength - dataSpec.position;
return simulateUnknownLength ? C.LENGTH_UNSET : bytesRemaining;
return fakeData.simulateUnknownLength ? C.LENGTH_UNSET : bytesRemaining;
} else {
bytesRemaining = dataSpec.length;
return bytesRemaining;
@ -102,10 +153,10 @@ public final class FakeDataSource implements DataSource {
public int read(byte[] buffer, int offset, int readLength) throws IOException {
Assertions.checkState(opened);
while (true) {
if (currentSegmentIndex == segments.size() || bytesRemaining == 0) {
if (currentSegmentIndex == fakeData.segments.size() || bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
}
Segment current = segments.get(currentSegmentIndex);
Segment current = fakeData.segments.get(currentSegmentIndex);
if (current.isErrorSegment()) {
if (!current.exceptionCleared) {
current.exceptionThrown = true;
@ -140,12 +191,13 @@ public final class FakeDataSource implements DataSource {
Assertions.checkState(opened);
opened = false;
uri = null;
if (currentSegmentIndex < segments.size()) {
Segment current = segments.get(currentSegmentIndex);
if (currentSegmentIndex < fakeData.segments.size()) {
Segment current = fakeData.segments.get(currentSegmentIndex);
if (current.isErrorSegment() && current.exceptionThrown) {
current.exceptionCleared = true;
}
}
fakeData = null;
}
/**
@ -181,16 +233,21 @@ public final class FakeDataSource implements DataSource {
}
/**
* Builder of {@link FakeDataSource} instances.
*/
public static final class Builder {
/** Container of fake data to be served by a {@link FakeDataSource}. */
public static final class FakeData {
private final ArrayList<Segment> segments;
private final FakeDataSet dataSet;
private boolean simulateUnknownLength;
public Builder() {
segments = new ArrayList<>();
public FakeData(FakeDataSet dataSet) {
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
* {@link C#LENGTH_UNSET}.
*/
public Builder setSimulateUnknownLength(boolean simulateUnknownLength) {
public FakeData setSimulateUnknownLength(boolean simulateUnknownLength) {
this.simulateUnknownLength = simulateUnknownLength;
return this;
}
@ -207,7 +264,7 @@ public final class FakeDataSource implements DataSource {
/**
* Appends to the underlying data.
*/
public Builder appendReadData(byte[] data) {
public FakeData appendReadData(byte[] data) {
Assertions.checkState(data != null && data.length > 0);
segments.add(new Segment(data, null));
return this;
@ -216,13 +273,41 @@ public final class FakeDataSource implements DataSource {
/**
* Appends an error in the underlying data.
*/
public Builder appendReadError(IOException exception) {
public FakeData appendReadError(IOException exception) {
segments.add(new Segment(null, exception));
return this;
}
}
public FakeDataSource build() {
return new FakeDataSource(simulateUnknownLength, segments);
/** A set of {@link FakeData} instances. */
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;
}
}