Introduce FakeExtractorInput (based loosely on RecordableExtractorInput).
- Use it to simplify a bunch of tests. - Will also replace RecordableExtractorInput in a subsequent CL. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117220030
This commit is contained in:
parent
a1a48abe92
commit
300b58e530
@ -15,14 +15,9 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.extractor.webm;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer.testutil.FakeDataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer.testutil.FakeExtractorInput;
|
||||
import com.google.android.exoplayer.testutil.TestUtil;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
@ -140,22 +135,15 @@ public class DefaultEbmlReaderTest extends TestCase {
|
||||
|
||||
/**
|
||||
* Helper to build an {@link ExtractorInput} from byte data.
|
||||
* <p>
|
||||
* Each argument must be able to cast to a byte value.
|
||||
*
|
||||
* @param data Zero or more integers with values between {@code 0x00} and {@code 0xFF}.
|
||||
* @return An {@link ExtractorInput} from which the data can be read.
|
||||
* @throws IOException If an error occurs creating the input.
|
||||
*/
|
||||
private static ExtractorInput createTestInput(int... data) throws IOException {
|
||||
byte[] bytes = new byte[data.length];
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
bytes[i] = (byte) data[i];
|
||||
}
|
||||
DataSource dataSource = new FakeDataSource.Builder().appendReadData(bytes).build();
|
||||
dataSource.open(new DataSpec(Uri.parse("http://www.google.com")));
|
||||
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED);
|
||||
return input;
|
||||
private static ExtractorInput createTestInput(int... data) {
|
||||
return new FakeExtractorInput.Builder()
|
||||
.setData(TestUtil.createByteArray(data))
|
||||
.setSimulateUnknownLength(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,26 +16,20 @@
|
||||
package com.google.android.exoplayer.extractor.webm;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer.testutil.FakeDataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSource;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
|
||||
import android.net.Uri;
|
||||
import com.google.android.exoplayer.testutil.FakeExtractorInput;
|
||||
import com.google.android.exoplayer.testutil.FakeExtractorInput.SimulatedIOException;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Tests for {@link VarintReader}.
|
||||
*/
|
||||
public class VarintReaderTest extends TestCase {
|
||||
public final class VarintReaderTest extends TestCase {
|
||||
|
||||
private static final String TEST_URI = "http://www.google.com";
|
||||
private static final byte MAX_BYTE = (byte) 0xFF;
|
||||
|
||||
private static final byte[] DATA_1_BYTE_0 = new byte[] {(byte) 0x80};
|
||||
@ -88,12 +82,10 @@ public class VarintReaderTest extends TestCase {
|
||||
|
||||
public void testReadVarintEndOfInputAtStart() throws IOException, InterruptedException {
|
||||
VarintReader reader = new VarintReader();
|
||||
// Build an input, and read to the end.
|
||||
DataSource dataSource = buildDataSource(new byte[1]);
|
||||
dataSource.open(new DataSpec(Uri.parse(TEST_URI)));
|
||||
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED);
|
||||
int bytesRead = input.read(new byte[1], 0, 1);
|
||||
assertEquals(1, bytesRead);
|
||||
// Build an input with no data.
|
||||
ExtractorInput input = new FakeExtractorInput.Builder()
|
||||
.setSimulateUnknownLength(true)
|
||||
.build();
|
||||
// End of input allowed.
|
||||
long result = reader.readUnsignedVarint(input, true, false, 8);
|
||||
assertEquals(C.RESULT_END_OF_INPUT, result);
|
||||
@ -108,9 +100,10 @@ public class VarintReaderTest extends TestCase {
|
||||
|
||||
public void testReadVarintExceedsMaximumAllowedLength() throws IOException, InterruptedException {
|
||||
VarintReader reader = new VarintReader();
|
||||
DataSource dataSource = buildDataSource(DATA_8_BYTE_0);
|
||||
dataSource.open(new DataSpec(Uri.parse(TEST_URI)));
|
||||
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED);
|
||||
ExtractorInput input = new FakeExtractorInput.Builder()
|
||||
.setData(DATA_8_BYTE_0)
|
||||
.setSimulateUnknownLength(true)
|
||||
.build();
|
||||
long result = reader.readUnsignedVarint(input, false, true, 4);
|
||||
assertEquals(C.RESULT_MAX_LENGTH_EXCEEDED, result);
|
||||
}
|
||||
@ -189,9 +182,10 @@ public class VarintReaderTest extends TestCase {
|
||||
|
||||
private static void testReadVarint(VarintReader reader, boolean removeMask, byte[] data,
|
||||
int expectedLength, long expectedValue) throws IOException, InterruptedException {
|
||||
DataSource dataSource = buildDataSource(data);
|
||||
dataSource.open(new DataSpec(Uri.parse(TEST_URI)));
|
||||
ExtractorInput input = new DefaultExtractorInput(dataSource, 0, C.LENGTH_UNBOUNDED);
|
||||
ExtractorInput input = new FakeExtractorInput.Builder()
|
||||
.setData(data)
|
||||
.setSimulateUnknownLength(true)
|
||||
.build();
|
||||
long result = reader.readUnsignedVarint(input, false, removeMask, 8);
|
||||
assertEquals(expectedLength, input.getPosition());
|
||||
assertEquals(expectedValue, result);
|
||||
@ -199,45 +193,22 @@ public class VarintReaderTest extends TestCase {
|
||||
|
||||
private static void testReadVarintFlaky(VarintReader reader, boolean removeMask, byte[] data,
|
||||
int expectedLength, long expectedValue) throws IOException, InterruptedException {
|
||||
DataSource dataSource = buildFlakyDataSource(data);
|
||||
ExtractorInput input = null;
|
||||
long position = 0;
|
||||
ExtractorInput input = new FakeExtractorInput.Builder()
|
||||
.setData(data)
|
||||
.setSimulateUnknownLength(true)
|
||||
.setSimulateIOErrors(true)
|
||||
.setSimulatePartialReads(true)
|
||||
.build();
|
||||
long result = -1;
|
||||
while (result == -1) {
|
||||
dataSource.open(new DataSpec(Uri.parse(TEST_URI), position, C.LENGTH_UNBOUNDED, null));
|
||||
input = new DefaultExtractorInput(dataSource, position, C.LENGTH_UNBOUNDED);
|
||||
try {
|
||||
result = reader.readUnsignedVarint(input, false, removeMask, 8);
|
||||
position = input.getPosition();
|
||||
} catch (IOException e) {
|
||||
// Expected. We'll try again from the position that the input was advanced to.
|
||||
position = input.getPosition();
|
||||
dataSource.close();
|
||||
} catch (SimulatedIOException e) {
|
||||
// Expected.
|
||||
}
|
||||
}
|
||||
assertEquals(expectedLength, input.getPosition());
|
||||
assertEquals(expectedValue, result);
|
||||
}
|
||||
|
||||
private static DataSource buildDataSource(byte[] data) {
|
||||
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
||||
builder.appendReadData(data);
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
private static DataSource buildFlakyDataSource(byte[] data) {
|
||||
FakeDataSource.Builder builder = new FakeDataSource.Builder();
|
||||
builder.appendReadError(new IOException("A"));
|
||||
builder.appendReadData(new byte[] {data[0]});
|
||||
if (data.length > 1) {
|
||||
builder.appendReadError(new IOException("B"));
|
||||
builder.appendReadData(new byte[] {data[1]});
|
||||
}
|
||||
if (data.length > 2) {
|
||||
builder.appendReadError(new IOException("C"));
|
||||
builder.appendReadData(Arrays.copyOfRange(data, 2, data.length));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ public final class FakeDataSource implements DataSource {
|
||||
private int currentSegmentIndex;
|
||||
private long bytesRemaining;
|
||||
|
||||
public FakeDataSource(boolean simulateUnknownLength, ArrayList<Segment> segments) {
|
||||
private FakeDataSource(boolean simulateUnknownLength, ArrayList<Segment> segments) {
|
||||
this.simulateUnknownLength = simulateUnknownLength;
|
||||
this.segments = segments;
|
||||
long totalLength = 0;
|
||||
@ -163,7 +163,7 @@ public final class FakeDataSource implements DataSource {
|
||||
/**
|
||||
* Builder of {@link FakeDataSource} instances.
|
||||
*/
|
||||
public static class Builder {
|
||||
public static final class Builder {
|
||||
|
||||
private final ArrayList<Segment> segments;
|
||||
private boolean simulateUnknownLength;
|
||||
|
@ -0,0 +1,270 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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.exoplayer.testutil;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
|
||||
import android.util.SparseBooleanArray;
|
||||
|
||||
import junit.framework.Assert;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* A fake {@link ExtractorInput} capable of simulating various scenarios.
|
||||
* <p>
|
||||
* Read, skip and peek errors can be simulated using {@link Builder#setSimulateIOErrors)}. When
|
||||
* enabled each read and skip will throw a {@link SimulatedIOException} unless one has already been
|
||||
* thrown from the current position. Each peek will throw {@link SimulatedIOException} unless one
|
||||
* has already been thrown from the current peek position. When a {@link SimulatedIOException} is
|
||||
* thrown the read position is left unchanged and the peek position is reset back to the read
|
||||
* position.
|
||||
* <p>
|
||||
* Partial reads and skips can be simulated using {@link Builder#setSimulatePartialReads}. When
|
||||
* enabled, {@link #read(byte[], int, int)} and {@link #skip(int)} calls will only read or skip a
|
||||
* single byte unless a partial read or skip has already been performed that had the same target
|
||||
* position. For example, a first read request for 10 bytes will be partially satisfied by reading
|
||||
* a single byte and advancing the position to 1. If the following read request attempts to read 9
|
||||
* bytes then it will be fully satisfied, since it has the same target position of 10.
|
||||
* <p>
|
||||
* Unknown data length can be simulated using {@link Builder#setSimulateUnknownLength}. When enabled
|
||||
* {@link getLength()} will return {@link C#LENGTH_UNBOUNDED} rather than the length of the data.
|
||||
*/
|
||||
public final class FakeExtractorInput implements ExtractorInput {
|
||||
|
||||
/**
|
||||
* Thrown when simulating an {@link IOException}.
|
||||
*/
|
||||
public static final class SimulatedIOException extends IOException {
|
||||
|
||||
public SimulatedIOException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final byte[] data;
|
||||
private final boolean simulateUnknownLength;
|
||||
private final boolean simulatePartialReads;
|
||||
private final boolean simulateIOErrors;
|
||||
|
||||
private int readPosition;
|
||||
private int peekPosition;
|
||||
|
||||
private final SparseBooleanArray partiallySatisfiedTargetPositions;
|
||||
private final SparseBooleanArray failedReadPositions;
|
||||
private final SparseBooleanArray failedPeekPositions;
|
||||
|
||||
private FakeExtractorInput(byte[] data, boolean simulateUnknownLength,
|
||||
boolean simulatePartialReads, boolean simulateIOErrors) {
|
||||
this.data = data;
|
||||
this.simulateUnknownLength = simulateUnknownLength;
|
||||
this.simulatePartialReads = simulatePartialReads;
|
||||
this.simulateIOErrors = simulateIOErrors;
|
||||
partiallySatisfiedTargetPositions = new SparseBooleanArray();
|
||||
failedReadPositions = new SparseBooleanArray();
|
||||
failedPeekPositions = new SparseBooleanArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the read and peek positions.
|
||||
*
|
||||
* @param position The position to set.
|
||||
*/
|
||||
public void setPosition(int position) {
|
||||
Assert.assertTrue(0 <= position && position < data.length);
|
||||
readPosition = position;
|
||||
peekPosition = position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] target, int offset, int length) throws IOException {
|
||||
length = getReadLength(length);
|
||||
if (readFully(target, offset, length, true)) {
|
||||
return length;
|
||||
}
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
|
||||
throws IOException {
|
||||
if (!checkXFully(allowEndOfInput, readPosition, length, failedReadPositions)) {
|
||||
return false;
|
||||
}
|
||||
System.arraycopy(data, readPosition, target, offset, length);
|
||||
readPosition += length;
|
||||
peekPosition = readPosition;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void readFully(byte[] target, int offset, int length) throws IOException {
|
||||
readFully(target, offset, length, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int skip(int length) throws IOException {
|
||||
length = getReadLength(length);
|
||||
if (skipFully(length, true)) {
|
||||
return length;
|
||||
}
|
||||
return C.RESULT_END_OF_INPUT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean skipFully(int length, boolean allowEndOfInput) throws IOException {
|
||||
if (!checkXFully(allowEndOfInput, readPosition, length, failedReadPositions)) {
|
||||
return false;
|
||||
}
|
||||
readPosition += length;
|
||||
peekPosition = readPosition;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void skipFully(int length) throws IOException {
|
||||
skipFully(length, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)
|
||||
throws IOException {
|
||||
if (!checkXFully(allowEndOfInput, peekPosition, length, failedPeekPositions)) {
|
||||
return false;
|
||||
}
|
||||
System.arraycopy(data, peekPosition, target, offset, length);
|
||||
peekPosition += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void peekFully(byte[] target, int offset, int length) throws IOException {
|
||||
peekFully(target, offset, length, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean advancePeekPosition(int length, boolean allowEndOfInput) throws IOException {
|
||||
if (!checkXFully(allowEndOfInput, peekPosition, length, failedPeekPositions)) {
|
||||
return false;
|
||||
}
|
||||
peekPosition += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void advancePeekPosition(int length) throws IOException {
|
||||
advancePeekPosition(length, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetPeekPosition() {
|
||||
peekPosition = readPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPeekPosition() {
|
||||
return peekPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getPosition() {
|
||||
return readPosition;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getLength() {
|
||||
return simulateUnknownLength ? C.LENGTH_UNBOUNDED : data.length;
|
||||
}
|
||||
|
||||
private boolean checkXFully(boolean allowEndOfInput, int position, int length,
|
||||
SparseBooleanArray failedPositions) throws IOException {
|
||||
if (simulateIOErrors && !failedPositions.get(position)) {
|
||||
failedPositions.put(position, true);
|
||||
peekPosition = readPosition;
|
||||
throw new SimulatedIOException("Simulated IO error at position: " + position);
|
||||
}
|
||||
if (isEof()) {
|
||||
if (allowEndOfInput) {
|
||||
return false;
|
||||
}
|
||||
throw new EOFException();
|
||||
}
|
||||
if (position + length > data.length) {
|
||||
throw new IOException("Attempted to move past end of data: (" + position + " + "
|
||||
+ length + ") > " + data.length);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int getReadLength(int requestedLength) {
|
||||
int targetPosition = readPosition + requestedLength;
|
||||
if (simulatePartialReads && requestedLength > 1
|
||||
&& !partiallySatisfiedTargetPositions.get(targetPosition)) {
|
||||
partiallySatisfiedTargetPositions.put(targetPosition, true);
|
||||
return 1;
|
||||
}
|
||||
return Math.min(requestedLength, data.length - readPosition);
|
||||
}
|
||||
|
||||
private boolean isEof() {
|
||||
return readPosition == data.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder of {@link FakeExtractorInput} instances.
|
||||
*/
|
||||
public static final class Builder {
|
||||
|
||||
private byte[] data;
|
||||
private boolean simulateUnknownLength;
|
||||
private boolean simulatePartialReads;
|
||||
private boolean simulateIOErrors;
|
||||
|
||||
public Builder() {
|
||||
data = new byte[0];
|
||||
}
|
||||
|
||||
public Builder setData(byte[] data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSimulateUnknownLength(boolean simulateUnknownLength) {
|
||||
this.simulateUnknownLength = simulateUnknownLength;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSimulatePartialReads(boolean simulatePartialReads) {
|
||||
this.simulatePartialReads = simulatePartialReads;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSimulateIOErrors(boolean simulateIOErrors) {
|
||||
this.simulateIOErrors = simulateIOErrors;
|
||||
return this;
|
||||
}
|
||||
|
||||
public FakeExtractorInput build() {
|
||||
return new FakeExtractorInput(data, simulateUnknownLength, simulatePartialReads,
|
||||
simulateIOErrors);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -15,24 +15,18 @@
|
||||
*/
|
||||
package com.google.android.exoplayer.testutil;
|
||||
|
||||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.extractor.DefaultExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.Extractor;
|
||||
import com.google.android.exoplayer.extractor.ExtractorInput;
|
||||
import com.google.android.exoplayer.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer.upstream.DataSpec;
|
||||
import com.google.android.exoplayer.util.Assertions;
|
||||
import com.google.android.exoplayer.util.Util;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.net.Uri;
|
||||
import android.test.InstrumentationTestCase;
|
||||
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
@ -44,32 +38,19 @@ public class TestUtil {
|
||||
|
||||
public static void consumeTestData(Extractor extractor, byte[] data)
|
||||
throws IOException, InterruptedException {
|
||||
ExtractorInput input = createTestExtractorInput(data);
|
||||
FakeExtractorInput input = new FakeExtractorInput.Builder().setData(data).build();
|
||||
PositionHolder seekPositionHolder = new PositionHolder();
|
||||
int readResult = Extractor.RESULT_CONTINUE;
|
||||
while (readResult != Extractor.RESULT_END_OF_INPUT) {
|
||||
readResult = extractor.read(input, seekPositionHolder);
|
||||
if (readResult == Extractor.RESULT_SEEK) {
|
||||
input = createTestExtractorInput(data, (int) seekPositionHolder.position);
|
||||
long seekPosition = seekPositionHolder.position;
|
||||
Assertions.checkState(0 < seekPosition && seekPosition <= Integer.MAX_VALUE);
|
||||
input.setPosition((int) seekPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ExtractorInput createTestExtractorInput(byte[] data) throws IOException {
|
||||
return createTestExtractorInput(data, 0);
|
||||
}
|
||||
|
||||
public static ExtractorInput createTestExtractorInput(byte[] data, int offset)
|
||||
throws IOException {
|
||||
if (offset != 0) {
|
||||
data = Arrays.copyOfRange(data, offset, data.length);
|
||||
}
|
||||
FakeDataSource dataSource = new FakeDataSource.Builder().appendReadData(data).build();
|
||||
dataSource.open(new DataSpec(Uri.parse("http://www.google.com")));
|
||||
ExtractorInput input = new DefaultExtractorInput(dataSource, offset, C.LENGTH_UNBOUNDED);
|
||||
return input;
|
||||
}
|
||||
|
||||
public static byte[] buildTestData(int length) {
|
||||
return buildTestData(length, length);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user