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