• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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 android.net.Uri;
19 import androidx.annotation.Nullable;
20 import com.google.android.exoplayer2.C;
21 import com.google.android.exoplayer2.upstream.DataSpec;
22 import com.google.android.exoplayer2.util.Assertions;
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.util.ArrayList;
26 import java.util.HashMap;
27 import java.util.List;
28 
29 /**
30  * Collection of {@link FakeData} to be served by a {@link FakeDataSource}.
31  *
32  * <p>Multiple fake data can be defined by {@link FakeDataSet#setData(Uri, byte[])} and {@link
33  * FakeDataSet#newData(Uri)} methods. It's also possible to define a default data by {@link
34  * FakeDataSet#newDefaultData()}.
35  *
36  * <p>{@link FakeDataSet#newData(Uri)} and {@link FakeDataSet#newDefaultData()} return a {@link
37  * FakeData} instance which can be used to define specific results during
38  * {@link FakeDataSource#read(byte[], int, int)} calls.
39  *
40  * <p>The data that will be read from the source can be constructed by calling {@link
41  * FakeData#appendReadData(byte[])} Calls to {@link FakeDataSource#read(byte[], int, int)} will not
42  * span the boundaries between arrays passed to successive calls, and hence the boundaries control
43  * the positions at which read requests to the source may only be partially satisfied.
44  *
45  * <p>Errors can be inserted by calling {@link FakeData#appendReadError(IOException)}. An inserted
46  * error will be thrown from the first call to {@link FakeDataSource#read(byte[], int, int)} that
47  * attempts to read from the corresponding position, and from all subsequent calls to
48  * {@link FakeDataSource#read(byte[], int, int)} until the source is closed. If the source is closed
49  * and re-opened having encountered an error, that error will not be thrown again.
50  *
51  * <p>Actions are inserted by calling {@link FakeData#appendReadAction(Runnable)}. An actions is
52  * triggered when the reading reaches action's position. This can be used to make sure the code is
53  * in a certain state while testing.
54  *
55  * <p>Example usage:
56  *
57  * <pre>
58  *   // Create a FakeDataSource then add default data and two FakeData
59  *   // "test_file" throws an IOException when tried to be read until closed and reopened.
60  *   FakeDataSource fakeDataSource = new FakeDataSource();
61  *   fakeDataSource.getDataSet()
62  *       .newDefaultData()
63  *         .appendReadData(defaultData)
64  *         .endData()
65  *       .setData("http://1", data1)
66  *       .newData("test_file")
67  *         .appendReadError(new IOException())
68  *         .appendReadData(data2)
69  *         .endData();
70  * </pre>
71  */
72 public class FakeDataSet {
73 
74   /** Container of fake data to be served by a {@link FakeDataSource}. */
75   public static final class FakeData {
76 
77     /**
78      * A segment of {@link FakeData}. May consist of an action or exception instead of actual data.
79      */
80     public static final class Segment {
81 
82       @Nullable public final IOException exception;
83       @Nullable public final byte[] data;
84       public final int length;
85       public final long byteOffset;
86       @Nullable public final Runnable action;
87 
88       public boolean exceptionThrown;
89       public boolean exceptionCleared;
90       public int bytesRead;
91 
Segment(byte[] data, @Nullable Segment previousSegment)92       private Segment(byte[] data, @Nullable Segment previousSegment) {
93         this(data, data.length, null, null, previousSegment);
94       }
95 
Segment(int length, @Nullable Segment previousSegment)96       private Segment(int length, @Nullable Segment previousSegment) {
97         this(null, length, null, null, previousSegment);
98       }
99 
Segment(IOException exception, @Nullable Segment previousSegment)100       private Segment(IOException exception, @Nullable Segment previousSegment) {
101         this(null, 0, exception, null, previousSegment);
102       }
103 
Segment(Runnable action, @Nullable Segment previousSegment)104       private Segment(Runnable action, @Nullable Segment previousSegment) {
105         this(null, 0, null, action, previousSegment);
106       }
107 
Segment( @ullable byte[] data, int length, @Nullable IOException exception, @Nullable Runnable action, @Nullable Segment previousSegment)108       private Segment(
109           @Nullable byte[] data,
110           int length,
111           @Nullable IOException exception,
112           @Nullable Runnable action,
113           @Nullable Segment previousSegment) {
114         this.exception = exception;
115         this.action = action;
116         this.data = data;
117         this.length = length;
118         this.byteOffset = previousSegment == null ? 0
119             : previousSegment.byteOffset + previousSegment.length;
120       }
121 
isErrorSegment()122       public boolean isErrorSegment() {
123         return exception != null;
124       }
125 
isActionSegment()126       public boolean isActionSegment() {
127         return action != null;
128       }
129 
130     }
131 
132     private final FakeDataSet dataSet;
133     /** Uri of the data or null if this is the default FakeData. */
134     @Nullable public final Uri uri;
135 
136     private final ArrayList<Segment> segments;
137     private boolean simulateUnknownLength;
138 
FakeData(FakeDataSet dataSet, @Nullable Uri uri)139     private FakeData(FakeDataSet dataSet, @Nullable Uri uri) {
140       this.dataSet = dataSet;
141       this.uri = uri;
142       this.segments = new ArrayList<>();
143     }
144 
145     /** Returns the {@link FakeDataSet} this FakeData belongs to. */
endData()146     public FakeDataSet endData() {
147       return dataSet;
148     }
149 
150     /**
151      * When set, {@link FakeDataSource#open(DataSpec)} will behave as though the source is unable to
152      * determine the length of the underlying data. Hence the return value will always be equal to
153      * the {@link DataSpec#length} of the argument, including the case where the length is equal to
154      * {@link C#LENGTH_UNSET}.
155      */
setSimulateUnknownLength(boolean simulateUnknownLength)156     public FakeData setSimulateUnknownLength(boolean simulateUnknownLength) {
157       this.simulateUnknownLength = simulateUnknownLength;
158       return this;
159     }
160 
161     /**
162      * Appends to the underlying data.
163      */
appendReadData(byte[] data)164     public FakeData appendReadData(byte[] data) {
165       Assertions.checkState(data.length > 0);
166       segments.add(new Segment(data, getLastSegment()));
167       return this;
168     }
169 
170     /**
171      * Appends a data segment of the specified length. No actual data is available and the
172      * {@link FakeDataSource} will perform no copy operations when this data is read.
173      */
appendReadData(int length)174     public FakeData appendReadData(int length) {
175       Assertions.checkState(length > 0);
176       segments.add(new Segment(length, getLastSegment()));
177       return this;
178     }
179 
180     /**
181      * Appends an error in the underlying data.
182      */
appendReadError(IOException exception)183     public FakeData appendReadError(IOException exception) {
184       segments.add(new Segment(exception, getLastSegment()));
185       return this;
186     }
187 
188     /**
189      * Appends an action.
190      */
appendReadAction(Runnable action)191     public FakeData appendReadAction(Runnable action) {
192       segments.add(new Segment(action, getLastSegment()));
193       return this;
194     }
195 
196     /** Returns the whole data added by {@link #appendReadData(byte[])}. */
getData()197     public byte[] getData() {
198       ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
199       for (Segment segment : segments) {
200         if (segment.data != null) {
201           try {
202             outputStream.write(segment.data);
203           } catch (IOException e) {
204             throw new IllegalStateException(e);
205           }
206         }
207       }
208       return outputStream.toByteArray();
209     }
210 
211     /** Returns the list of {@link Segment}s. */
getSegments()212     public List<Segment> getSegments() {
213       return segments;
214     }
215 
216     /** Retuns whether unknown length is simulated */
isSimulatingUnknownLength()217     public boolean isSimulatingUnknownLength() {
218       return simulateUnknownLength;
219     }
220 
221     @Nullable
getLastSegment()222     private Segment getLastSegment() {
223       int count = segments.size();
224       return count > 0 ? segments.get(count - 1) : null;
225     }
226 
227   }
228 
229   private final HashMap<Uri, FakeData> dataMap;
230   @Nullable private FakeData defaultData;
231 
FakeDataSet()232   public FakeDataSet() {
233     dataMap = new HashMap<>();
234   }
235 
236   /** Sets the default data, overwrites if there is one already. */
newDefaultData()237   public FakeData newDefaultData() {
238     defaultData = new FakeData(this, null);
239     return defaultData;
240   }
241 
242   /** Sets random data with the given {@code length} for the given {@code uri}. */
setRandomData(String uri, int length)243   public FakeDataSet setRandomData(String uri, int length) {
244     return setRandomData(Uri.parse(uri), length);
245   }
246 
247   /** Sets random data with the given {@code length} for the given {@code uri}. */
setRandomData(Uri uri, int length)248   public FakeDataSet setRandomData(Uri uri, int length) {
249     return setData(uri, TestUtil.buildTestData(length));
250   }
251 
252   /** Sets the given {@code data} for the given {@code uri}. */
setData(String uri, byte[] data)253   public FakeDataSet setData(String uri, byte[] data) {
254     return setData(Uri.parse(uri), data);
255   }
256 
257   /** Sets the given {@code data} for the given {@code uri}. */
setData(Uri uri, byte[] data)258   public FakeDataSet setData(Uri uri, byte[] data) {
259     return newData(uri).appendReadData(data).endData();
260   }
261 
262   /** Returns a new {@link FakeData} with the given {@code uri}. */
newData(String uri)263   public FakeData newData(String uri) {
264     return newData(Uri.parse(uri));
265   }
266 
267   /** Returns a new {@link FakeData} with the given {@code uri}. */
newData(Uri uri)268   public FakeData newData(Uri uri) {
269     FakeData data = new FakeData(this, uri);
270     dataMap.put(uri, data);
271     return data;
272   }
273 
274   /** Returns the data for the given {@code uri}, or {@code defaultData} if no data is set. */
275   @Nullable
getData(String uri)276   public FakeData getData(String uri) {
277     return getData(Uri.parse(uri));
278   }
279 
280   /** Returns the data for the given {@code uri}, or {@code defaultData} if no data is set. */
281   @Nullable
getData(Uri uri)282   public FakeData getData(Uri uri) {
283     @Nullable FakeData data = dataMap.get(uri);
284     return data != null ? data : defaultData;
285   }
286 
287   /** Returns a list of all data including {@code defaultData}. */
getAllData()288   public ArrayList<FakeData> getAllData() {
289     ArrayList<FakeData> fakeDatas = new ArrayList<>(dataMap.values());
290     if (defaultData != null) {
291       fakeDatas.add(defaultData);
292     }
293     return fakeDatas;
294   }
295 
296 }
297