/* * Copyright (C) 2016 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.exoplayer2.testutil; import android.app.Instrumentation; import android.test.InstrumentationTestCase; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.SeekMap; import com.google.android.exoplayer2.testutil.FakeExtractorInput.SimulatedIOException; import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Util; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Random; import junit.framework.Assert; import org.mockito.MockitoAnnotations; /** * Utility methods for tests. */ public class TestUtil { /** * A factory for {@link Extractor} instances. */ public interface ExtractorFactory { Extractor create(); } private static final String DUMP_EXTENSION = ".dump"; private static final String UNKNOWN_LENGTH_EXTENSION = ".unklen" + DUMP_EXTENSION; private TestUtil() {} public static boolean sniffTestData(Extractor extractor, byte[] data) throws IOException, InterruptedException { return sniffTestData(extractor, newExtractorInput(data)); } public static boolean sniffTestData(Extractor extractor, FakeExtractorInput input) throws IOException, InterruptedException { while (true) { try { return extractor.sniff(input); } catch (SimulatedIOException e) { // Ignore. } } } public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input, long timeUs) throws IOException, InterruptedException { return consumeTestData(extractor, input, timeUs, false); } public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input, long timeUs, boolean retryFromStartIfLive) throws IOException, InterruptedException { FakeExtractorOutput output = new FakeExtractorOutput(); extractor.init(output); consumeTestData(extractor, input, timeUs, output, retryFromStartIfLive); return output; } private static void consumeTestData(Extractor extractor, FakeExtractorInput input, long timeUs, FakeExtractorOutput output, boolean retryFromStartIfLive) throws IOException, InterruptedException { extractor.seek(input.getPosition(), timeUs); PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; while (readResult != Extractor.RESULT_END_OF_INPUT) { try { // Extractor.read should not read seekPositionHolder.position. Set it to a value that's // likely to cause test failure if a read does occur. seekPositionHolder.position = Long.MIN_VALUE; readResult = extractor.read(input, seekPositionHolder); if (readResult == Extractor.RESULT_SEEK) { long seekPosition = seekPositionHolder.position; Assertions.checkState(0 <= seekPosition && seekPosition <= Integer.MAX_VALUE); input.setPosition((int) seekPosition); } } catch (SimulatedIOException e) { if (!retryFromStartIfLive) { continue; } boolean isOnDemand = input.getLength() != C.LENGTH_UNSET || (output.seekMap != null && output.seekMap.getDurationUs() != C.TIME_UNSET); if (isOnDemand) { continue; } input.setPosition(0); for (int i = 0; i < output.numberOfTracks; i++) { output.trackOutputs.valueAt(i).clear(); } extractor.seek(0, 0); } } } public static byte[] buildTestData(int length) { return buildTestData(length, length); } public static byte[] buildTestData(int length, int seed) { return buildTestData(length, new Random(seed)); } public static byte[] buildTestData(int length, Random random) { byte[] source = new byte[length]; random.nextBytes(source); return source; } public static String buildTestString(int maxLength, Random random) { int length = random.nextInt(maxLength); StringBuilder builder = new StringBuilder(length); for (int i = 0; i < length; i++) { builder.append((char) random.nextInt()); } return builder.toString(); } /** * Converts an array of integers in the range [0, 255] into an equivalent byte array. * * @param intArray An array of integers, all of which must be in the range [0, 255]. * @return The equivalent byte array. */ public static byte[] createByteArray(int... intArray) { byte[] byteArray = new byte[intArray.length]; for (int i = 0; i < byteArray.length; i++) { Assertions.checkState(0x00 <= intArray[i] && intArray[i] <= 0xFF); byteArray[i] = (byte) intArray[i]; } return byteArray; } public static byte[] joinByteArrays(byte[]... byteArrays) { int length = 0; for (byte[] byteArray : byteArrays) { length += byteArray.length; } byte[] joined = new byte[length]; length = 0; for (byte[] byteArray : byteArrays) { System.arraycopy(byteArray, 0, joined, length, byteArray.length); length += byteArray.length; } return joined; } public static void setUpMockito(InstrumentationTestCase instrumentationTestCase) { // Workaround for https://code.google.com/p/dexmaker/issues/detail?id=2. System.setProperty("dexmaker.dexcache", instrumentationTestCase.getInstrumentation().getTargetContext().getCacheDir().getPath()); MockitoAnnotations.initMocks(instrumentationTestCase); } public static boolean assetExists(Instrumentation instrumentation, String fileName) throws IOException { int i = fileName.lastIndexOf('/'); String path = i >= 0 ? fileName.substring(0, i) : ""; String file = i >= 0 ? fileName.substring(i + 1) : fileName; return Arrays.asList(instrumentation.getContext().getResources().getAssets().list(path)) .contains(file); } public static byte[] getByteArray(Instrumentation instrumentation, String fileName) throws IOException { return Util.toByteArray(getInputStream(instrumentation, fileName)); } public static InputStream getInputStream(Instrumentation instrumentation, String fileName) throws IOException { return instrumentation.getContext().getResources().getAssets().open(fileName); } public static String getString(Instrumentation instrumentation, String fileName) throws IOException { return new String(getByteArray(instrumentation, fileName)); } private static FakeExtractorInput newExtractorInput(byte[] data) { return new FakeExtractorInput.Builder().setData(data).build(); } /** * Calls {@link #assertOutput(Extractor, String, byte[], Instrumentation, boolean, boolean, * boolean)} with all possible combinations of "simulate" parameters. * * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor} * class which is to be tested. * @param sampleFile The path to the input sample. * @param instrumentation To be used to load the sample file. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. * @see #assertOutput(Extractor, String, byte[], Instrumentation, boolean, boolean, boolean) */ public static void assertOutput(ExtractorFactory factory, String sampleFile, Instrumentation instrumentation) throws IOException, InterruptedException { byte[] fileData = getByteArray(instrumentation, sampleFile); assertOutput(factory, sampleFile, fileData, instrumentation); } /** * Calls {@link #assertOutput(Extractor, String, byte[], Instrumentation, boolean, boolean, * boolean)} with all possible combinations of "simulate" parameters. * * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor} * class which is to be tested. * @param sampleFile The path to the input sample. * @param fileData Content of the input file. * @param instrumentation To be used to load the sample file. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. * @see #assertOutput(Extractor, String, byte[], Instrumentation, boolean, boolean, boolean) */ public static void assertOutput(ExtractorFactory factory, String sampleFile, byte[] fileData, Instrumentation instrumentation) throws IOException, InterruptedException { assertOutput(factory.create(), sampleFile, fileData, instrumentation, false, false, false); assertOutput(factory.create(), sampleFile, fileData, instrumentation, true, false, false); assertOutput(factory.create(), sampleFile, fileData, instrumentation, false, true, false); assertOutput(factory.create(), sampleFile, fileData, instrumentation, true, true, false); assertOutput(factory.create(), sampleFile, fileData, instrumentation, false, false, true); assertOutput(factory.create(), sampleFile, fileData, instrumentation, true, false, true); assertOutput(factory.create(), sampleFile, fileData, instrumentation, false, true, true); assertOutput(factory.create(), sampleFile, fileData, instrumentation, true, true, true); } /** * Asserts that {@code extractor} consumes {@code sampleFile} successfully and its output equals * to a prerecorded output dump file with the name {@code sampleFile} + "{@value * #DUMP_EXTENSION}". If {@code simulateUnknownLength} is true and {@code sampleFile} + "{@value * #UNKNOWN_LENGTH_EXTENSION}" exists, it's preferred. * * @param extractor The {@link Extractor} to be tested. * @param sampleFile The path to the input sample. * @param fileData Content of the input file. * @param instrumentation To be used to load the sample file. * @param simulateIOErrors If true simulates IOErrors. * @param simulateUnknownLength If true simulates unknown input length. * @param simulatePartialReads If true simulates partial reads. * @return The {@link FakeExtractorOutput} used in the test. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. */ public static FakeExtractorOutput assertOutput(Extractor extractor, String sampleFile, byte[] fileData, Instrumentation instrumentation, boolean simulateIOErrors, boolean simulateUnknownLength, boolean simulatePartialReads) throws IOException, InterruptedException { FakeExtractorInput input = new FakeExtractorInput.Builder().setData(fileData) .setSimulateIOErrors(simulateIOErrors) .setSimulateUnknownLength(simulateUnknownLength) .setSimulatePartialReads(simulatePartialReads).build(); Assert.assertTrue(sniffTestData(extractor, input)); input.resetPeekPosition(); FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, 0, true); if (simulateUnknownLength && assetExists(instrumentation, sampleFile + UNKNOWN_LENGTH_EXTENSION)) { extractorOutput.assertOutput(instrumentation, sampleFile + UNKNOWN_LENGTH_EXTENSION); } else { extractorOutput.assertOutput(instrumentation, sampleFile + ".0" + DUMP_EXTENSION); } SeekMap seekMap = extractorOutput.seekMap; if (seekMap.isSeekable()) { long durationUs = seekMap.getDurationUs(); for (int j = 0; j < 4; j++) { long timeUs = (durationUs * j) / 3; long position = seekMap.getPosition(timeUs); input.setPosition((int) position); for (int i = 0; i < extractorOutput.numberOfTracks; i++) { extractorOutput.trackOutputs.valueAt(i).clear(); } consumeTestData(extractor, input, timeUs, extractorOutput, false); extractorOutput.assertOutput(instrumentation, sampleFile + '.' + j + DUMP_EXTENSION); } } return extractorOutput; } /** * Calls {@link #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean)} with all * possible combinations of "simulate" parameters. * * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor} * class which is to be tested. * @param sampleFile The path to the input sample. * @param instrumentation To be used to load the sample file. * @param expectedThrowable Expected {@link Throwable} class. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. * @see #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean) */ public static void assertThrows(ExtractorFactory factory, String sampleFile, Instrumentation instrumentation, Class<? extends Throwable> expectedThrowable) throws IOException, InterruptedException { byte[] fileData = getByteArray(instrumentation, sampleFile); assertThrows(factory, fileData, expectedThrowable); } /** * Calls {@link #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean)} with all * possible combinations of "simulate" parameters. * * @param factory An {@link ExtractorFactory} which creates instances of the {@link Extractor} * class which is to be tested. * @param fileData Content of the input file. * @param expectedThrowable Expected {@link Throwable} class. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. * @see #assertThrows(Extractor, byte[], Class, boolean, boolean, boolean) */ public static void assertThrows(ExtractorFactory factory, byte[] fileData, Class<? extends Throwable> expectedThrowable) throws IOException, InterruptedException { assertThrows(factory.create(), fileData, expectedThrowable, false, false, false); assertThrows(factory.create(), fileData, expectedThrowable, true, false, false); assertThrows(factory.create(), fileData, expectedThrowable, false, true, false); assertThrows(factory.create(), fileData, expectedThrowable, true, true, false); assertThrows(factory.create(), fileData, expectedThrowable, false, false, true); assertThrows(factory.create(), fileData, expectedThrowable, true, false, true); assertThrows(factory.create(), fileData, expectedThrowable, false, true, true); assertThrows(factory.create(), fileData, expectedThrowable, true, true, true); } /** * Asserts {@code extractor} throws {@code expectedThrowable} while consuming {@code sampleFile}. * * @param extractor The {@link Extractor} to be tested. * @param fileData Content of the input file. * @param expectedThrowable Expected {@link Throwable} class. * @param simulateIOErrors If true simulates IOErrors. * @param simulateUnknownLength If true simulates unknown input length. * @param simulatePartialReads If true simulates partial reads. * @throws IOException If reading from the input fails. * @throws InterruptedException If interrupted while reading from the input. */ public static void assertThrows(Extractor extractor, byte[] fileData, Class<? extends Throwable> expectedThrowable, boolean simulateIOErrors, boolean simulateUnknownLength, boolean simulatePartialReads) throws IOException, InterruptedException { FakeExtractorInput input = new FakeExtractorInput.Builder().setData(fileData) .setSimulateIOErrors(simulateIOErrors) .setSimulateUnknownLength(simulateUnknownLength) .setSimulatePartialReads(simulatePartialReads).build(); try { consumeTestData(extractor, input, 0, true); throw new AssertionError(expectedThrowable.getSimpleName() + " expected but not thrown"); } catch (Throwable throwable) { if (expectedThrowable.equals(throwable.getClass())) { return; // Pass! } throw throwable; } } }