• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.google.android.exoplayer2.testutil;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import android.util.SparseBooleanArray;
21 import com.google.android.exoplayer2.C;
22 import com.google.android.exoplayer2.extractor.ExtractorInput;
23 import com.google.android.exoplayer2.util.Util;
24 import java.io.EOFException;
25 import java.io.IOException;
26 
27 /**
28  * A fake {@link ExtractorInput} capable of simulating various scenarios.
29  * <p>
30  * Read, skip and peek errors can be simulated using {@link Builder#setSimulateIOErrors}. When
31  * enabled each read and skip will throw a {@link SimulatedIOException} unless one has already been
32  * thrown from the current position. Each peek will throw {@link SimulatedIOException} unless one
33  * has already been thrown from the current peek position. When a {@link SimulatedIOException} is
34  * thrown the read position is left unchanged and the peek position is reset back to the read
35  * position.
36  * <p>
37  * Partial reads and skips can be simulated using {@link Builder#setSimulatePartialReads}. When
38  * enabled, {@link #read(byte[], int, int)} and {@link #skip(int)} calls will only read or skip a
39  * single byte unless a partial read or skip has already been performed that had the same target
40  * position. For example, a first read request for 10 bytes will be partially satisfied by reading
41  * a single byte and advancing the position to 1. If the following read request attempts to read 9
42  * bytes then it will be fully satisfied, since it has the same target position of 10.
43  * <p>
44  * Unknown data length can be simulated using {@link Builder#setSimulateUnknownLength}. When enabled
45  * {@link #getLength()} will return {@link C#LENGTH_UNSET} rather than the length of the data.
46  */
47 public final class FakeExtractorInput implements ExtractorInput {
48 
49   /**
50    * Thrown when simulating an {@link IOException}.
51    */
52   public static final class SimulatedIOException extends IOException {
53 
SimulatedIOException(String message)54     public SimulatedIOException(String message) {
55       super(message);
56     }
57 
58   }
59 
60   private final byte[] data;
61   private final boolean simulateUnknownLength;
62   private final boolean simulatePartialReads;
63   private final boolean simulateIOErrors;
64 
65   private int readPosition;
66   private int peekPosition;
67 
68   private final SparseBooleanArray partiallySatisfiedTargetReadPositions;
69   private final SparseBooleanArray partiallySatisfiedTargetPeekPositions;
70   private final SparseBooleanArray failedReadPositions;
71   private final SparseBooleanArray failedPeekPositions;
72 
FakeExtractorInput(byte[] data, boolean simulateUnknownLength, boolean simulatePartialReads, boolean simulateIOErrors)73   private FakeExtractorInput(byte[] data, boolean simulateUnknownLength,
74       boolean simulatePartialReads, boolean simulateIOErrors) {
75     this.data = data;
76     this.simulateUnknownLength = simulateUnknownLength;
77     this.simulatePartialReads = simulatePartialReads;
78     this.simulateIOErrors = simulateIOErrors;
79     partiallySatisfiedTargetReadPositions = new SparseBooleanArray();
80     partiallySatisfiedTargetPeekPositions = new SparseBooleanArray();
81     failedReadPositions = new SparseBooleanArray();
82     failedPeekPositions = new SparseBooleanArray();
83   }
84 
85   /** Resets the input to its initial state. */
reset()86   public void reset() {
87     readPosition = 0;
88     peekPosition = 0;
89     partiallySatisfiedTargetReadPositions.clear();
90     partiallySatisfiedTargetPeekPositions.clear();
91     failedReadPositions.clear();
92     failedPeekPositions.clear();
93   }
94 
95   /**
96    * Sets the read and peek positions.
97    *
98    * @param position The position to set.
99    */
setPosition(int position)100   public void setPosition(int position) {
101     assertThat(0 <= position).isTrue();
102     assertThat(position <= data.length).isTrue();
103     readPosition = position;
104     peekPosition = position;
105   }
106 
107   @Override
read(byte[] target, int offset, int length)108   public int read(byte[] target, int offset, int length) throws IOException {
109     checkIOException(readPosition, failedReadPositions);
110     length = getLengthToRead(readPosition, length, partiallySatisfiedTargetReadPositions);
111     return readFullyInternal(target, offset, length, true) ? length : C.RESULT_END_OF_INPUT;
112   }
113 
114   @Override
readFully(byte[] target, int offset, int length, boolean allowEndOfInput)115   public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
116       throws IOException {
117     checkIOException(readPosition, failedReadPositions);
118     return readFullyInternal(target, offset, length, allowEndOfInput);
119   }
120 
121   @Override
readFully(byte[] target, int offset, int length)122   public void readFully(byte[] target, int offset, int length) throws IOException {
123     readFully(target, offset, length, false);
124   }
125 
126   @Override
skip(int length)127   public int skip(int length) throws IOException {
128     checkIOException(readPosition, failedReadPositions);
129     length = getLengthToRead(readPosition, length, partiallySatisfiedTargetReadPositions);
130     return skipFullyInternal(length, true) ? length : C.RESULT_END_OF_INPUT;
131   }
132 
133   @Override
skipFully(int length, boolean allowEndOfInput)134   public boolean skipFully(int length, boolean allowEndOfInput) throws IOException {
135     checkIOException(readPosition, failedReadPositions);
136     return skipFullyInternal(length, allowEndOfInput);
137   }
138 
139   @Override
skipFully(int length)140   public void skipFully(int length) throws IOException {
141     skipFully(length, false);
142   }
143 
144   @Override
peek(byte[] target, int offset, int length)145   public int peek(byte[] target, int offset, int length) throws IOException {
146     checkIOException(peekPosition, failedPeekPositions);
147     length = getLengthToRead(peekPosition, length, partiallySatisfiedTargetPeekPositions);
148     return peekFullyInternal(target, offset, length, true) ? length : C.RESULT_END_OF_INPUT;
149   }
150 
151   @Override
peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)152   public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)
153       throws IOException {
154     checkIOException(peekPosition, failedPeekPositions);
155     return peekFullyInternal(target, offset, length, allowEndOfInput);
156   }
157 
158   @Override
peekFully(byte[] target, int offset, int length)159   public void peekFully(byte[] target, int offset, int length) throws IOException {
160     peekFully(target, offset, length, false);
161   }
162 
163   @Override
advancePeekPosition(int length, boolean allowEndOfInput)164   public boolean advancePeekPosition(int length, boolean allowEndOfInput) throws IOException {
165     checkIOException(peekPosition, failedPeekPositions);
166     if (!checkXFully(allowEndOfInput, peekPosition, length)) {
167       return false;
168     }
169     peekPosition += length;
170     return true;
171   }
172 
173   @Override
advancePeekPosition(int length)174   public void advancePeekPosition(int length) throws IOException {
175     advancePeekPosition(length, false);
176   }
177 
178   @Override
resetPeekPosition()179   public void resetPeekPosition() {
180     peekPosition = readPosition;
181   }
182 
183   @Override
getPeekPosition()184   public long getPeekPosition() {
185     return peekPosition;
186   }
187 
188   @Override
getPosition()189   public long getPosition() {
190     return readPosition;
191   }
192 
193   @Override
getLength()194   public long getLength() {
195     return simulateUnknownLength ? C.LENGTH_UNSET : data.length;
196   }
197 
198   @Override
setRetryPosition(long position, E e)199   public <E extends Throwable> void setRetryPosition(long position, E e) throws E {
200     assertThat(position >= 0).isTrue();
201     readPosition = (int) position;
202     throw e;
203   }
204 
checkIOException(int position, SparseBooleanArray failedPositions)205   private void checkIOException(int position, SparseBooleanArray failedPositions)
206       throws SimulatedIOException {
207     if (simulateIOErrors && !failedPositions.get(position)) {
208       failedPositions.put(position, true);
209       peekPosition = readPosition;
210       throw new SimulatedIOException("Simulated IO error at position: " + position);
211     }
212   }
213 
checkXFully(boolean allowEndOfInput, int position, int length)214   private boolean checkXFully(boolean allowEndOfInput, int position, int length)
215       throws EOFException {
216     if (length > 0 && position == data.length) {
217       if (allowEndOfInput) {
218         return false;
219       }
220       throw new EOFException();
221     }
222     if (position + length > data.length) {
223       throw new EOFException("Attempted to move past end of data: (" + position + " + "
224           + length + ") > " + data.length);
225     }
226     return true;
227   }
228 
getLengthToRead( int position, int requestedLength, SparseBooleanArray partiallySatisfiedTargetPositions)229   private int getLengthToRead(
230       int position, int requestedLength, SparseBooleanArray partiallySatisfiedTargetPositions) {
231     if (position == data.length) {
232       // If the requested length is non-zero, the end of the input will be read.
233       return requestedLength == 0 ? 0 : Integer.MAX_VALUE;
234     }
235     int targetPosition = position + requestedLength;
236     if (simulatePartialReads && requestedLength > 1
237         && !partiallySatisfiedTargetPositions.get(targetPosition)) {
238       partiallySatisfiedTargetPositions.put(targetPosition, true);
239       return 1;
240     }
241     return Math.min(requestedLength, data.length - position);
242   }
243 
readFullyInternal(byte[] target, int offset, int length, boolean allowEndOfInput)244   private boolean readFullyInternal(byte[] target, int offset, int length, boolean allowEndOfInput)
245       throws EOFException {
246     if (!checkXFully(allowEndOfInput, readPosition, length)) {
247       return false;
248     }
249     System.arraycopy(data, readPosition, target, offset, length);
250     readPosition += length;
251     peekPosition = readPosition;
252     return true;
253   }
254 
skipFullyInternal(int length, boolean allowEndOfInput)255   private boolean skipFullyInternal(int length, boolean allowEndOfInput) throws EOFException {
256     if (!checkXFully(allowEndOfInput, readPosition, length)) {
257       return false;
258     }
259     readPosition += length;
260     peekPosition = readPosition;
261     return true;
262   }
263 
peekFullyInternal(byte[] target, int offset, int length, boolean allowEndOfInput)264   private boolean peekFullyInternal(byte[] target, int offset, int length, boolean allowEndOfInput)
265       throws EOFException {
266     if (!checkXFully(allowEndOfInput, peekPosition, length)) {
267       return false;
268     }
269     System.arraycopy(data, peekPosition, target, offset, length);
270     peekPosition += length;
271     return true;
272   }
273 
274   /**
275    * Builder of {@link FakeExtractorInput} instances.
276    */
277   public static final class Builder {
278 
279     private byte[] data;
280     private boolean simulateUnknownLength;
281     private boolean simulatePartialReads;
282     private boolean simulateIOErrors;
283 
Builder()284     public Builder() {
285       data = Util.EMPTY_BYTE_ARRAY;
286     }
287 
setData(byte[] data)288     public Builder setData(byte[] data) {
289       this.data = data;
290       return this;
291     }
292 
setSimulateUnknownLength(boolean simulateUnknownLength)293     public Builder setSimulateUnknownLength(boolean simulateUnknownLength) {
294       this.simulateUnknownLength = simulateUnknownLength;
295       return this;
296     }
297 
setSimulatePartialReads(boolean simulatePartialReads)298     public Builder setSimulatePartialReads(boolean simulatePartialReads) {
299       this.simulatePartialReads = simulatePartialReads;
300       return this;
301     }
302 
setSimulateIOErrors(boolean simulateIOErrors)303     public Builder setSimulateIOErrors(boolean simulateIOErrors) {
304       this.simulateIOErrors = simulateIOErrors;
305       return this;
306     }
307 
build()308     public FakeExtractorInput build() {
309       return new FakeExtractorInput(data, simulateUnknownLength, simulatePartialReads,
310           simulateIOErrors);
311     }
312 
313   }
314 
315 }
316