Move extension tests to Robolectric.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=187021822
This commit is contained in:
tonihei 2018-02-26 07:58:29 -08:00 committed by Oliver Woodman
parent d400efd0f7
commit b47fb2826b
81 changed files with 3851 additions and 2327 deletions

View File

@ -24,6 +24,7 @@ include modulePrefix + 'library-hls'
include modulePrefix + 'library-smoothstreaming'
include modulePrefix + 'library-ui'
include modulePrefix + 'testutils'
include modulePrefix + 'testutils-robolectric'
include modulePrefix + 'extension-ffmpeg'
include modulePrefix + 'extension-flac'
include modulePrefix + 'extension-gvr'
@ -44,6 +45,7 @@ project(modulePrefix + 'library-hls').projectDir = new File(rootDir, 'library/hl
project(modulePrefix + 'library-smoothstreaming').projectDir = new File(rootDir, 'library/smoothstreaming')
project(modulePrefix + 'library-ui').projectDir = new File(rootDir, 'library/ui')
project(modulePrefix + 'testutils').projectDir = new File(rootDir, 'testutils')
project(modulePrefix + 'testutils-robolectric').projectDir = new File(rootDir, 'testutils_robolectric')
project(modulePrefix + 'extension-ffmpeg').projectDir = new File(rootDir, 'extensions/ffmpeg')
project(modulePrefix + 'extension-flac').projectDir = new File(rootDir, 'extensions/flac')
project(modulePrefix + 'extension-gvr').projectDir = new File(rootDir, 'extensions/gvr')

View File

@ -38,10 +38,7 @@ dependencies {
compile 'com.google.android.gms:play-services-cast-framework:' + playServicesLibraryVersion
compile project(modulePrefix + 'library-core')
compile project(modulePrefix + 'library-ui')
testCompile project(modulePrefix + 'testutils')
testCompile 'junit:junit:' + junitVersion
testCompile 'org.mockito:mockito-core:' + mockitoVersion
testCompile 'org.robolectric:robolectric:' + robolectricVersion
testCompile project(modulePrefix + 'testutils-robolectric')
}
ext {

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.ext.cast.test">
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
</manifest>

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.ext.cast;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.TimelineAsserts;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.gms.cast.MediaInfo;
import com.google.android.gms.cast.MediaQueueItem;
import com.google.android.gms.cast.MediaStatus;
@ -25,11 +26,9 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
/** Tests for {@link CastTimelineTracker}. */
@RunWith(RobolectricTestRunner.class)
@Config(sdk = Config.TARGET_SDK, manifest = Config.NONE)
public class CastTimelineTrackerTest {
private static final long DURATION_1_MS = 1000;
@ -49,12 +48,12 @@ public class CastTimelineTrackerTest {
new long[] {DURATION_1_MS, MediaInfo.UNKNOWN_DURATION, MediaInfo.UNKNOWN_DURATION});
CastTimelineTracker tracker = new CastTimelineTracker();
mediaInfo = mockMediaInfo("contentId1", DURATION_1_MS);
mediaInfo = getMediaInfo("contentId1", DURATION_1_MS);
Mockito.when(status.getMediaInfo()).thenReturn(mediaInfo);
TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(status), C.msToUs(DURATION_1_MS), C.TIME_UNSET, C.TIME_UNSET);
mediaInfo = mockMediaInfo("contentId3", DURATION_3_MS);
mediaInfo = getMediaInfo("contentId3", DURATION_3_MS);
Mockito.when(status.getMediaInfo()).thenReturn(mediaInfo);
TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(status),
@ -62,7 +61,7 @@ public class CastTimelineTrackerTest {
C.TIME_UNSET,
C.msToUs(DURATION_3_MS));
mediaInfo = mockMediaInfo("contentId2", DURATION_2_MS);
mediaInfo = getMediaInfo("contentId2", DURATION_2_MS);
Mockito.when(status.getMediaInfo()).thenReturn(mediaInfo);
TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(status),
@ -80,7 +79,7 @@ public class CastTimelineTrackerTest {
DURATION_5_MS,
MediaInfo.UNKNOWN_DURATION
});
mediaInfo = mockMediaInfo("contentId5", DURATION_5_MS);
mediaInfo = getMediaInfo("contentId5", DURATION_5_MS);
Mockito.when(newStatus.getMediaInfo()).thenReturn(mediaInfo);
TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(newStatus),
@ -89,7 +88,7 @@ public class CastTimelineTrackerTest {
C.msToUs(DURATION_5_MS),
C.msToUs(DURATION_3_MS));
mediaInfo = mockMediaInfo("contentId3", DURATION_3_MS);
mediaInfo = getMediaInfo("contentId3", DURATION_3_MS);
Mockito.when(newStatus.getMediaInfo()).thenReturn(mediaInfo);
TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(newStatus),
@ -98,7 +97,7 @@ public class CastTimelineTrackerTest {
C.msToUs(DURATION_5_MS),
C.msToUs(DURATION_3_MS));
mediaInfo = mockMediaInfo("contentId4", DURATION_4_MS);
mediaInfo = getMediaInfo("contentId4", DURATION_4_MS);
Mockito.when(newStatus.getMediaInfo()).thenReturn(mediaInfo);
TimelineAsserts.assertPeriodDurations(
tracker.getCastTimeline(newStatus),
@ -112,7 +111,7 @@ public class CastTimelineTrackerTest {
int[] itemIds, String[] contentIds, long[] durationsMs) {
ArrayList<MediaQueueItem> items = new ArrayList<>();
for (int i = 0; i < contentIds.length; i++) {
MediaInfo mediaInfo = mockMediaInfo(contentIds[i], durationsMs[i]);
MediaInfo mediaInfo = getMediaInfo(contentIds[i], durationsMs[i]);
MediaQueueItem item = Mockito.mock(MediaQueueItem.class);
Mockito.when(item.getMedia()).thenReturn(mediaInfo);
Mockito.when(item.getItemId()).thenReturn(itemIds[i]);
@ -123,10 +122,11 @@ public class CastTimelineTrackerTest {
return status;
}
private static MediaInfo mockMediaInfo(String contentId, long durationMs) {
MediaInfo mediaInfo = Mockito.mock(MediaInfo.class);
Mockito.when(mediaInfo.getContentId()).thenReturn(contentId);
Mockito.when(mediaInfo.getStreamDuration()).thenReturn(durationMs);
return mediaInfo;
private static MediaInfo getMediaInfo(String contentId, long durationMs) {
return new MediaInfo.Builder(contentId)
.setStreamDuration(durationMs)
.setContentType(MimeTypes.APPLICATION_MP4)
.setStreamType(MediaInfo.STREAM_TYPE_NONE)
.build();
}
}

View File

@ -0,0 +1 @@
manifest=src/test/AndroidManifest.xml

View File

@ -39,12 +39,8 @@ dependencies {
compile files('libs/cronet_api.jar')
compile files('libs/cronet_impl_common_java.jar')
compile files('libs/cronet_impl_native_java.jar')
androidTestCompile project(modulePrefix + 'library')
androidTestCompile project(modulePrefix + 'testutils')
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion
androidTestCompile 'com.android.support.test:runner:' + testSupportLibraryVersion
testCompile project(modulePrefix + 'library')
testCompile project(modulePrefix + 'testutils-robolectric')
}
ext {

View File

@ -18,16 +18,6 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.ext.cronet">
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="27"/>
<application android:debuggable="true"
android:allowBackup="false"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
<uses-library android:name="android.test.runner" />
</application>
<instrumentation
android:name="android.test.InstrumentationTestRunner"
android:targetPackage="com.google.android.exoplayer2.ext.cronet"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
</manifest>

View File

@ -19,9 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.MockitoUtil;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
@ -30,11 +27,11 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
/**
* Tests for {@link ByteArrayUploadDataProvider}.
*/
@RunWith(AndroidJUnit4.class)
/** Tests for {@link ByteArrayUploadDataProvider}. */
@RunWith(RobolectricTestRunner.class)
public final class ByteArrayUploadDataProviderTest {
private static final byte[] TEST_DATA = new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
@ -45,7 +42,7 @@ public final class ByteArrayUploadDataProviderTest {
@Before
public void setUp() {
MockitoUtil.setUpMockito(InstrumentationRegistry.getTargetContext(), this);
MockitoAnnotations.initMocks(this);
byteBuffer = ByteBuffer.allocate(TEST_DATA.length);
byteArrayUploadDataProvider = new ByteArrayUploadDataProvider(TEST_DATA);
}
@ -90,5 +87,4 @@ public final class ByteArrayUploadDataProviderTest {
assertThat(byteBuffer.array()).isEqualTo(TEST_DATA);
verify(mockUploadDataSink).onRewindSucceeded();
}
}

View File

@ -31,10 +31,8 @@ import static org.mockito.Mockito.when;
import android.net.Uri;
import android.os.ConditionVariable;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.os.SystemClock;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.MockitoUtil;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException;
@ -50,6 +48,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import org.chromium.net.CronetEngine;
@ -61,13 +60,14 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.shadows.ShadowSystemClock;
/**
* Tests for {@link CronetDataSource}.
*/
@RunWith(AndroidJUnit4.class)
/** Tests for {@link CronetDataSource}. */
@RunWith(RobolectricTestRunner.class)
public final class CronetDataSourceTest {
private static final int TEST_CONNECT_TIMEOUT_MS = 100;
@ -85,18 +85,11 @@ public final class CronetDataSourceTest {
private UrlResponseInfo testUrlResponseInfo;
@Mock private UrlRequest.Builder mockUrlRequestBuilder;
@Mock
private UrlRequest mockUrlRequest;
@Mock
private Predicate<String> mockContentTypePredicate;
@Mock
private TransferListener<CronetDataSource> mockTransferListener;
@Mock
private Clock mockClock;
@Mock
private Executor mockExecutor;
@Mock
private NetworkException mockNetworkException;
@Mock private UrlRequest mockUrlRequest;
@Mock private Predicate<String> mockContentTypePredicate;
@Mock private TransferListener<CronetDataSource> mockTransferListener;
@Mock private Executor mockExecutor;
@Mock private NetworkException mockNetworkException;
@Mock private CronetEngine mockCronetEngine;
private CronetDataSource dataSourceUnderTest;
@ -104,30 +97,31 @@ public final class CronetDataSourceTest {
@Before
public void setUp() throws Exception {
MockitoUtil.setUpMockito(InstrumentationRegistry.getTargetContext(), this);
dataSourceUnderTest = spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
mockClock,
null,
false));
MockitoAnnotations.initMocks(this);
dataSourceUnderTest =
spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
false));
when(mockContentTypePredicate.evaluate(anyString())).thenReturn(true);
when(mockCronetEngine.newUrlRequestBuilder(
anyString(), any(UrlRequest.Callback.class), any(Executor.class)))
anyString(), any(UrlRequest.Callback.class), any(Executor.class)))
.thenReturn(mockUrlRequestBuilder);
when(mockUrlRequestBuilder.allowDirectExecutor()).thenReturn(mockUrlRequestBuilder);
when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest);
mockStatusResponse();
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, C.LENGTH_UNSET, null);
testPostDataSpec = new DataSpec(
Uri.parse(TEST_URL), TEST_POST_BODY, 0, 0, C.LENGTH_UNSET, null, 0);
testPostDataSpec =
new DataSpec(Uri.parse(TEST_URL), TEST_POST_BODY, 0, 0, C.LENGTH_UNSET, null, 0);
testResponseHeader = new HashMap<>();
testResponseHeader.put("Content-Type", TEST_CONTENT_TYPE);
// This value can be anything since the DataSpec is unset.
@ -173,20 +167,19 @@ public final class CronetDataSourceTest {
// Prepare a mock UrlRequest to be used in the second open() call.
final UrlRequest mockUrlRequest2 = mock(UrlRequest.class);
when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest2);
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
// Invoke the callback for the previous request.
dataSourceUnderTest.onFailed(
mockUrlRequest,
testUrlResponseInfo,
mockNetworkException);
dataSourceUnderTest.onResponseStarted(
mockUrlRequest2,
testUrlResponseInfo);
return null;
}
}).when(mockUrlRequest2).start();
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
// Invoke the callback for the previous request.
dataSourceUnderTest.onFailed(
mockUrlRequest, testUrlResponseInfo, mockNetworkException);
dataSourceUnderTest.onResponseStarted(mockUrlRequest2, testUrlResponseInfo);
return null;
}
})
.when(mockUrlRequest2)
.start();
dataSourceUnderTest.open(testDataSpec);
}
@ -253,8 +246,8 @@ public final class CronetDataSourceTest {
@Test
public void testRequestOpenFailDueToDnsFailure() {
mockResponseStartFailure();
when(mockNetworkException.getErrorCode()).thenReturn(
NetworkException.ERROR_HOSTNAME_NOT_RESOLVED);
when(mockNetworkException.getErrorCode())
.thenReturn(NetworkException.ERROR_HOSTNAME_NOT_RESOLVED);
try {
dataSourceUnderTest.open(testDataSpec);
@ -524,8 +517,8 @@ public final class CronetDataSourceTest {
assertThat(bytesOverRead).isEqualTo(C.RESULT_END_OF_INPUT);
assertThat(returnedBuffer).isEqualTo(new byte[16]);
// C.RESULT_END_OF_INPUT should not be reported though the TransferListener.
verify(mockTransferListener, never()).onBytesTransferred(dataSourceUnderTest,
C.RESULT_END_OF_INPUT);
verify(mockTransferListener, never())
.onBytesTransferred(dataSourceUnderTest, C.RESULT_END_OF_INPUT);
// There should still be only one call to read on cronet.
verify(mockUrlRequest, times(1)).read(any(ByteBuffer.class));
// Check for connection not automatically closed.
@ -534,10 +527,10 @@ public final class CronetDataSourceTest {
}
@Test
public void testConnectTimeout() {
when(mockClock.elapsedRealtime()).thenReturn(0L);
public void testConnectTimeout() throws InterruptedException {
long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final ConditionVariable timedOutCondition = new ConditionVariable();
final CountDownLatch timedOutLatch = new CountDownLatch(1);
new Thread() {
@Override
@ -551,29 +544,29 @@ public final class CronetDataSourceTest {
assertThat(e.getCause() instanceof SocketTimeoutException).isTrue();
assertThat(((CronetDataSource.OpenException) e).cronetConnectionStatus)
.isEqualTo(TEST_CONNECTION_STATUS);
timedOutCondition.open();
timedOutLatch.countDown();
}
}
}.start();
startCondition.block();
// We should still be trying to open.
assertThat(timedOutCondition.block(50)).isFalse();
assertNotCountedDown(timedOutLatch);
// We should still be trying to open as we approach the timeout.
when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1);
assertThat(timedOutCondition.block(50)).isFalse();
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(timedOutLatch);
// Now we timeout.
when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS);
timedOutCondition.block();
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS + 10);
timedOutLatch.await();
verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec);
}
@Test
public void testConnectInterrupted() {
when(mockClock.elapsedRealtime()).thenReturn(0L);
public void testConnectInterrupted() throws InterruptedException {
long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final ConditionVariable timedOutCondition = new ConditionVariable();
final CountDownLatch timedOutLatch = new CountDownLatch(1);
Thread thread =
new Thread() {
@ -588,7 +581,7 @@ public final class CronetDataSourceTest {
assertThat(e.getCause() instanceof CronetDataSource.InterruptedIOException).isTrue();
assertThat(((CronetDataSource.OpenException) e).cronetConnectionStatus)
.isEqualTo(TEST_INVALID_CONNECTION_STATUS);
timedOutCondition.open();
timedOutLatch.countDown();
}
}
};
@ -596,29 +589,29 @@ public final class CronetDataSourceTest {
startCondition.block();
// We should still be trying to open.
assertThat(timedOutCondition.block(50)).isFalse();
assertNotCountedDown(timedOutLatch);
// We should still be trying to open as we approach the timeout.
when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1);
assertThat(timedOutCondition.block(50)).isFalse();
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(timedOutLatch);
// Now we interrupt.
thread.interrupt();
timedOutCondition.block();
timedOutLatch.await();
verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec);
}
@Test
public void testConnectResponseBeforeTimeout() {
when(mockClock.elapsedRealtime()).thenReturn(0L);
public void testConnectResponseBeforeTimeout() throws InterruptedException {
long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final ConditionVariable openCondition = new ConditionVariable();
final CountDownLatch openLatch = new CountDownLatch(1);
new Thread() {
@Override
public void run() {
try {
dataSourceUnderTest.open(testDataSpec);
openCondition.open();
openLatch.countDown();
} catch (HttpDataSourceException e) {
fail();
}
@ -627,20 +620,20 @@ public final class CronetDataSourceTest {
startCondition.block();
// We should still be trying to open.
assertThat(openCondition.block(50)).isFalse();
assertNotCountedDown(openLatch);
// We should still be trying to open as we approach the timeout.
when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1);
assertThat(openCondition.block(50)).isFalse();
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(openLatch);
// The response arrives just in time.
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
openCondition.block();
openLatch.await();
}
@Test
public void testRedirectIncreasesConnectionTimeout() throws InterruptedException {
when(mockClock.elapsedRealtime()).thenReturn(0L);
long startTimeMs = SystemClock.elapsedRealtime();
final ConditionVariable startCondition = buildUrlRequestStartedCondition();
final ConditionVariable timedOutCondition = new ConditionVariable();
final CountDownLatch timedOutLatch = new CountDownLatch(1);
final AtomicInteger openExceptions = new AtomicInteger(0);
new Thread() {
@ -654,40 +647,36 @@ public final class CronetDataSourceTest {
assertThat(e instanceof CronetDataSource.OpenException).isTrue();
assertThat(e.getCause() instanceof SocketTimeoutException).isTrue();
openExceptions.getAndIncrement();
timedOutCondition.open();
timedOutLatch.countDown();
}
}
}.start();
startCondition.block();
// We should still be trying to open.
assertThat(timedOutCondition.block(50)).isFalse();
assertNotCountedDown(timedOutLatch);
// We should still be trying to open as we approach the timeout.
when(mockClock.elapsedRealtime()).thenReturn((long) TEST_CONNECT_TIMEOUT_MS - 1);
assertThat(timedOutCondition.block(50)).isFalse();
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1);
assertNotCountedDown(timedOutLatch);
// A redirect arrives just in time.
dataSourceUnderTest.onRedirectReceived(mockUrlRequest, testUrlResponseInfo,
"RandomRedirectedUrl1");
dataSourceUnderTest.onRedirectReceived(
mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1");
long newTimeoutMs = 2 * TEST_CONNECT_TIMEOUT_MS - 1;
when(mockClock.elapsedRealtime()).thenReturn(newTimeoutMs - 1);
// Give the thread some time to run.
assertThat(timedOutCondition.block(newTimeoutMs)).isFalse();
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1);
// We should still be trying to open as we approach the new timeout.
assertThat(timedOutCondition.block(50)).isFalse();
assertNotCountedDown(timedOutLatch);
// A redirect arrives just in time.
dataSourceUnderTest.onRedirectReceived(mockUrlRequest, testUrlResponseInfo,
"RandomRedirectedUrl2");
dataSourceUnderTest.onRedirectReceived(
mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2");
newTimeoutMs = 3 * TEST_CONNECT_TIMEOUT_MS - 2;
when(mockClock.elapsedRealtime()).thenReturn(newTimeoutMs - 1);
// Give the thread some time to run.
assertThat(timedOutCondition.block(newTimeoutMs)).isFalse();
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs - 1);
// We should still be trying to open as we approach the new timeout.
assertThat(timedOutCondition.block(50)).isFalse();
assertNotCountedDown(timedOutLatch);
// Now we timeout.
when(mockClock.elapsedRealtime()).thenReturn(newTimeoutMs);
timedOutCondition.block();
ShadowSystemClock.setCurrentTimeMillis(startTimeMs + newTimeoutMs + 10);
timedOutLatch.await();
verify(mockTransferListener, never()).onTransferStart(dataSourceUnderTest, testDataSpec);
assertThat(openExceptions.get()).isEqualTo(1);
@ -707,20 +696,22 @@ public final class CronetDataSourceTest {
}
@Test
public void testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeaders()
throws HttpDataSourceException {
dataSourceUnderTest = spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
mockClock,
null,
true));
public void
testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeaders()
throws HttpDataSourceException {
dataSourceUnderTest =
spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
true));
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
mockSingleRedirectSuccess();
@ -736,21 +727,23 @@ public final class CronetDataSourceTest {
}
@Test
public void testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeadersIncludingByteRangeHeader()
throws HttpDataSourceException {
public void
testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeadersIncludingByteRangeHeader()
throws HttpDataSourceException {
testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000, null);
dataSourceUnderTest = spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
mockClock,
null,
true));
dataSourceUnderTest =
spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
true));
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
mockSingleRedirectSuccess();
@ -778,18 +771,19 @@ public final class CronetDataSourceTest {
@Test
public void testRedirectNoSetCookieFollowsRedirect_dataSourceHandlesSetCookie()
throws HttpDataSourceException {
dataSourceUnderTest = spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
mockClock,
null,
true));
dataSourceUnderTest =
spy(
new CronetDataSource(
mockCronetEngine,
mockExecutor,
mockContentTypePredicate,
mockTransferListener,
TEST_CONNECT_TIMEOUT_MS,
TEST_READ_TIMEOUT_MS,
true, // resetTimeoutOnRedirects
Clock.DEFAULT,
null,
true));
mockSingleRedirectSuccess();
mockFollowRedirectSuccess();
@ -804,8 +798,9 @@ public final class CronetDataSourceTest {
// Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that
// the subsequent open() call succeeds.
doThrow(new NullPointerException()).when(mockTransferListener).onTransferEnd(
dataSourceUnderTest);
doThrow(new NullPointerException())
.when(mockTransferListener)
.onTransferEnd(dataSourceUnderTest);
dataSourceUnderTest.open(testDataSpec);
try {
dataSourceUnderTest.close();
@ -833,13 +828,12 @@ public final class CronetDataSourceTest {
}
@Test
public void testReadInterrupted() throws HttpDataSourceException {
when(mockClock.elapsedRealtime()).thenReturn(0L);
public void testReadInterrupted() throws HttpDataSourceException, InterruptedException {
mockResponseStartSuccess();
dataSourceUnderTest.open(testDataSpec);
final ConditionVariable startCondition = buildReadStartedCondition();
final ConditionVariable timedOutCondition = new ConditionVariable();
final CountDownLatch timedOutLatch = new CountDownLatch(1);
byte[] returnedBuffer = new byte[8];
Thread thread =
new Thread() {
@ -851,17 +845,17 @@ public final class CronetDataSourceTest {
} catch (HttpDataSourceException e) {
// Expected.
assertThat(e.getCause() instanceof CronetDataSource.InterruptedIOException).isTrue();
timedOutCondition.open();
timedOutLatch.countDown();
}
}
};
thread.start();
startCondition.block();
assertThat(timedOutCondition.block(50)).isFalse();
assertNotCountedDown(timedOutLatch);
// Now we interrupt.
thread.interrupt();
timedOutCondition.block();
timedOutLatch.await();
}
@Test
@ -876,122 +870,135 @@ public final class CronetDataSourceTest {
// Helper methods.
private void mockStatusResponse() {
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
UrlRequest.StatusListener statusListener =
(UrlRequest.StatusListener) invocation.getArguments()[0];
statusListener.onStatus(TEST_CONNECTION_STATUS);
return null;
}
}).when(mockUrlRequest).getStatus(any(UrlRequest.StatusListener.class));
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
UrlRequest.StatusListener statusListener =
(UrlRequest.StatusListener) invocation.getArguments()[0];
statusListener.onStatus(TEST_CONNECTION_STATUS);
return null;
}
})
.when(mockUrlRequest)
.getStatus(any(UrlRequest.StatusListener.class));
}
private void mockResponseStartSuccess() {
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onResponseStarted(
mockUrlRequest,
testUrlResponseInfo);
return null;
}
}).when(mockUrlRequest).start();
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
return null;
}
})
.when(mockUrlRequest)
.start();
}
private void mockResponseStartRedirect() {
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onRedirectReceived(
mockUrlRequest,
createUrlResponseInfo(307), // statusCode
"http://redirect.location.com");
return null;
}
}).when(mockUrlRequest).start();
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onRedirectReceived(
mockUrlRequest,
createUrlResponseInfo(307), // statusCode
"http://redirect.location.com");
return null;
}
})
.when(mockUrlRequest)
.start();
}
private void mockSingleRedirectSuccess() {
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if (!redirectCalled) {
redirectCalled = true;
dataSourceUnderTest.onRedirectReceived(
mockUrlRequest,
createUrlResponseInfoWithUrl("http://example.com/video", 300),
"http://example.com/video/redirect");
} else {
dataSourceUnderTest.onResponseStarted(
mockUrlRequest,
testUrlResponseInfo);
}
return null;
}
}).when(mockUrlRequest).start();
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
if (!redirectCalled) {
redirectCalled = true;
dataSourceUnderTest.onRedirectReceived(
mockUrlRequest,
createUrlResponseInfoWithUrl("http://example.com/video", 300),
"http://example.com/video/redirect");
} else {
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
}
return null;
}
})
.when(mockUrlRequest)
.start();
}
private void mockFollowRedirectSuccess() {
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onResponseStarted(
mockUrlRequest,
testUrlResponseInfo);
return null;
}
}).when(mockUrlRequest).followRedirect();
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onResponseStarted(mockUrlRequest, testUrlResponseInfo);
return null;
}
})
.when(mockUrlRequest)
.followRedirect();
}
private void mockResponseStartFailure() {
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onFailed(
mockUrlRequest,
createUrlResponseInfo(500), // statusCode
mockNetworkException);
return null;
}
}).when(mockUrlRequest).start();
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onFailed(
mockUrlRequest,
createUrlResponseInfo(500), // statusCode
mockNetworkException);
return null;
}
})
.when(mockUrlRequest)
.start();
}
private void mockReadSuccess(int position, int length) {
final int[] positionAndRemaining = new int[] {position, length};
doAnswer(new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
if (positionAndRemaining[1] == 0) {
dataSourceUnderTest.onSucceeded(mockUrlRequest, testUrlResponseInfo);
} else {
ByteBuffer inputBuffer = (ByteBuffer) invocation.getArguments()[0];
int readLength = Math.min(positionAndRemaining[1], inputBuffer.remaining());
inputBuffer.put(buildTestDataBuffer(positionAndRemaining[0], readLength));
positionAndRemaining[0] += readLength;
positionAndRemaining[1] -= readLength;
dataSourceUnderTest.onReadCompleted(
mockUrlRequest,
testUrlResponseInfo,
inputBuffer);
}
return null;
}
}).when(mockUrlRequest).read(any(ByteBuffer.class));
doAnswer(
new Answer<Void>() {
@Override
public Void answer(InvocationOnMock invocation) throws Throwable {
if (positionAndRemaining[1] == 0) {
dataSourceUnderTest.onSucceeded(mockUrlRequest, testUrlResponseInfo);
} else {
ByteBuffer inputBuffer = (ByteBuffer) invocation.getArguments()[0];
int readLength = Math.min(positionAndRemaining[1], inputBuffer.remaining());
inputBuffer.put(buildTestDataBuffer(positionAndRemaining[0], readLength));
positionAndRemaining[0] += readLength;
positionAndRemaining[1] -= readLength;
dataSourceUnderTest.onReadCompleted(
mockUrlRequest, testUrlResponseInfo, inputBuffer);
}
return null;
}
})
.when(mockUrlRequest)
.read(any(ByteBuffer.class));
}
private void mockReadFailure() {
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onFailed(
mockUrlRequest,
createUrlResponseInfo(500), // statusCode
mockNetworkException);
return null;
}
})
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
dataSourceUnderTest.onFailed(
mockUrlRequest,
createUrlResponseInfo(500), // statusCode
mockNetworkException);
return null;
}
})
.when(mockUrlRequest)
.read(any(ByteBuffer.class));
}
@ -999,13 +1006,13 @@ public final class CronetDataSourceTest {
private ConditionVariable buildReadStartedCondition() {
final ConditionVariable startedCondition = new ConditionVariable();
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
startedCondition.open();
return null;
}
})
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
startedCondition.open();
return null;
}
})
.when(mockUrlRequest)
.read(any(ByteBuffer.class));
return startedCondition;
@ -1013,16 +1020,26 @@ public final class CronetDataSourceTest {
private ConditionVariable buildUrlRequestStartedCondition() {
final ConditionVariable startedCondition = new ConditionVariable();
doAnswer(new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
startedCondition.open();
return null;
}
}).when(mockUrlRequest).start();
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) throws Throwable {
startedCondition.open();
return null;
}
})
.when(mockUrlRequest)
.start();
return startedCondition;
}
private void assertNotCountedDown(CountDownLatch countDownLatch) throws InterruptedException {
// We are asserting that another thread does not count down the latch. We therefore sleep some
// time to give the other thread the chance to fail this test.
Thread.sleep(50);
assertThat(countDownLatch.getCount()).isGreaterThan(0L);
}
private static byte[] buildTestDataArray(int position, int length) {
return buildTestDataBuffer(position, length).array();
}
@ -1045,5 +1062,4 @@ public final class CronetDataSourceTest {
testBuffer.flip();
return testBuffer;
}
}

View File

@ -0,0 +1 @@
manifest=src/test/AndroidManifest.xml

View File

@ -31,6 +31,7 @@ android {
}
test {
java.srcDirs += "../../testutils/src/main/java/"
java.srcDirs += "../../testutils_robolectric/src/main/java/"
}
}

View File

@ -89,7 +89,7 @@ public final class ContentDataSourceTest extends InstrumentationTestCase {
ContentDataSource dataSource = new ContentDataSource(instrumentation.getContext());
try {
DataSpec dataSpec = new DataSpec(contentUri, offset, length, null);
byte[] completeData = TestUtil.getByteArray(instrumentation, DATA_PATH);
byte[] completeData = TestUtil.getByteArray(instrumentation.getContext(), DATA_PATH);
byte[] expectedData = Arrays.copyOfRange(completeData, offset,
length == C.LENGTH_UNSET ? completeData.length : offset + length);
TestUtil.assertDataSourceContent(dataSource, dataSpec, expectedData, !pipeMode);

View File

@ -42,6 +42,7 @@ import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.FakeTrackSelection;
import com.google.android.exoplayer2.testutil.FakeTrackSelector;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.upstream.Allocator;
import java.util.ArrayList;
import java.util.Arrays;

View File

@ -22,8 +22,8 @@ import static org.mockito.Mockito.when;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.RobolectricUtil;
import com.google.android.exoplayer2.drm.DrmInitData.SchemeData;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import java.util.HashMap;
import org.junit.After;
import org.junit.Before;

View File

@ -21,11 +21,11 @@ import static org.junit.Assert.fail;
import android.os.ConditionVariable;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.RobolectricUtil;
import com.google.android.exoplayer2.offline.DownloadManager.DownloadListener;
import com.google.android.exoplayer2.offline.DownloadManager.DownloadState;
import com.google.android.exoplayer2.offline.DownloadManager.DownloadState.State;
import com.google.android.exoplayer2.testutil.DummyMainThread;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.upstream.DummyDataSource;
import com.google.android.exoplayer2.upstream.cache.Cache;
import com.google.android.exoplayer2.util.Util;

View File

@ -20,7 +20,6 @@ import static org.junit.Assert.fail;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RobolectricUtil;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.Timeline.Window;
@ -29,6 +28,7 @@ import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.MediaSourceTestRunner;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.testutil.TimelineAsserts;
import java.io.IOException;
import org.junit.Before;

View File

@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RobolectricUtil;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
@ -27,6 +26,7 @@ import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.MediaSourceTestRunner;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.testutil.TimelineAsserts;
import java.io.IOException;
import org.junit.Test;

View File

@ -22,7 +22,6 @@ import static org.mockito.Mockito.verify;
import android.os.ConditionVariable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RobolectricUtil;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.testutil.DummyMainThread;
@ -31,6 +30,7 @@ import com.google.android.exoplayer2.testutil.FakeShuffleOrder;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.MediaSourceTestRunner;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.testutil.TimelineAsserts;
import java.io.IOException;
import java.util.Arrays;

View File

@ -17,12 +17,12 @@ package com.google.android.exoplayer2.source;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.RobolectricUtil;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.MediaSourceTestRunner;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.testutil.TimelineAsserts;
import java.io.IOException;
import org.junit.Before;

View File

@ -19,13 +19,13 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.RobolectricUtil;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MergingMediaSource.IllegalMergeException;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.testutil.MediaSourceTestRunner;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;

View File

@ -35,15 +35,7 @@ android {
dependencies {
compile project(modulePrefix + 'library-core')
compile 'com.android.support:support-annotations:' + supportLibraryVersion
androidTestCompile project(modulePrefix + 'testutils')
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion
testCompile project(modulePrefix + 'testutils')
testCompile 'com.google.truth:truth:' + truthVersion
testCompile 'junit:junit:' + junitVersion
testCompile 'org.mockito:mockito-core:' + mockitoVersion
testCompile 'org.robolectric:robolectric:' + robolectricVersion
testCompile project(modulePrefix + 'testutils-robolectric')
}
ext {

View File

@ -1,100 +0,0 @@
/*
* Copyright (C) 2017 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.source.dash.offline;
import android.net.Uri;
/**
* Data for DASH downloading tests.
*/
/* package */ interface DashDownloadTestData {
Uri TEST_MPD_URI = Uri.parse("test.mpd");
byte[] TEST_MPD =
("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" type=\"static\" "
+ " mediaPresentationDuration=\"PT31S\">\n"
+ " <Period duration=\"PT16S\" >\n"
+ " <AdaptationSet>\n"
+ " <SegmentList>\n"
+ " <SegmentTimeline>\n"
+ " <S d=\"5\" />\n"
+ " <S d=\"5\" />\n"
+ " <S d=\"5\" />\n"
+ " </SegmentTimeline>\n"
+ " </SegmentList>\n"
+ " <Representation>\n"
+ " <SegmentList>\n"
// Bounded range data
+ " <Initialization range=\"0-9\" sourceURL=\"audio_init_data\" />\n"
// Unbounded range data
+ " <SegmentURL media=\"audio_segment_1\" />\n"
+ " <SegmentURL media=\"audio_segment_2\" />\n"
+ " <SegmentURL media=\"audio_segment_3\" />\n"
+ " </SegmentList>\n"
+ " </Representation>\n"
+ " </AdaptationSet>\n"
+ " <AdaptationSet>\n"
// This segment list has a 1 second offset to make sure the progressive download order
+ " <SegmentList>\n"
+ " <SegmentTimeline>\n"
+ " <S t=\"1\" d=\"5\" />\n" // 1s offset
+ " <S d=\"5\" />\n"
+ " <S d=\"5\" />\n"
+ " </SegmentTimeline>\n"
+ " </SegmentList>\n"
+ " <Representation>\n"
+ " <SegmentList>\n"
+ " <SegmentURL media=\"text_segment_1\" />\n"
+ " <SegmentURL media=\"text_segment_2\" />\n"
+ " <SegmentURL media=\"text_segment_3\" />\n"
+ " </SegmentList>\n"
+ " </Representation>\n"
+ " </AdaptationSet>\n"
+ " </Period>\n"
+ " <Period>\n"
+ " <SegmentList>\n"
+ " <SegmentTimeline>\n"
+ " <S d=\"5\" />\n"
+ " <S d=\"5\" />\n"
+ " <S d=\"5\" />\n"
+ " </SegmentTimeline>\n"
+ " </SegmentList>\n"
+ " <AdaptationSet>\n"
+ " <Representation>\n"
+ " <SegmentList>\n"
+ " <SegmentURL media=\"period_2_segment_1\" />\n"
+ " <SegmentURL media=\"period_2_segment_2\" />\n"
+ " <SegmentURL media=\"period_2_segment_3\" />\n"
+ " </SegmentList>\n"
+ " </Representation>\n"
+ " </AdaptationSet>\n"
+ " </Period>\n"
+ "</MPD>").getBytes();
byte[] TEST_MPD_NO_INDEX =
("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" type=\"dynamic\">\n"
+ " <Period start=\"PT6462826.784S\" >\n"
+ " <AdaptationSet>\n"
+ " <Representation>\n"
+ " <SegmentBase indexRange='0-10'/>\n"
+ " </Representation>\n"
+ " </AdaptationSet>\n"
+ " </Period>\n"
+ "</MPD>").getBytes();
}

View File

@ -50,6 +50,7 @@ import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
@ -1113,7 +1114,9 @@ public final class DashMediaSource implements MediaSource {
@Override
public Long parse(Uri uri, InputStream inputStream) throws IOException {
String firstLine = new BufferedReader(new InputStreamReader(inputStream)).readLine();
String firstLine =
new BufferedReader(new InputStreamReader(inputStream, Charset.forName(C.UTF8_NAME)))
.readLine();
try {
Matcher matcher = TIMESTAMP_WITH_TIMEZONE_PATTERN.matcher(firstLine);
if (!matcher.matches()) {

View File

@ -18,16 +18,6 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.source.dash.test">
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="27"/>
<application android:debuggable="true"
android:allowBackup="false"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
<uses-library android:name="android.test.runner"/>
</application>
<instrumentation
android:targetPackage="com.google.android.exoplayer2.source.dash.test"
android:name="android.test.InstrumentationTestRunner"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="25"/>
</manifest>

View File

@ -0,0 +1,76 @@
/*
* Copyright (C) 2017 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.source.dash;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Util;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Unit test for {@link DashMediaSource}. */
@RunWith(RobolectricTestRunner.class)
public final class DashMediaSourceTest {
@Test
public void testIso8601ParserParse() throws IOException {
DashMediaSource.Iso8601Parser parser = new DashMediaSource.Iso8601Parser();
// UTC.
assertParseStringToLong(1512381697000L, parser, "2017-12-04T10:01:37Z");
assertParseStringToLong(1512381697000L, parser, "2017-12-04T10:01:37+00:00");
assertParseStringToLong(1512381697000L, parser, "2017-12-04T10:01:37+0000");
assertParseStringToLong(1512381697000L, parser, "2017-12-04T10:01:37+00");
// Positive timezone offsets.
assertParseStringToLong(1512381697000L - 4980000L, parser, "2017-12-04T10:01:37+01:23");
assertParseStringToLong(1512381697000L - 4980000L, parser, "2017-12-04T10:01:37+0123");
assertParseStringToLong(1512381697000L - 3600000L, parser, "2017-12-04T10:01:37+01");
// Negative timezone offsets with minus character.
assertParseStringToLong(1512381697000L + 4980000L, parser, "2017-12-04T10:01:37-01:23");
assertParseStringToLong(1512381697000L + 4980000L, parser, "2017-12-04T10:01:37-0123");
assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:37-01:00");
assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:37-0100");
assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:37-01");
// Negative timezone offsets with hyphen character.
assertParseStringToLong(1512381697000L + 4980000L, parser, "2017-12-04T10:01:3701:23");
assertParseStringToLong(1512381697000L + 4980000L, parser, "2017-12-04T10:01:370123");
assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:3701:00");
assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:370100");
assertParseStringToLong(1512381697000L + 3600000L, parser, "2017-12-04T10:01:3701");
}
@Test
public void testIso8601ParserParseMissingTimezone() throws IOException {
DashMediaSource.Iso8601Parser parser = new DashMediaSource.Iso8601Parser();
try {
assertParseStringToLong(0, parser, "2017-12-04T10:01:37");
fail();
} catch (ParserException e) {
// Expected.
}
}
private static void assertParseStringToLong(
long expected, ParsingLoadable.Parser<Long> parser, String data) throws IOException {
long actual = parser.parse(null, new ByteArrayInputStream(Util.getUtf8Bytes(data)));
assertThat(actual).isEqualTo(expected);
}
}

View File

@ -28,33 +28,38 @@ import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegm
import com.google.android.exoplayer2.upstream.DummyDataSource;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.Arrays;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Unit tests for {@link DashUtil}.
*/
public final class DashUtilTest extends TestCase {
/** Unit tests for {@link DashUtil}. */
@RunWith(RobolectricTestRunner.class)
public final class DashUtilTest {
@Test
public void testLoadDrmInitDataFromManifest() throws Exception {
Period period = newPeriod(newAdaptationSets(newRepresentations(newDrmInitData())));
DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period);
assertThat(drmInitData).isEqualTo(newDrmInitData());
}
@Test
public void testLoadDrmInitDataMissing() throws Exception {
Period period = newPeriod(newAdaptationSets(newRepresentations(null /* no init data */)));
DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period);
assertThat(drmInitData).isNull();
}
@Test
public void testLoadDrmInitDataNoRepresentations() throws Exception {
Period period = newPeriod(newAdaptationSets(/* no representation */));
Period period = newPeriod(newAdaptationSets(/* no representation */ ));
DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period);
assertThat(drmInitData).isNull();
}
@Test
public void testLoadDrmInitDataNoAdaptationSets() throws Exception {
Period period = newPeriod(/* no adaptation set */);
Period period = newPeriod(/* no adaptation set */ );
DrmInitData drmInitData = DashUtil.loadDrmInitData(DummyDataSource.INSTANCE, period);
assertThat(drmInitData).isNull();
}
@ -68,8 +73,18 @@ public final class DashUtilTest extends TestCase {
}
private static Representation newRepresentations(DrmInitData drmInitData) {
Format format = Format.createVideoContainerFormat("id", MimeTypes.VIDEO_MP4,
MimeTypes.VIDEO_H264, "", Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, 0);
Format format =
Format.createVideoContainerFormat(
"id",
MimeTypes.VIDEO_MP4,
MimeTypes.VIDEO_H264,
"",
Format.NO_VALUE,
1024,
768,
Format.NO_VALUE,
null,
0);
if (drmInitData != null) {
format = format.copyWithDrmInitData(drmInitData);
}
@ -77,8 +92,7 @@ public final class DashUtilTest extends TestCase {
}
private static DrmInitData newDrmInitData() {
return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, "mimeType",
new byte[] {1, 4, 7, 0, 3, 6}));
return new DrmInitData(
new SchemeData(C.WIDEVINE_UUID, "mimeType", new byte[] {1, 4, 7, 0, 3, 6}));
}
}

View File

@ -0,0 +1,356 @@
/*
* Copyright (C) 2017 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.source.dash;
import static com.google.common.truth.Truth.assertThat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.metadata.MetadataInputBuffer;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder;
import com.google.android.exoplayer2.source.dash.manifest.EventStream;
import com.google.android.exoplayer2.util.MimeTypes;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Unit test for {@link EventSampleStream}.
*/
@RunWith(RobolectricTestRunner.class)
public final class EventSampleStreamTest {
private static final String SCHEME_ID = "urn:test";
private static final String VALUE = "123";
private static final Format FORMAT = Format.createSampleFormat("urn:test/123",
MimeTypes.APPLICATION_EMSG, null, Format.NO_VALUE, null);
private static final byte[] MESSAGE_DATA = new byte[] {1, 2, 3, 4};
private static final long DURATION_MS = 3000;
private static final long TIME_SCALE = 1000;
private FormatHolder formatHolder;
private MetadataInputBuffer inputBuffer;
private EventMessageEncoder eventMessageEncoder;
@Before
public void setUp() {
formatHolder = new FormatHolder();
inputBuffer = new MetadataInputBuffer();
eventMessageEncoder = new EventMessageEncoder();
}
/**
* Tests that {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} will
* return format for the first call.
*/
@Test
public void testReadDataReturnFormatForFirstRead() {
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[0], new EventMessage[0]);
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false);
int result = readData(sampleStream);
assertThat(result).isEqualTo(C.RESULT_FORMAT_READ);
assertThat(formatHolder.format).isEqualTo(FORMAT);
}
/**
* Tests that a non-dynamic {@link EventSampleStream} will return a buffer with
* {@link C#BUFFER_FLAG_END_OF_STREAM} when trying to read sample out-of-bound.
*/
@Test
public void testReadDataOutOfBoundReturnEndOfStreamAfterFormatForNonDynamicEventSampleStream() {
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[0], new EventMessage[0]);
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false);
// first read - read format
readData(sampleStream);
int result = readData(sampleStream);
assertThat(result).isEqualTo(C.RESULT_BUFFER_READ);
assertThat(inputBuffer.isEndOfStream()).isTrue();
}
/**
* Tests that a dynamic {@link EventSampleStream} will return {@link C#RESULT_NOTHING_READ}
* when trying to read sample out-of-bound.
*/
@Test
public void testReadDataOutOfBoundReturnEndOfStreamAfterFormatForDynamicEventSampleStream() {
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[0], new EventMessage[0]);
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, true);
// first read - read format
readData(sampleStream);
int result = readData(sampleStream);
assertThat(result).isEqualTo(C.RESULT_NOTHING_READ);
}
/**
* Tests that {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} will
* return sample data after the first call.
*/
@Test
public void testReadDataReturnDataAfterFormat() {
long presentationTimeUs = 1000000;
EventMessage eventMessage = newEventMessageWithIdAndTime(1, presentationTimeUs);
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs}, new EventMessage[] {eventMessage});
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false);
// first read - read format
readData(sampleStream);
int result = readData(sampleStream);
assertThat(result).isEqualTo(C.RESULT_BUFFER_READ);
assertThat(inputBuffer.data.array())
.isEqualTo(getEncodedMessage(eventMessage));
}
/**
* Tests that {@link EventSampleStream#skipData(long)} will skip until the given position, and
* the next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
* will return sample data from that position.
*/
@Test
public void testSkipDataThenReadDataReturnDataFromSkippedPosition() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1, presentationTimeUs2},
new EventMessage[] {eventMessage1, eventMessage2});
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false);
// first read - read format
readData(sampleStream);
int skipped = sampleStream.skipData(presentationTimeUs2);
int result = readData(sampleStream);
assertThat(skipped).isEqualTo(1);
assertThat(result).isEqualTo(C.RESULT_BUFFER_READ);
assertThat(inputBuffer.data.array())
.isEqualTo(getEncodedMessage(eventMessage2));
}
/**
* Tests that {@link EventSampleStream#seekToUs(long)} (long)} will seek to the given position,
* and the next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
* will return sample data from that position.
*/
@Test
public void testSeekToUsThenReadDataReturnDataFromSeekPosition() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
EventStream eventStream = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1, presentationTimeUs2},
new EventMessage[] {eventMessage1, eventMessage2});
EventSampleStream sampleStream = new EventSampleStream(eventStream, FORMAT, false);
// first read - read format
readData(sampleStream);
sampleStream.seekToUs(presentationTimeUs2);
int result = readData(sampleStream);
assertThat(result).isEqualTo(C.RESULT_BUFFER_READ);
assertThat(inputBuffer.data.array())
.isEqualTo(getEncodedMessage(eventMessage2));
}
/**
* Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the
* underlying event stream, but keep the read timestamp, so the next
* {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
* will return sample data from after the last read sample timestamp.
*/
@Test
public void testUpdateEventStreamContinueToReadAfterLastReadSamplePresentationTime() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
long presentationTimeUs3 = 3000000;
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1, presentationTimeUs2},
new EventMessage[] {eventMessage1, eventMessage2});
EventStream eventStream2 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1, presentationTimeUs2, presentationTimeUs3},
new EventMessage[] {eventMessage1, eventMessage2, eventMessage3});
EventSampleStream sampleStream = new EventSampleStream(eventStream1, FORMAT, true);
// first read - read format
readData(sampleStream);
// read first and second sample.
readData(sampleStream);
readData(sampleStream);
sampleStream.updateEventStream(eventStream2, true);
int result = readData(sampleStream);
assertThat(result).isEqualTo(C.RESULT_BUFFER_READ);
assertThat(inputBuffer.data.array())
.isEqualTo(getEncodedMessage(eventMessage3));
}
/**
* Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the
* underlying event stream, but keep the timestamp the stream has skipped to, so the next
* {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
* will return sample data from the skipped position.
*/
@Test
public void testSkipDataThenUpdateStreamContinueToReadFromSkippedPosition() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
long presentationTimeUs3 = 3000000;
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1, presentationTimeUs2},
new EventMessage[] {eventMessage1, eventMessage2});
EventStream eventStream2 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1, presentationTimeUs2, presentationTimeUs3},
new EventMessage[] {eventMessage1, eventMessage2, eventMessage3});
EventSampleStream sampleStream = new EventSampleStream(eventStream1, FORMAT, true);
// first read - read format
readData(sampleStream);
sampleStream.skipData(presentationTimeUs2 + 1);
sampleStream.updateEventStream(eventStream2, true);
int result = readData(sampleStream);
assertThat(result).isEqualTo(C.RESULT_BUFFER_READ);
assertThat(inputBuffer.data.array())
.isEqualTo(getEncodedMessage(eventMessage3));
}
/**
* Tests that {@link EventSampleStream#skipData(long)} will only skip to the point right after
* it last event. A following {@link EventSampleStream#updateEventStream(EventStream, boolean)}
* will update the underlying event stream and keep the timestamp the stream has skipped to, so
* the next {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
* will return sample data from the skipped position.
*/
@Test
public void testSkipDataThenUpdateStreamContinueToReadDoNotSkippedMoreThanAvailable() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
long presentationTimeUs3 = 3000000;
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1},
new EventMessage[] {eventMessage1});
EventStream eventStream2 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1, presentationTimeUs2, presentationTimeUs3},
new EventMessage[] {eventMessage1, eventMessage2, eventMessage3});
EventSampleStream sampleStream = new EventSampleStream(eventStream1, FORMAT, true);
// first read - read format
readData(sampleStream);
// even though the skip call is to 2000001, since eventStream1 only contains sample until
// 1000000, it will only skip to 1000001.
sampleStream.skipData(presentationTimeUs2 + 1);
sampleStream.updateEventStream(eventStream2, true);
int result = readData(sampleStream);
assertThat(result).isEqualTo(C.RESULT_BUFFER_READ);
assertThat(inputBuffer.data.array())
.isEqualTo(getEncodedMessage(eventMessage2));
}
/**
* Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the
* underlying event stream, but keep the timestamp the stream has seek to, so the next
* {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
* will return sample data from the seek position.
*/
@Test
public void testSeekToUsThenUpdateStreamContinueToReadFromSeekPosition() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
long presentationTimeUs3 = 3000000;
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1, presentationTimeUs2},
new EventMessage[] {eventMessage1, eventMessage2});
EventStream eventStream2 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1, presentationTimeUs2, presentationTimeUs3},
new EventMessage[] {eventMessage1, eventMessage2, eventMessage3});
EventSampleStream sampleStream = new EventSampleStream(eventStream1, FORMAT, true);
// first read - read format
readData(sampleStream);
sampleStream.seekToUs(presentationTimeUs2);
sampleStream.updateEventStream(eventStream2, true);
int result = readData(sampleStream);
assertThat(result).isEqualTo(C.RESULT_BUFFER_READ);
assertThat(inputBuffer.data.array())
.isEqualTo(getEncodedMessage(eventMessage2));
}
/**
* Tests that {@link EventSampleStream#updateEventStream(EventStream, boolean)} will update the
* underlying event stream, but keep the timestamp the stream has seek to, so the next
* {@link EventSampleStream#readData(FormatHolder, DecoderInputBuffer, boolean)} call
* will return sample data from the seek position.
*/
@Test
public void testSeekToThenUpdateStreamContinueToReadFromSeekPositionEvenSeekMoreThanAvailable() {
long presentationTimeUs1 = 1000000;
long presentationTimeUs2 = 2000000;
long presentationTimeUs3 = 3000000;
EventMessage eventMessage1 = newEventMessageWithIdAndTime(1, presentationTimeUs1);
EventMessage eventMessage2 = newEventMessageWithIdAndTime(2, presentationTimeUs2);
EventMessage eventMessage3 = newEventMessageWithIdAndTime(3, presentationTimeUs3);
EventStream eventStream1 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1},
new EventMessage[] {eventMessage1});
EventStream eventStream2 = new EventStream(SCHEME_ID, VALUE, TIME_SCALE,
new long[] {presentationTimeUs1, presentationTimeUs2, presentationTimeUs3},
new EventMessage[] {eventMessage1, eventMessage2, eventMessage3});
EventSampleStream sampleStream = new EventSampleStream(eventStream1, FORMAT, true);
// first read - read format
readData(sampleStream);
sampleStream.seekToUs(presentationTimeUs2 + 1);
sampleStream.updateEventStream(eventStream2, true);
int result = readData(sampleStream);
assertThat(result).isEqualTo(C.RESULT_BUFFER_READ);
assertThat(inputBuffer.data.array())
.isEqualTo(getEncodedMessage(eventMessage3));
}
private int readData(EventSampleStream sampleStream) {
inputBuffer.clear();
return sampleStream.readData(formatHolder, inputBuffer, false);
}
private EventMessage newEventMessageWithIdAndTime(int id, long presentationTimeUs) {
return new EventMessage(SCHEME_ID, VALUE, DURATION_MS, id, MESSAGE_DATA, presentationTimeUs);
}
private byte[] getEncodedMessage(EventMessage eventMessage) {
return eventMessageEncoder.encode(eventMessage, TIME_SCALE);
}
}

View File

@ -18,39 +18,47 @@ package com.google.android.exoplayer2.source.dash.manifest;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/**
* Unit tests for {@link DashManifestParser}.
*/
public class DashManifestParserTest extends InstrumentationTestCase {
/** Unit tests for {@link DashManifestParser}. */
@RunWith(RobolectricTestRunner.class)
public class DashManifestParserTest {
private static final String SAMPLE_MPD_1 = "sample_mpd_1";
private static final String SAMPLE_MPD_2_UNKNOWN_MIME_TYPE = "sample_mpd_2_unknown_mime_type";
private static final String SAMPLE_MPD_3_SEGMENT_TEMPLATE = "sample_mpd_3_segment_template";
private static final String SAMPLE_MPD_4_EVENT_STREAM = "sample_mpd_4_event_stream";
/**
* Simple test to ensure the sample manifests parse without any exceptions being thrown.
*/
/** Simple test to ensure the sample manifests parse without any exceptions being thrown. */
@Test
public void testParseMediaPresentationDescription() throws IOException {
DashManifestParser parser = new DashManifestParser();
parser.parse(Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_1));
parser.parse(Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_2_UNKNOWN_MIME_TYPE));
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_MPD_1));
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_MPD_2_UNKNOWN_MIME_TYPE));
}
@Test
public void testParseMediaPresentationDescriptionWithSegmentTemplate() throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest mpd = parser.parse(Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_3_SEGMENT_TEMPLATE));
DashManifest mpd =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_MPD_3_SEGMENT_TEMPLATE));
assertThat(mpd.getPeriodCount()).isEqualTo(1);
@ -75,11 +83,13 @@ public class DashManifestParserTest extends InstrumentationTestCase {
}
}
public void testParseMediaPresentationDescriptionCanParseEventStream()
throws IOException {
@Test
public void testParseMediaPresentationDescriptionCanParseEventStream() throws IOException {
DashManifestParser parser = new DashManifestParser();
DashManifest mpd = parser.parse(Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(getInstrumentation(), SAMPLE_MPD_4_EVENT_STREAM));
DashManifest mpd =
parser.parse(
Uri.parse("https://example.com/test.mpd"),
TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_MPD_4_EVENT_STREAM));
Period period = mpd.getPeriod(0);
assertThat(period.eventStreams).hasSize(3);
@ -87,8 +97,14 @@ public class DashManifestParserTest extends InstrumentationTestCase {
// assert text-only event stream
EventStream eventStream1 = period.eventStreams.get(0);
assertThat(eventStream1.events.length).isEqualTo(1);
EventMessage expectedEvent1 = new EventMessage("urn:uuid:XYZY", "call", 10000, 0,
"+ 1 800 10101010".getBytes(), 0);
EventMessage expectedEvent1 =
new EventMessage(
"urn:uuid:XYZY",
"call",
10000,
0,
"+ 1 800 10101010".getBytes(Charset.forName(C.UTF8_NAME)),
0);
assertThat(eventStream1.events[0]).isEqualTo(expectedEvent1);
// assert CData-structured event stream
@ -135,6 +151,7 @@ public class DashManifestParserTest extends InstrumentationTestCase {
1000000000));
}
@Test
public void testParseCea608AccessibilityChannel() {
assertThat(
DashManifestParser.parseCea608AccessibilityChannel(
@ -175,6 +192,7 @@ public class DashManifestParserTest extends InstrumentationTestCase {
.isEqualTo(Format.NO_VALUE);
}
@Test
public void testParseCea708AccessibilityChannel() {
assertThat(
DashManifestParser.parseCea708AccessibilityChannel(
@ -226,5 +244,4 @@ public class DashManifestParserTest extends InstrumentationTestCase {
private static List<Descriptor> buildCea708AccessibilityDescriptors(String value) {
return Collections.singletonList(new Descriptor("urn:scte:dash:cc:cea-708:2015", value, null));
}
}

View File

@ -24,109 +24,143 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Unit tests for {@link DashManifest}.
*/
public class DashManifestTest extends TestCase {
/** Unit tests for {@link DashManifest}. */
@RunWith(RobolectricTestRunner.class)
public class DashManifestTest {
private static final UtcTimingElement DUMMY_UTC_TIMING = new UtcTimingElement("", "");
private static final SingleSegmentBase DUMMY_SEGMENT_BASE = new SingleSegmentBase();
private static final Format DUMMY_FORMAT = Format.createSampleFormat("", "", 0);
@Test
public void testCopy() throws Exception {
Representation[][][] representations = newRepresentations(3, 2, 3);
DashManifest sourceManifest = newDashManifest(10,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0]),
newAdaptationSet(3, representations[0][1])),
newPeriod("4", 4,
newAdaptationSet(5, representations[1][0]),
newAdaptationSet(6, representations[1][1])),
newPeriod("7", 7,
newAdaptationSet(8, representations[2][0]),
newAdaptationSet(9, representations[2][1])));
DashManifest sourceManifest =
newDashManifest(
10,
newPeriod(
"1",
1,
newAdaptationSet(2, representations[0][0]),
newAdaptationSet(3, representations[0][1])),
newPeriod(
"4",
4,
newAdaptationSet(5, representations[1][0]),
newAdaptationSet(6, representations[1][1])),
newPeriod(
"7",
7,
newAdaptationSet(8, representations[2][0]),
newAdaptationSet(9, representations[2][1])));
List<RepresentationKey> keys = Arrays.asList(
new RepresentationKey(0, 0, 0),
new RepresentationKey(0, 0, 1),
new RepresentationKey(0, 1, 2),
new RepresentationKey(1, 0, 1),
new RepresentationKey(1, 1, 0),
new RepresentationKey(1, 1, 2),
new RepresentationKey(2, 0, 1),
new RepresentationKey(2, 0, 2),
new RepresentationKey(2, 1, 0));
List<RepresentationKey> keys =
Arrays.asList(
new RepresentationKey(0, 0, 0),
new RepresentationKey(0, 0, 1),
new RepresentationKey(0, 1, 2),
new RepresentationKey(1, 0, 1),
new RepresentationKey(1, 1, 0),
new RepresentationKey(1, 1, 2),
new RepresentationKey(2, 0, 1),
new RepresentationKey(2, 0, 2),
new RepresentationKey(2, 1, 0));
// Keys don't need to be in any particular order
Collections.shuffle(keys, new Random(0));
DashManifest copyManifest = sourceManifest.copy(keys);
DashManifest expectedManifest = newDashManifest(10,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0][0], representations[0][0][1]),
newAdaptationSet(3, representations[0][1][2])),
newPeriod("4", 4,
newAdaptationSet(5, representations[1][0][1]),
newAdaptationSet(6, representations[1][1][0], representations[1][1][2])),
newPeriod("7", 7,
newAdaptationSet(8, representations[2][0][1], representations[2][0][2]),
newAdaptationSet(9, representations[2][1][0])));
DashManifest expectedManifest =
newDashManifest(
10,
newPeriod(
"1",
1,
newAdaptationSet(2, representations[0][0][0], representations[0][0][1]),
newAdaptationSet(3, representations[0][1][2])),
newPeriod(
"4",
4,
newAdaptationSet(5, representations[1][0][1]),
newAdaptationSet(6, representations[1][1][0], representations[1][1][2])),
newPeriod(
"7",
7,
newAdaptationSet(8, representations[2][0][1], representations[2][0][2]),
newAdaptationSet(9, representations[2][1][0])));
assertManifestEquals(expectedManifest, copyManifest);
}
@Test
public void testCopySameAdaptationIndexButDifferentPeriod() throws Exception {
Representation[][][] representations = newRepresentations(2, 1, 1);
DashManifest sourceManifest = newDashManifest(10,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0])),
newPeriod("4", 4,
newAdaptationSet(5, representations[1][0])));
DashManifest sourceManifest =
newDashManifest(
10,
newPeriod("1", 1, newAdaptationSet(2, representations[0][0])),
newPeriod("4", 4, newAdaptationSet(5, representations[1][0])));
DashManifest copyManifest = sourceManifest.copy(Arrays.asList(
new RepresentationKey(0, 0, 0),
new RepresentationKey(1, 0, 0)));
DashManifest copyManifest =
sourceManifest.copy(
Arrays.asList(new RepresentationKey(0, 0, 0), new RepresentationKey(1, 0, 0)));
DashManifest expectedManifest = newDashManifest(10,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0])),
newPeriod("4", 4,
newAdaptationSet(5, representations[1][0])));
DashManifest expectedManifest =
newDashManifest(
10,
newPeriod("1", 1, newAdaptationSet(2, representations[0][0])),
newPeriod("4", 4, newAdaptationSet(5, representations[1][0])));
assertManifestEquals(expectedManifest, copyManifest);
}
@Test
public void testCopySkipPeriod() throws Exception {
Representation[][][] representations = newRepresentations(3, 2, 3);
DashManifest sourceManifest = newDashManifest(10,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0]),
newAdaptationSet(3, representations[0][1])),
newPeriod("4", 4,
newAdaptationSet(5, representations[1][0]),
newAdaptationSet(6, representations[1][1])),
newPeriod("7", 7,
newAdaptationSet(8, representations[2][0]),
newAdaptationSet(9, representations[2][1])));
DashManifest sourceManifest =
newDashManifest(
10,
newPeriod(
"1",
1,
newAdaptationSet(2, representations[0][0]),
newAdaptationSet(3, representations[0][1])),
newPeriod(
"4",
4,
newAdaptationSet(5, representations[1][0]),
newAdaptationSet(6, representations[1][1])),
newPeriod(
"7",
7,
newAdaptationSet(8, representations[2][0]),
newAdaptationSet(9, representations[2][1])));
DashManifest copyManifest = sourceManifest.copy(Arrays.asList(
new RepresentationKey(0, 0, 0),
new RepresentationKey(0, 0, 1),
new RepresentationKey(0, 1, 2),
DashManifest copyManifest =
sourceManifest.copy(
Arrays.asList(
new RepresentationKey(0, 0, 0),
new RepresentationKey(0, 0, 1),
new RepresentationKey(0, 1, 2),
new RepresentationKey(2, 0, 1),
new RepresentationKey(2, 0, 2),
new RepresentationKey(2, 1, 0)));
new RepresentationKey(2, 0, 1),
new RepresentationKey(2, 0, 2),
new RepresentationKey(2, 1, 0)));
DashManifest expectedManifest = newDashManifest(7,
newPeriod("1", 1,
newAdaptationSet(2, representations[0][0][0], representations[0][0][1]),
newAdaptationSet(3, representations[0][1][2])),
newPeriod("7", 4,
newAdaptationSet(8, representations[2][0][1], representations[2][0][2]),
newAdaptationSet(9, representations[2][1][0])));
DashManifest expectedManifest =
newDashManifest(
7,
newPeriod(
"1",
1,
newAdaptationSet(2, representations[0][0][0], representations[0][0][1]),
newAdaptationSet(3, representations[0][1][2])),
newPeriod(
"7",
4,
newAdaptationSet(8, representations[2][0][1], representations[2][0][2]),
newAdaptationSet(9, representations[2][1][0])));
assertManifestEquals(expectedManifest, copyManifest);
}
@ -164,8 +198,8 @@ public class DashManifestTest extends TestCase {
}
}
private static Representation[][][] newRepresentations(int periodCount, int adaptationSetCounts,
int representationCounts) {
private static Representation[][][] newRepresentations(
int periodCount, int adaptationSetCounts, int representationCounts) {
Representation[][][] representations = new Representation[periodCount][][];
for (int i = 0; i < periodCount; i++) {
representations[i] = new Representation[adaptationSetCounts][];
@ -184,8 +218,8 @@ public class DashManifestTest extends TestCase {
}
private static DashManifest newDashManifest(int duration, Period... periods) {
return new DashManifest(0, duration, 1, false, 2, 3, 4, 12345, DUMMY_UTC_TIMING, Uri.EMPTY,
Arrays.asList(periods));
return new DashManifest(
0, duration, 1, false, 2, 3, 4, 12345, DUMMY_UTC_TIMING, Uri.EMPTY, Arrays.asList(periods));
}
private static Period newPeriod(String id, int startMs, AdaptationSet... adaptationSets) {
@ -195,5 +229,4 @@ public class DashManifestTest extends TestCase {
private static AdaptationSet newAdaptationSet(int seed, Representation... representations) {
return new AdaptationSet(++seed, ++seed, Arrays.asList(representations), null, null);
}
}

View File

@ -18,17 +18,19 @@ package com.google.android.exoplayer2.source.dash.manifest;
import static com.google.common.truth.Truth.assertThat;
import com.google.android.exoplayer2.C;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Unit test for {@link RangedUri}.
*/
public class RangedUriTest extends TestCase {
/** Unit test for {@link RangedUri}. */
@RunWith(RobolectricTestRunner.class)
public class RangedUriTest {
private static final String BASE_URI = "http://www.test.com/";
private static final String PARTIAL_URI = "path/file.ext";
private static final String FULL_URI = BASE_URI + PARTIAL_URI;
@Test
public void testMerge() {
RangedUri rangeA = new RangedUri(FULL_URI, 0, 10);
RangedUri rangeB = new RangedUri(FULL_URI, 10, 10);
@ -36,6 +38,7 @@ public class RangedUriTest extends TestCase {
assertMerge(rangeA, rangeB, expected, null);
}
@Test
public void testMergeUnbounded() {
RangedUri rangeA = new RangedUri(FULL_URI, 0, 10);
RangedUri rangeB = new RangedUri(FULL_URI, 10, C.LENGTH_UNSET);
@ -43,6 +46,7 @@ public class RangedUriTest extends TestCase {
assertMerge(rangeA, rangeB, expected, null);
}
@Test
public void testNonMerge() {
// A and B do not overlap, so should not merge
RangedUri rangeA = new RangedUri(FULL_URI, 0, 10);
@ -65,6 +69,7 @@ public class RangedUriTest extends TestCase {
assertNonMerge(rangeA, rangeB, null);
}
@Test
public void testMergeWithBaseUri() {
RangedUri rangeA = new RangedUri(PARTIAL_URI, 0, 10);
RangedUri rangeB = new RangedUri(FULL_URI, 10, 10);
@ -85,5 +90,4 @@ public class RangedUriTest extends TestCase {
merged = rangeB.attemptMerge(rangeA, baseUrl);
assertThat(merged).isNull();
}
}

View File

@ -20,27 +20,49 @@ import static com.google.common.truth.Truth.assertThat;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.dash.manifest.SegmentBase.SingleSegmentBase;
import com.google.android.exoplayer2.util.MimeTypes;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Unit test for {@link Representation}.
*/
public class RepresentationTest extends TestCase {
/** Unit test for {@link Representation}. */
@RunWith(RobolectricTestRunner.class)
public class RepresentationTest {
@Test
public void testGetCacheKey() {
String uri = "http://www.google.com";
SegmentBase base = new SingleSegmentBase(new RangedUri(null, 0, 1), 1, 0, 1, 1);
Format format = Format.createVideoContainerFormat("0", MimeTypes.APPLICATION_MP4, null,
MimeTypes.VIDEO_H264, 2500000, 1920, 1080, Format.NO_VALUE, null, 0);
Representation representation = Representation.newInstance("test_stream_1", 3, format, uri,
base);
Format format =
Format.createVideoContainerFormat(
"0",
MimeTypes.APPLICATION_MP4,
null,
MimeTypes.VIDEO_H264,
2500000,
1920,
1080,
Format.NO_VALUE,
null,
0);
Representation representation =
Representation.newInstance("test_stream_1", 3, format, uri, base);
assertThat(representation.getCacheKey()).isEqualTo("test_stream_1.0.3");
format = Format.createVideoContainerFormat("150", MimeTypes.APPLICATION_MP4, null,
MimeTypes.VIDEO_H264, 2500000, 1920, 1080, Format.NO_VALUE, null, 0);
representation = Representation.newInstance("test_stream_1", Representation.REVISION_ID_DEFAULT,
format, uri, base);
format =
Format.createVideoContainerFormat(
"150",
MimeTypes.APPLICATION_MP4,
null,
MimeTypes.VIDEO_H264,
2500000,
1920,
1080,
Format.NO_VALUE,
null,
0);
representation =
Representation.newInstance(
"test_stream_1", Representation.REVISION_ID_DEFAULT, format, uri, base);
assertThat(representation.getCacheKey()).isEqualTo("test_stream_1.150.-1");
}
}

View File

@ -16,14 +16,17 @@
package com.google.android.exoplayer2.source.dash.manifest;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Unit test for {@link UrlTemplate}.
*/
public class UrlTemplateTest extends TestCase {
/** Unit test for {@link UrlTemplate}. */
@RunWith(RobolectricTestRunner.class)
public class UrlTemplateTest {
@Test
public void testRealExamples() {
String template = "QualityLevels($Bandwidth$)/Fragments(video=$Time$,format=mpd-time-csf)";
UrlTemplate urlTemplate = UrlTemplate.compile(template);
@ -41,6 +44,7 @@ public class UrlTemplateTest extends TestCase {
assertThat(url).isEqualTo("chunk_ctvideo_cfm4s_ridabc1_cn10_w2073857842_mpd.m4s");
}
@Test
public void testFull() {
String template = "$Bandwidth$_a_$RepresentationID$_b_$Time$_c_$Number$";
UrlTemplate urlTemplate = UrlTemplate.compile(template);
@ -48,6 +52,7 @@ public class UrlTemplateTest extends TestCase {
assertThat(url).isEqualTo("650000_a_abc1_b_5000_c_10");
}
@Test
public void testFullWithDollarEscaping() {
String template = "$$$Bandwidth$$$_a$$_$RepresentationID$_b_$Time$_c_$Number$$$";
UrlTemplate urlTemplate = UrlTemplate.compile(template);
@ -55,6 +60,7 @@ public class UrlTemplateTest extends TestCase {
assertThat(url).isEqualTo("$650000$_a$_abc1_b_5000_c_10$");
}
@Test
public void testInvalidSubstitution() {
String template = "$IllegalId$";
try {
@ -64,5 +70,4 @@ public class UrlTemplateTest extends TestCase {
// Expected.
}
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2017 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.source.dash.offline;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import com.google.android.exoplayer2.offline.DownloadAction;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey;
import com.google.android.exoplayer2.upstream.DummyDataSource;
import com.google.android.exoplayer2.upstream.cache.Cache;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
/**
* Unit tests for {@link DashDownloadAction}.
*/
@RunWith(RobolectricTestRunner.class)
public class DashDownloadActionTest {
@Test
public void testDownloadActionIsNotRemoveAction() throws Exception {
DashDownloadAction action = new DashDownloadAction(Uri.parse("uri"), false, null);
assertThat(action.isRemoveAction()).isFalse();
}
@Test
public void testRemoveActionIsRemoveAction() throws Exception {
DashDownloadAction action2 = new DashDownloadAction(Uri.parse("uri"), true, null);
assertThat(action2.isRemoveAction()).isTrue();
}
@Test
public void testCreateDownloader() throws Exception {
MockitoAnnotations.initMocks(this);
DashDownloadAction action = new DashDownloadAction(Uri.parse("uri"), false, null);
DownloaderConstructorHelper constructorHelper = new DownloaderConstructorHelper(
Mockito.mock(Cache.class), DummyDataSource.FACTORY);
assertThat(action.createDownloader(constructorHelper)).isNotNull();
}
@Test
public void testSameUriDifferentAction_IsSameMedia() throws Exception {
DashDownloadAction action1 = new DashDownloadAction(Uri.parse("uri"), true, null);
DashDownloadAction action2 = new DashDownloadAction(Uri.parse("uri"), false, null);
assertThat(action1.isSameMedia(action2)).isTrue();
}
@Test
public void testDifferentUriAndAction_IsNotSameMedia() throws Exception {
DashDownloadAction action3 = new DashDownloadAction(Uri.parse("uri2"), true, null);
DashDownloadAction action4 = new DashDownloadAction(Uri.parse("uri"), false, null);
assertThat(action3.isSameMedia(action4)).isFalse();
}
@SuppressWarnings("EqualsWithItself")
@Test
public void testEquals() throws Exception {
DashDownloadAction action1 = new DashDownloadAction(Uri.parse("uri"), true, null);
assertThat(action1.equals(action1)).isTrue();
DashDownloadAction action2 = new DashDownloadAction(Uri.parse("uri"), true, null);
DashDownloadAction action3 = new DashDownloadAction(Uri.parse("uri"), true, null);
assertEqual(action2, action3);
DashDownloadAction action4 = new DashDownloadAction(Uri.parse("uri"), true, null);
DashDownloadAction action5 = new DashDownloadAction(Uri.parse("uri"), false, null);
assertNotEqual(action4, action5);
DashDownloadAction action6 = new DashDownloadAction(Uri.parse("uri"), false, null);
DashDownloadAction action7 =
new DashDownloadAction(Uri.parse("uri"), false, null, new RepresentationKey(0, 0, 0));
assertNotEqual(action6, action7);
DashDownloadAction action8 =
new DashDownloadAction(Uri.parse("uri"), false, null, new RepresentationKey(1, 1, 1));
DashDownloadAction action9 =
new DashDownloadAction(Uri.parse("uri"), false, null, new RepresentationKey(0, 0, 0));
assertNotEqual(action8, action9);
DashDownloadAction action10 = new DashDownloadAction(Uri.parse("uri"), true, null);
DashDownloadAction action11 = new DashDownloadAction(Uri.parse("uri2"), true, null);
assertNotEqual(action10, action11);
DashDownloadAction action12 = new DashDownloadAction(Uri.parse("uri"), false, null,
new RepresentationKey(0, 0, 0), new RepresentationKey(1, 1, 1));
DashDownloadAction action13 = new DashDownloadAction(Uri.parse("uri"), false, null,
new RepresentationKey(1, 1, 1), new RepresentationKey(0, 0, 0));
assertEqual(action12, action13);
DashDownloadAction action14 = new DashDownloadAction(Uri.parse("uri"), false, null,
new RepresentationKey(0, 0, 0));
DashDownloadAction action15 = new DashDownloadAction(Uri.parse("uri"), false, null,
new RepresentationKey(1, 1, 1), new RepresentationKey(0, 0, 0));
assertNotEqual(action14, action15);
DashDownloadAction action16 = new DashDownloadAction(Uri.parse("uri"), false, null);
DashDownloadAction action17 =
new DashDownloadAction(Uri.parse("uri"), false, null, new RepresentationKey[0]);
assertEqual(action16, action17);
}
@Test
public void testSerializerGetType() throws Exception {
DashDownloadAction action = new DashDownloadAction(Uri.parse("uri"), false, null);
assertThat(action.getType()).isNotNull();
}
@Test
public void testSerializerWriteRead() throws Exception {
doTestSerializationRoundTrip(new DashDownloadAction(Uri.parse("uri"), false, null));
doTestSerializationRoundTrip(new DashDownloadAction(Uri.parse("uri"), true, null));
doTestSerializationRoundTrip(new DashDownloadAction(Uri.parse("uri2"), false, null,
new RepresentationKey(0, 0, 0), new RepresentationKey(1, 1, 1)));
}
private static void assertNotEqual(DashDownloadAction action1, DashDownloadAction action2) {
assertThat(action1).isNotEqualTo(action2);
assertThat(action2).isNotEqualTo(action1);
}
private static void assertEqual(DashDownloadAction action1, DashDownloadAction action2) {
assertThat(action1).isEqualTo(action2);
assertThat(action2).isEqualTo(action1);
}
private static void doTestSerializationRoundTrip(DashDownloadAction action1) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
DataOutputStream output = new DataOutputStream(out);
action1.writeToStream(output);
ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
DataInputStream input = new DataInputStream(in);
DownloadAction action2 =
DashDownloadAction.DESERIALIZER.readFromStream(DownloadAction.MASTER_VERSION, input);
assertThat(action1).isEqualTo(action2);
}
}

View File

@ -0,0 +1,103 @@
/*
* Copyright (C) 2017 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.source.dash.offline;
import android.net.Uri;
import com.google.android.exoplayer2.C;
import java.nio.charset.Charset;
/** Data for DASH downloading tests. */
/* package */ interface DashDownloadTestData {
Uri TEST_MPD_URI = Uri.parse("test.mpd");
byte[] TEST_MPD =
("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" type=\"static\" "
+ " mediaPresentationDuration=\"PT31S\">\n"
+ " <Period duration=\"PT16S\" >\n"
+ " <AdaptationSet>\n"
+ " <SegmentList>\n"
+ " <SegmentTimeline>\n"
+ " <S d=\"5\" />\n"
+ " <S d=\"5\" />\n"
+ " <S d=\"5\" />\n"
+ " </SegmentTimeline>\n"
+ " </SegmentList>\n"
+ " <Representation>\n"
+ " <SegmentList>\n"
// Bounded range data
+ " <Initialization\n"
+ " range=\"0-9\" sourceURL=\"audio_init_data\" />\n"
// Unbounded range data
+ " <SegmentURL media=\"audio_segment_1\" />\n"
+ " <SegmentURL media=\"audio_segment_2\" />\n"
+ " <SegmentURL media=\"audio_segment_3\" />\n"
+ " </SegmentList>\n"
+ " </Representation>\n"
+ " </AdaptationSet>\n"
+ " <AdaptationSet>\n"
// This segment list has a 1 second offset to make sure the progressive download order
+ " <SegmentList>\n"
+ " <SegmentTimeline>\n"
+ " <S t=\"1\" d=\"5\" />\n" // 1s offset
+ " <S d=\"5\" />\n"
+ " <S d=\"5\" />\n"
+ " </SegmentTimeline>\n"
+ " </SegmentList>\n"
+ " <Representation>\n"
+ " <SegmentList>\n"
+ " <SegmentURL media=\"text_segment_1\" />\n"
+ " <SegmentURL media=\"text_segment_2\" />\n"
+ " <SegmentURL media=\"text_segment_3\" />\n"
+ " </SegmentList>\n"
+ " </Representation>\n"
+ " </AdaptationSet>\n"
+ " </Period>\n"
+ " <Period>\n"
+ " <SegmentList>\n"
+ " <SegmentTimeline>\n"
+ " <S d=\"5\" />\n"
+ " <S d=\"5\" />\n"
+ " <S d=\"5\" />\n"
+ " </SegmentTimeline>\n"
+ " </SegmentList>\n"
+ " <AdaptationSet>\n"
+ " <Representation>\n"
+ " <SegmentList>\n"
+ " <SegmentURL media=\"period_2_segment_1\" />\n"
+ " <SegmentURL media=\"period_2_segment_2\" />\n"
+ " <SegmentURL media=\"period_2_segment_3\" />\n"
+ " </SegmentList>\n"
+ " </Representation>\n"
+ " </AdaptationSet>\n"
+ " </Period>\n"
+ "</MPD>")
.getBytes(Charset.forName(C.UTF8_NAME));
byte[] TEST_MPD_NO_INDEX =
("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<MPD xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" type=\"dynamic\">\n"
+ " <Period start=\"PT6462826.784S\" >\n"
+ " <AdaptationSet>\n"
+ " <Representation>\n"
+ " <SegmentBase indexRange='0-10'/>\n"
+ " </Representation>\n"
+ " </AdaptationSet>\n"
+ " </Period>\n"
+ "</MPD>")
.getBytes(Charset.forName(C.UTF8_NAME));
}

View File

@ -22,10 +22,10 @@ import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCacheEmp
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedData;
import static com.google.android.exoplayer2.testutil.CacheAsserts.assertDataCached;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.offline.DownloadException;
import com.google.android.exoplayer2.offline.Downloader.ProgressListener;
@ -35,7 +35,6 @@ import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey;
import com.google.android.exoplayer2.testutil.FakeDataSet;
import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.FakeDataSource.Factory;
import com.google.android.exoplayer2.testutil.MockitoUtil;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
@ -44,34 +43,38 @@ import com.google.android.exoplayer2.util.Util;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/**
* Unit tests for {@link DashDownloader}.
*/
public class DashDownloaderTest extends InstrumentationTestCase {
/** Unit tests for {@link DashDownloader}. */
@RunWith(RobolectricTestRunner.class)
public class DashDownloaderTest {
private SimpleCache cache;
private File tempFolder;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
MockitoUtil.setUpMockito(this);
tempFolder = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest");
MockitoAnnotations.initMocks(this);
tempFolder = Util.createTempDirectory(RuntimeEnvironment.application, "ExoPlayerTest");
cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
}
@Override
@After
public void tearDown() throws Exception {
Util.recursiveDelete(tempFolder);
super.tearDown();
}
@Test
public void testGetManifest() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD);
FakeDataSet fakeDataSet = new FakeDataSet().setData(TEST_MPD_URI, TEST_MPD);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
DashManifest manifest = dashDownloader.getManifest();
@ -80,15 +83,17 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testDownloadManifestFailure() throws Exception {
byte[] testMpdFirstPart = Arrays.copyOf(TEST_MPD, 10);
byte[] testMpdSecondPart = Arrays.copyOfRange(TEST_MPD, 10, TEST_MPD.length);
FakeDataSet fakeDataSet = new FakeDataSet()
.newData(TEST_MPD_URI)
.appendReadData(testMpdFirstPart)
.appendReadError(new IOException())
.appendReadData(testMpdSecondPart)
.endData();
FakeDataSet fakeDataSet =
new FakeDataSet()
.newData(TEST_MPD_URI)
.appendReadData(testMpdFirstPart)
.appendReadError(new IOException())
.appendReadData(testMpdSecondPart)
.endData();
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
// fails on the first try
@ -108,13 +113,15 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testDownloadRepresentation() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
@ -123,17 +130,19 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testDownloadRepresentationInSmallParts() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.newData("audio_segment_1")
.appendReadData(TestUtil.buildTestData(10))
.appendReadData(TestUtil.buildTestData(10))
.appendReadData(TestUtil.buildTestData(10))
.endData()
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.newData("audio_segment_1")
.appendReadData(TestUtil.buildTestData(10))
.appendReadData(TestUtil.buildTestData(10))
.appendReadData(TestUtil.buildTestData(10))
.endData()
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
@ -142,16 +151,18 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testDownloadRepresentations() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(
@ -161,19 +172,21 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testDownloadAllRepresentations() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3)
.setRandomData("period_2_segment_1", 1)
.setRandomData("period_2_segment_2", 2)
.setRandomData("period_2_segment_3", 3);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3)
.setRandomData("period_2_segment_1", 1)
.setRandomData("period_2_segment_2", 2)
.setRandomData("period_2_segment_3", 3);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
// dashDownloader.selectRepresentations() isn't called
@ -190,21 +203,23 @@ public class DashDownloaderTest extends InstrumentationTestCase {
dashDownloader.remove();
}
@Test
public void testProgressiveDownload() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3);
FakeDataSource fakeDataSource = new FakeDataSource(fakeDataSet);
Factory factory = mock(Factory.class);
when(factory.createDataSource()).thenReturn(fakeDataSource);
DashDownloader dashDownloader = new DashDownloader(TEST_MPD_URI,
new DownloaderConstructorHelper(cache, factory));
DashDownloader dashDownloader =
new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory));
dashDownloader.selectRepresentations(
new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0)});
@ -222,21 +237,23 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertThat(openedDataSpecs[7].uri.getPath()).isEqualTo("text_segment_3");
}
@Test
public void testProgressiveDownloadSeparatePeriods() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("period_2_segment_1", 1)
.setRandomData("period_2_segment_2", 2)
.setRandomData("period_2_segment_3", 3);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("period_2_segment_1", 1)
.setRandomData("period_2_segment_2", 2)
.setRandomData("period_2_segment_3", 3);
FakeDataSource fakeDataSource = new FakeDataSource(fakeDataSet);
Factory factory = mock(Factory.class);
when(factory.createDataSource()).thenReturn(fakeDataSource);
DashDownloader dashDownloader = new DashDownloader(TEST_MPD_URI,
new DownloaderConstructorHelper(cache, factory));
DashDownloader dashDownloader =
new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory));
dashDownloader.selectRepresentations(
new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(1, 0, 0)});
@ -254,17 +271,19 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertThat(openedDataSpecs[7].uri.getPath()).isEqualTo("period_2_segment_3");
}
@Test
public void testDownloadRepresentationFailure() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.newData("audio_segment_2")
.appendReadData(TestUtil.buildTestData(2))
.appendReadError(new IOException())
.appendReadData(TestUtil.buildTestData(3))
.endData()
.setRandomData("audio_segment_3", 6);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.newData("audio_segment_2")
.appendReadData(TestUtil.buildTestData(2))
.appendReadError(new IOException())
.appendReadData(TestUtil.buildTestData(3))
.endData()
.setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
@ -280,17 +299,19 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testCounters() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.newData("audio_segment_2")
.appendReadData(TestUtil.buildTestData(2))
.appendReadError(new IOException())
.appendReadData(TestUtil.buildTestData(3))
.endData()
.setRandomData("audio_segment_3", 6);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.newData("audio_segment_2")
.appendReadData(TestUtil.buildTestData(2))
.appendReadError(new IOException())
.appendReadData(TestUtil.buildTestData(3))
.endData()
.setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
assertCounters(dashDownloader, C.LENGTH_UNSET, C.LENGTH_UNSET, C.LENGTH_UNSET);
@ -314,13 +335,15 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertCounters(dashDownloader, 4, 4, 10 + 4 + 5 + 6);
}
@Test
public void testListener() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
@ -335,16 +358,18 @@ public class DashDownloaderTest extends InstrumentationTestCase {
inOrder.verifyNoMoreInteractions();
}
@Test
public void testRemoveAll() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(
new RepresentationKey[] {new RepresentationKey(0, 0, 0), new RepresentationKey(0, 1, 0)});
@ -355,10 +380,12 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertCacheEmpty(cache);
}
@Test
public void testRepresentationWithoutIndex() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD_NO_INDEX)
.setRandomData("test_segment_1", 4);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD_NO_INDEX)
.setRandomData("test_segment_1", 4);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(new RepresentationKey[] {new RepresentationKey(0, 0, 0)});
@ -374,13 +401,15 @@ public class DashDownloaderTest extends InstrumentationTestCase {
assertCacheEmpty(cache);
}
@Test
public void testSelectRepresentationsClearsPreviousSelection() throws Exception {
FakeDataSet fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6);
FakeDataSet fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6);
DashDownloader dashDownloader = getDashDownloader(fakeDataSet);
dashDownloader.selectRepresentations(
@ -396,11 +425,13 @@ public class DashDownloaderTest extends InstrumentationTestCase {
return new DashDownloader(TEST_MPD_URI, new DownloaderConstructorHelper(cache, factory));
}
private static void assertCounters(DashDownloader dashDownloader, int totalSegments,
int downloadedSegments, int downloadedBytes) {
private static void assertCounters(
DashDownloader dashDownloader,
int totalSegments,
int downloadedSegments,
int downloadedBytes) {
assertThat(dashDownloader.getTotalSegments()).isEqualTo(totalSegments);
assertThat(dashDownloader.getDownloadedSegments()).isEqualTo(downloadedSegments);
assertThat(dashDownloader.getDownloadedBytes()).isEqualTo(downloadedBytes);
}
}

View File

@ -23,26 +23,33 @@ import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
import android.os.ConditionVariable;
import android.test.InstrumentationTestCase;
import android.test.UiThreadTest;
import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey;
import com.google.android.exoplayer2.testutil.DummyMainThread;
import com.google.android.exoplayer2.testutil.FakeDataSet;
import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.MockitoUtil;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.DataSource.Factory;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
/**
* Tests {@link DownloadManager}.
*/
public class DownloadManagerDashTest extends InstrumentationTestCase {
/** Tests {@link DownloadManager}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class})
public class DownloadManagerDashTest {
private static final int ASSERT_TRUE_TIMEOUT = 1000;
@ -56,26 +63,25 @@ public class DownloadManagerDashTest extends InstrumentationTestCase {
private File actionFile;
private DummyMainThread dummyMainThread;
@UiThreadTest
@Override
@Before
public void setUp() throws Exception {
super.setUp();
dummyMainThread = new DummyMainThread();
Context context = getInstrumentation().getContext();
Context context = RuntimeEnvironment.application;
tempFolder = Util.createTempDirectory(context, "ExoPlayerTest");
File cacheFolder = new File(tempFolder, "cache");
cacheFolder.mkdir();
cache = new SimpleCache(cacheFolder, new NoOpCacheEvictor());
MockitoUtil.setUpMockito(this);
fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3);
MockitoAnnotations.initMocks(this);
fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.setRandomData("audio_init_data", 10)
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3);
fakeRepresentationKey1 = new RepresentationKey(0, 0, 0);
fakeRepresentationKey2 = new RepresentationKey(0, 1, 0);
@ -83,33 +89,35 @@ public class DownloadManagerDashTest extends InstrumentationTestCase {
createDownloadManager();
}
@UiThreadTest
@Override
@After
public void tearDown() throws Exception {
downloadManager.release();
Util.recursiveDelete(tempFolder);
dummyMainThread.release();
super.tearDown();
}
// Disabled due to flakiness.
public void disabledTestSaveAndLoadActionFile() throws Throwable {
@Ignore
@Test
public void testSaveAndLoadActionFile() throws Throwable {
// Configure fakeDataSet to block until interrupted when TEST_MPD is read.
fakeDataSet.newData(TEST_MPD_URI)
.appendReadAction(new Runnable() {
@SuppressWarnings("InfiniteLoopStatement")
@Override
public void run() {
try {
// Wait until interrupted.
while (true) {
Thread.sleep(100000);
fakeDataSet
.newData(TEST_MPD_URI)
.appendReadAction(
new Runnable() {
@SuppressWarnings("InfiniteLoopStatement")
@Override
public void run() {
try {
// Wait until interrupted.
while (true) {
Thread.sleep(100000);
}
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
}
})
})
.appendReadData(TEST_MPD)
.endData();
@ -122,15 +130,20 @@ public class DownloadManagerDashTest extends InstrumentationTestCase {
// Setup an Action and immediately release the DM.
handleDownloadAction(fakeRepresentationKey1, fakeRepresentationKey2);
downloadManager.release();
}
});
assertThat(actionFile.exists()).isTrue();
assertThat(actionFile.length()).isGreaterThan(0L);
assertThat(actionFile.exists()).isTrue();
assertThat(actionFile.length()).isGreaterThan(0L);
assertCacheEmpty(cache);
assertCacheEmpty(cache);
// Revert fakeDataSet to normal.
fakeDataSet.setData(TEST_MPD_URI, TEST_MPD);
// Revert fakeDataSet to normal.
fakeDataSet.setData(TEST_MPD_URI, TEST_MPD);
dummyMainThread.runOnMainThread(
new Runnable() {
@Override
public void run() {
createDownloadManager();
}
});
@ -140,12 +153,14 @@ public class DownloadManagerDashTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testHandleDownloadAction() throws Throwable {
handleDownloadAction(fakeRepresentationKey1, fakeRepresentationKey2);
blockUntilTasksCompleteAndThrowAnyDownloadError();
assertCachedData(cache, fakeDataSet);
}
@Test
public void testHandleMultipleDownloadAction() throws Throwable {
handleDownloadAction(fakeRepresentationKey1);
handleDownloadAction(fakeRepresentationKey2);
@ -153,6 +168,7 @@ public class DownloadManagerDashTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testHandleInterferingDownloadAction() throws Throwable {
fakeDataSet
.newData("audio_segment_2")
@ -172,6 +188,7 @@ public class DownloadManagerDashTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testHandleRemoveAction() throws Throwable {
handleDownloadAction(fakeRepresentationKey1);
@ -185,7 +202,9 @@ public class DownloadManagerDashTest extends InstrumentationTestCase {
}
// Disabled due to flakiness.
public void disabledTestHandleRemoveActionBeforeDownloadFinish() throws Throwable {
@Ignore
@Test
public void testHandleRemoveActionBeforeDownloadFinish() throws Throwable {
handleDownloadAction(fakeRepresentationKey1);
handleRemoveAction();
@ -194,15 +213,18 @@ public class DownloadManagerDashTest extends InstrumentationTestCase {
assertCacheEmpty(cache);
}
@Test
public void testHandleInterferingRemoveAction() throws Throwable {
final ConditionVariable downloadInProgressCondition = new ConditionVariable();
fakeDataSet.newData("audio_segment_2")
.appendReadAction(new Runnable() {
@Override
public void run() {
downloadInProgressCondition.open();
}
})
fakeDataSet
.newData("audio_segment_2")
.appendReadAction(
new Runnable() {
@Override
public void run() {
downloadInProgressCondition.open();
}
})
.appendReadData(TestUtil.buildTestData(5))
.endData();
@ -250,5 +272,4 @@ public class DownloadManagerDashTest extends InstrumentationTestCase {
}
});
}
}

View File

@ -22,7 +22,6 @@ import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedDa
import android.content.Context;
import android.content.Intent;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadService;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
@ -32,6 +31,7 @@ import com.google.android.exoplayer2.source.dash.manifest.RepresentationKey;
import com.google.android.exoplayer2.testutil.DummyMainThread;
import com.google.android.exoplayer2.testutil.FakeDataSet;
import com.google.android.exoplayer2.testutil.FakeDataSource;
import com.google.android.exoplayer2.testutil.RobolectricUtil;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
@ -40,11 +40,18 @@ import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import java.io.IOException;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
/**
* Unit tests for {@link DownloadService}.
*/
public class DownloadServiceDashTest extends InstrumentationTestCase {
/** Unit tests for {@link DownloadService}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class})
public class DownloadServiceDashTest {
private SimpleCache cache;
private File tempFolder;
@ -57,44 +64,44 @@ public class DownloadServiceDashTest extends InstrumentationTestCase {
private TestDownloadListener testDownloadListener;
private DummyMainThread dummyMainThread;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
dummyMainThread = new DummyMainThread();
tempFolder = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest");
context = RuntimeEnvironment.application;
tempFolder = Util.createTempDirectory(context, "ExoPlayerTest");
cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
Runnable pauseAction = new Runnable() {
@Override
public void run() {
if (pauseDownloadCondition != null) {
try {
pauseDownloadCondition.block();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Runnable pauseAction =
new Runnable() {
@Override
public void run() {
if (pauseDownloadCondition != null) {
try {
pauseDownloadCondition.block();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}
};
fakeDataSet = new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.newData("audio_init_data")
.appendReadAction(pauseAction)
.appendReadData(TestUtil.buildTestData(10))
.endData()
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3);
};
fakeDataSet =
new FakeDataSet()
.setData(TEST_MPD_URI, TEST_MPD)
.newData("audio_init_data")
.appendReadAction(pauseAction)
.appendReadData(TestUtil.buildTestData(10))
.endData()
.setRandomData("audio_segment_1", 4)
.setRandomData("audio_segment_2", 5)
.setRandomData("audio_segment_3", 6)
.setRandomData("text_segment_1", 1)
.setRandomData("text_segment_2", 2)
.setRandomData("text_segment_3", 3);
final DataSource.Factory fakeDataSourceFactory =
new FakeDataSource.Factory(null).setFakeDataSet(fakeDataSet);
fakeRepresentationKey1 = new RepresentationKey(0, 0, 0);
fakeRepresentationKey2 = new RepresentationKey(0, 1, 0);
context = getInstrumentation().getContext();
try {
dummyMainThread.runOnMainThread(
new Runnable() {
@ -128,7 +135,7 @@ public class DownloadServiceDashTest extends InstrumentationTestCase {
@Override
protected String getNotificationChannelId() {
return null;
return "";
}
@Override
@ -149,7 +156,7 @@ public class DownloadServiceDashTest extends InstrumentationTestCase {
}
}
@Override
@After
public void tearDown() throws Exception {
try {
dummyMainThread.runOnMainThread(
@ -164,9 +171,9 @@ public class DownloadServiceDashTest extends InstrumentationTestCase {
}
Util.recursiveDelete(tempFolder);
dummyMainThread.release();
super.tearDown();
}
@Test
public void testMultipleDownloadAction() throws Throwable {
downloadKeys(fakeRepresentationKey1);
downloadKeys(fakeRepresentationKey2);
@ -176,6 +183,7 @@ public class DownloadServiceDashTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testRemoveAction() throws Throwable {
downloadKeys(fakeRepresentationKey1, fakeRepresentationKey2);
@ -188,6 +196,7 @@ public class DownloadServiceDashTest extends InstrumentationTestCase {
assertCacheEmpty(cache);
}
@Test
public void testRemoveBeforeDownloadComplete() throws Throwable {
pauseDownloadCondition = new ConditionVariable();
downloadKeys(fakeRepresentationKey1, fakeRepresentationKey2);
@ -219,5 +228,4 @@ public class DownloadServiceDashTest extends InstrumentationTestCase {
}
});
}
}

View File

@ -21,6 +21,8 @@ import com.google.android.exoplayer2.offline.DownloadManager;
import com.google.android.exoplayer2.offline.DownloadManager.DownloadListener;
import com.google.android.exoplayer2.offline.DownloadManager.DownloadState;
import com.google.android.exoplayer2.testutil.DummyMainThread;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/** A {@link DownloadListener} for testing. */
/*package*/ final class TestDownloadListener implements DownloadListener {
@ -29,13 +31,12 @@ import com.google.android.exoplayer2.testutil.DummyMainThread;
private final DownloadManager downloadManager;
private final DummyMainThread dummyMainThread;
private final android.os.ConditionVariable downloadFinishedCondition;
private CountDownLatch downloadFinishedCondition;
private Throwable downloadError;
public TestDownloadListener(DownloadManager downloadManager, DummyMainThread dummyMainThread) {
this.downloadManager = downloadManager;
this.dummyMainThread = dummyMainThread;
this.downloadFinishedCondition = new android.os.ConditionVariable();
}
@Override
@ -46,8 +47,10 @@ import com.google.android.exoplayer2.testutil.DummyMainThread;
}
@Override
public void onIdle(DownloadManager downloadManager) {
downloadFinishedCondition.open();
public synchronized void onIdle(DownloadManager downloadManager) {
if (downloadFinishedCondition != null) {
downloadFinishedCondition.countDown();
}
}
/**
@ -55,18 +58,19 @@ import com.google.android.exoplayer2.testutil.DummyMainThread;
* error.
*/
public void blockUntilTasksCompleteAndThrowAnyDownloadError() throws Throwable {
synchronized (this) {
downloadFinishedCondition = new CountDownLatch(1);
}
dummyMainThread.runOnMainThread(
new Runnable() {
@Override
public void run() {
if (downloadManager.isIdle()) {
downloadFinishedCondition.open();
} else {
downloadFinishedCondition.close();
downloadFinishedCondition.countDown();
}
}
});
assertThat(downloadFinishedCondition.block(TIMEOUT)).isTrue();
assertThat(downloadFinishedCondition.await(TIMEOUT, TimeUnit.MILLISECONDS)).isTrue();
if (downloadError != null) {
throw new Exception(downloadError);
}

View File

@ -0,0 +1 @@
manifest=src/test/AndroidManifest.xml

View File

@ -35,10 +35,7 @@ android {
dependencies {
compile project(modulePrefix + 'library-core')
compile 'com.android.support:support-annotations:' + supportLibraryVersion
androidTestCompile project(modulePrefix + 'testutils')
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion
testCompile project(modulePrefix + 'testutils-robolectric')
}
ext {

View File

@ -1,77 +0,0 @@
/*
* Copyright (C) 2017 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.source.hls.offline;
/**
* Data for HLS downloading tests.
*/
/* package */ interface HlsDownloadTestData {
String MASTER_PLAYLIST_URI = "test.m3u8";
String MEDIA_PLAYLIST_0_DIR = "gear0/";
String MEDIA_PLAYLIST_0_URI = MEDIA_PLAYLIST_0_DIR + "prog_index.m3u8";
String MEDIA_PLAYLIST_1_DIR = "gear1/";
String MEDIA_PLAYLIST_1_URI = MEDIA_PLAYLIST_1_DIR + "prog_index.m3u8";
String MEDIA_PLAYLIST_2_DIR = "gear2/";
String MEDIA_PLAYLIST_2_URI = MEDIA_PLAYLIST_2_DIR + "prog_index.m3u8";
String MEDIA_PLAYLIST_3_DIR = "gear3/";
String MEDIA_PLAYLIST_3_URI = MEDIA_PLAYLIST_3_DIR + "prog_index.m3u8";
byte[] MASTER_PLAYLIST_DATA =
("#EXTM3U\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=232370,CODECS=\"mp4a.40.2, avc1.4d4015\"\n"
+ MEDIA_PLAYLIST_1_URI + "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=649879,CODECS=\"mp4a.40.2, avc1.4d401e\"\n"
+ MEDIA_PLAYLIST_2_URI + "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=991714,CODECS=\"mp4a.40.2, avc1.4d401e\"\n"
+ MEDIA_PLAYLIST_3_URI + "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=41457,CODECS=\"mp4a.40.2\"\n"
+ MEDIA_PLAYLIST_0_URI).getBytes();
byte[] MEDIA_PLAYLIST_DATA =
("#EXTM3U\n"
+ "#EXT-X-TARGETDURATION:10\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence0.ts\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence1.ts\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence2.ts\n"
+ "#EXT-X-ENDLIST").getBytes();
String ENC_MEDIA_PLAYLIST_URI = "enc_index.m3u8";
byte[] ENC_MEDIA_PLAYLIST_DATA =
("#EXTM3U\n"
+ "#EXT-X-TARGETDURATION:10\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"enc.key\"\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence0.ts\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence1.ts\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"enc2.key\"\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence2.ts\n"
+ "#EXT-X-ENDLIST").getBytes();
}

View File

@ -18,16 +18,6 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.source.hls.test">
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="27"/>
<application android:debuggable="true"
android:allowBackup="false"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
<uses-library android:name="android.test.runner"/>
</application>
<instrumentation
android:targetPackage="com.google.android.exoplayer2.source.hls.test"
android:name="android.test.InstrumentationTestRunner"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
</manifest>

View File

@ -0,0 +1,83 @@
/*
* Copyright (C) 2017 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.source.hls.offline;
import com.google.android.exoplayer2.C;
import java.nio.charset.Charset;
/** Data for HLS downloading tests. */
/* package */ interface HlsDownloadTestData {
String MASTER_PLAYLIST_URI = "test.m3u8";
String MEDIA_PLAYLIST_0_DIR = "gear0/";
String MEDIA_PLAYLIST_0_URI = MEDIA_PLAYLIST_0_DIR + "prog_index.m3u8";
String MEDIA_PLAYLIST_1_DIR = "gear1/";
String MEDIA_PLAYLIST_1_URI = MEDIA_PLAYLIST_1_DIR + "prog_index.m3u8";
String MEDIA_PLAYLIST_2_DIR = "gear2/";
String MEDIA_PLAYLIST_2_URI = MEDIA_PLAYLIST_2_DIR + "prog_index.m3u8";
String MEDIA_PLAYLIST_3_DIR = "gear3/";
String MEDIA_PLAYLIST_3_URI = MEDIA_PLAYLIST_3_DIR + "prog_index.m3u8";
byte[] MASTER_PLAYLIST_DATA =
("#EXTM3U\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=232370,CODECS=\"mp4a.40.2, avc1.4d4015\"\n"
+ MEDIA_PLAYLIST_1_URI
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=649879,CODECS=\"mp4a.40.2, avc1.4d401e\"\n"
+ MEDIA_PLAYLIST_2_URI
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=991714,CODECS=\"mp4a.40.2, avc1.4d401e\"\n"
+ MEDIA_PLAYLIST_3_URI
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=41457,CODECS=\"mp4a.40.2\"\n"
+ MEDIA_PLAYLIST_0_URI)
.getBytes(Charset.forName(C.UTF8_NAME));
byte[] MEDIA_PLAYLIST_DATA =
("#EXTM3U\n"
+ "#EXT-X-TARGETDURATION:10\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence0.ts\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence1.ts\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence2.ts\n"
+ "#EXT-X-ENDLIST")
.getBytes(Charset.forName(C.UTF8_NAME));
String ENC_MEDIA_PLAYLIST_URI = "enc_index.m3u8";
byte[] ENC_MEDIA_PLAYLIST_DATA =
("#EXTM3U\n"
+ "#EXT-X-TARGETDURATION:10\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"enc.key\"\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence0.ts\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence1.ts\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"enc2.key\"\n"
+ "#EXTINF:9.97667,\n"
+ "fileSequence2.ts\n"
+ "#EXT-X-ENDLIST")
.getBytes(Charset.forName(C.UTF8_NAME));
}

View File

@ -33,7 +33,6 @@ import static com.google.android.exoplayer2.testutil.CacheAsserts.assertCachedDa
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.offline.DownloaderConstructorHelper;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer2.testutil.FakeDataSet;
@ -42,40 +41,47 @@ import com.google.android.exoplayer2.upstream.cache.NoOpCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.android.exoplayer2.util.Util;
import java.io.File;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/** Unit tests for {@link HlsDownloader}. */
public class HlsDownloaderTest extends InstrumentationTestCase {
@RunWith(RobolectricTestRunner.class)
public class HlsDownloaderTest {
private SimpleCache cache;
private File tempFolder;
private FakeDataSet fakeDataSet;
private HlsDownloader hlsDownloader;
@Override
@Before
public void setUp() throws Exception {
super.setUp();
tempFolder = Util.createTempDirectory(getInstrumentation().getContext(), "ExoPlayerTest");
tempFolder = Util.createTempDirectory(RuntimeEnvironment.application, "ExoPlayerTest");
cache = new SimpleCache(tempFolder, new NoOpCacheEvictor());
fakeDataSet = new FakeDataSet()
.setData(MASTER_PLAYLIST_URI, MASTER_PLAYLIST_DATA)
.setData(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_DATA)
.setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts", 10)
.setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts", 11)
.setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence2.ts", 12)
.setData(MEDIA_PLAYLIST_2_URI, MEDIA_PLAYLIST_DATA)
.setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence0.ts", 13)
.setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence1.ts", 14)
.setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence2.ts", 15);
fakeDataSet =
new FakeDataSet()
.setData(MASTER_PLAYLIST_URI, MASTER_PLAYLIST_DATA)
.setData(MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_DATA)
.setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts", 10)
.setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts", 11)
.setRandomData(MEDIA_PLAYLIST_1_DIR + "fileSequence2.ts", 12)
.setData(MEDIA_PLAYLIST_2_URI, MEDIA_PLAYLIST_DATA)
.setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence0.ts", 13)
.setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence1.ts", 14)
.setRandomData(MEDIA_PLAYLIST_2_DIR + "fileSequence2.ts", 15);
hlsDownloader = getHlsDownloader(MASTER_PLAYLIST_URI);
}
@Override
@After
public void tearDown() throws Exception {
Util.recursiveDelete(tempFolder);
super.tearDown();
}
@Test
public void testDownloadManifest() throws Exception {
HlsMasterPlaylist manifest = hlsDownloader.getManifest();
@ -83,17 +89,23 @@ public class HlsDownloaderTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet, MASTER_PLAYLIST_URI);
}
@Test
public void testSelectRepresentationsClearsPreviousSelection() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_2_URI});
hlsDownloader.download(null);
assertCachedData(cache, fakeDataSet, MASTER_PLAYLIST_URI, MEDIA_PLAYLIST_2_URI,
assertCachedData(
cache,
fakeDataSet,
MASTER_PLAYLIST_URI,
MEDIA_PLAYLIST_2_URI,
MEDIA_PLAYLIST_2_DIR + "fileSequence0.ts",
MEDIA_PLAYLIST_2_DIR + "fileSequence1.ts",
MEDIA_PLAYLIST_2_DIR + "fileSequence2.ts");
}
@Test
public void testCounterMethods() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.download(null);
@ -104,12 +116,12 @@ public class HlsDownloaderTest extends InstrumentationTestCase {
.isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12);
}
@Test
public void testInitStatus() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.download(null);
HlsDownloader newHlsDownloader =
getHlsDownloader(MASTER_PLAYLIST_URI);
HlsDownloader newHlsDownloader = getHlsDownloader(MASTER_PLAYLIST_URI);
newHlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
newHlsDownloader.init();
@ -119,16 +131,22 @@ public class HlsDownloaderTest extends InstrumentationTestCase {
.isEqualTo(MEDIA_PLAYLIST_DATA.length + 10 + 11 + 12);
}
@Test
public void testDownloadRepresentation() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.download(null);
assertCachedData(cache, fakeDataSet, MASTER_PLAYLIST_URI, MEDIA_PLAYLIST_1_URI,
assertCachedData(
cache,
fakeDataSet,
MASTER_PLAYLIST_URI,
MEDIA_PLAYLIST_1_URI,
MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts",
MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts",
MEDIA_PLAYLIST_1_DIR + "fileSequence2.ts");
}
@Test
public void testDownloadMultipleRepresentations() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI});
hlsDownloader.download(null);
@ -136,9 +154,11 @@ public class HlsDownloaderTest extends InstrumentationTestCase {
assertCachedData(cache, fakeDataSet);
}
@Test
public void testDownloadAllRepresentations() throws Exception {
// Add data for the rest of the playlists
fakeDataSet.setData(MEDIA_PLAYLIST_0_URI, MEDIA_PLAYLIST_DATA)
fakeDataSet
.setData(MEDIA_PLAYLIST_0_URI, MEDIA_PLAYLIST_DATA)
.setRandomData(MEDIA_PLAYLIST_0_DIR + "fileSequence0.ts", 10)
.setRandomData(MEDIA_PLAYLIST_0_DIR + "fileSequence1.ts", 11)
.setRandomData(MEDIA_PLAYLIST_0_DIR + "fileSequence2.ts", 12)
@ -162,6 +182,7 @@ public class HlsDownloaderTest extends InstrumentationTestCase {
hlsDownloader.remove();
}
@Test
public void testRemoveAll() throws Exception {
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI, MEDIA_PLAYLIST_2_URI});
hlsDownloader.download(null);
@ -170,27 +191,32 @@ public class HlsDownloaderTest extends InstrumentationTestCase {
assertCacheEmpty(cache);
}
@Test
public void testDownloadMediaPlaylist() throws Exception {
hlsDownloader = getHlsDownloader(MEDIA_PLAYLIST_1_URI);
hlsDownloader.selectRepresentations(new String[] {MEDIA_PLAYLIST_1_URI});
hlsDownloader.download(null);
assertCachedData(cache, fakeDataSet, MEDIA_PLAYLIST_1_URI,
assertCachedData(
cache,
fakeDataSet,
MEDIA_PLAYLIST_1_URI,
MEDIA_PLAYLIST_1_DIR + "fileSequence0.ts",
MEDIA_PLAYLIST_1_DIR + "fileSequence1.ts",
MEDIA_PLAYLIST_1_DIR + "fileSequence2.ts");
}
@Test
public void testDownloadEncMediaPlaylist() throws Exception {
fakeDataSet = new FakeDataSet()
.setData(ENC_MEDIA_PLAYLIST_URI, ENC_MEDIA_PLAYLIST_DATA)
.setRandomData("enc.key", 8)
.setRandomData("enc2.key", 9)
.setRandomData("fileSequence0.ts", 10)
.setRandomData("fileSequence1.ts", 11)
.setRandomData("fileSequence2.ts", 12);
hlsDownloader =
getHlsDownloader(ENC_MEDIA_PLAYLIST_URI);
fakeDataSet =
new FakeDataSet()
.setData(ENC_MEDIA_PLAYLIST_URI, ENC_MEDIA_PLAYLIST_DATA)
.setRandomData("enc.key", 8)
.setRandomData("enc2.key", 9)
.setRandomData("fileSequence0.ts", 10)
.setRandomData("fileSequence1.ts", 11)
.setRandomData("fileSequence2.ts", 12);
hlsDownloader = getHlsDownloader(ENC_MEDIA_PLAYLIST_URI);
hlsDownloader.selectRepresentations(new String[] {ENC_MEDIA_PLAYLIST_URI});
hlsDownloader.download(null);
@ -199,8 +225,7 @@ public class HlsDownloaderTest extends InstrumentationTestCase {
private HlsDownloader getHlsDownloader(String mediaPlaylistUri) {
Factory factory = new Factory(null).setFakeDataSet(fakeDataSet);
return new HlsDownloader(Uri.parse(mediaPlaylistUri),
new DownloaderConstructorHelper(cache, factory));
return new HlsDownloader(
Uri.parse(mediaPlaylistUri), new DownloaderConstructorHelper(cache, factory));
}
}

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.source.hls.playlist;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.net.Uri;
import com.google.android.exoplayer2.C;
@ -26,70 +27,85 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Test for {@link HlsMasterPlaylistParserTest}.
*/
public class HlsMasterPlaylistParserTest extends TestCase {
/** Test for {@link HlsMasterPlaylistParserTest}. */
@RunWith(RobolectricTestRunner.class)
public class HlsMasterPlaylistParserTest {
private static final String PLAYLIST_URI = "https://example.com/test.m3u8";
private static final String PLAYLIST_SIMPLE = " #EXTM3U \n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
+ "http://example.com/spaces_in_codecs.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=2560000,FRAME-RATE=25,RESOLUTION=384x160\n"
+ "http://example.com/mid.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=7680000,FRAME-RATE=29.997\n"
+ "http://example.com/hi.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n"
+ "http://example.com/audio-only.m3u8";
private static final String PLAYLIST_SIMPLE =
" #EXTM3U \n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
+ "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
+ "http://example.com/spaces_in_codecs.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=2560000,FRAME-RATE=25,RESOLUTION=384x160\n"
+ "http://example.com/mid.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=7680000,FRAME-RATE=29.997\n"
+ "http://example.com/hi.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=65000,CODECS=\"mp4a.40.5\"\n"
+ "http://example.com/audio-only.m3u8";
private static final String PLAYLIST_WITH_AVG_BANDWIDTH = " #EXTM3U \n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1270000,"
+ "CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
+ "http://example.com/spaces_in_codecs.m3u8\n";
private static final String PLAYLIST_WITH_AVG_BANDWIDTH =
" #EXTM3U \n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
+ "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n"
+ "\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,AVERAGE-BANDWIDTH=1270000,"
+ "CODECS=\"mp4a.40.2 , avc1.66.30 \"\n"
+ "http://example.com/spaces_in_codecs.m3u8\n";
private static final String PLAYLIST_WITH_INVALID_HEADER = "#EXTMU3\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n";
private static final String PLAYLIST_WITH_INVALID_HEADER =
"#EXTMU3\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
+ "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n";
private static final String PLAYLIST_WITH_CC = " #EXTM3U \n"
+ "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n";
private static final String PLAYLIST_WITH_CC =
" #EXTM3U \n"
+ "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,"
+ "LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
+ "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n";
private static final String PLAYLIST_WITHOUT_CC = " #EXTM3U \n"
+ "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128,"
+ "CLOSED-CAPTIONS=NONE\n"
+ "http://example.com/low.m3u8\n";
private static final String PLAYLIST_WITHOUT_CC =
" #EXTM3U \n"
+ "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,"
+ "LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
+ "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128,"
+ "CLOSED-CAPTIONS=NONE\n"
+ "http://example.com/low.m3u8\n";
private static final String PLAYLIST_WITH_AUDIO_MEDIA_TAG = "#EXTM3U\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=2227464,CODECS=\"avc1.640020,mp4a.40.2\",AUDIO=\"aud1\"\n"
+ "uri1.m3u8\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=8178040,CODECS=\"avc1.64002a,mp4a.40.2\",AUDIO=\"aud1\"\n"
+ "uri2.m3u8\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=2448841,CODECS=\"avc1.640020,ac-3\",AUDIO=\"aud2\"\n"
+ "uri1.m3u8\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=8399417,CODECS=\"avc1.64002a,ac-3\",AUDIO=\"aud2\"\n"
+ "uri2.m3u8\n"
+ "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",LANGUAGE=\"en\",NAME=\"English\","
+ "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"2\",URI=\"a1/prog_index.m3u8\"\n"
+ "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud2\",LANGUAGE=\"en\",NAME=\"English\","
+ "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"6\",URI=\"a2/prog_index.m3u8\"\n";
private static final String PLAYLIST_WITH_AUDIO_MEDIA_TAG =
"#EXTM3U\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=2227464,CODECS=\"avc1.640020,mp4a.40.2\",AUDIO=\"aud1\"\n"
+ "uri1.m3u8\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=8178040,CODECS=\"avc1.64002a,mp4a.40.2\",AUDIO=\"aud1\"\n"
+ "uri2.m3u8\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=2448841,CODECS=\"avc1.640020,ac-3\",AUDIO=\"aud2\"\n"
+ "uri1.m3u8\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=8399417,CODECS=\"avc1.64002a,ac-3\",AUDIO=\"aud2\"\n"
+ "uri2.m3u8\n"
+ "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud1\",LANGUAGE=\"en\",NAME=\"English\","
+ "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"2\",URI=\"a1/prog_index.m3u8\"\n"
+ "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"aud2\",LANGUAGE=\"en\",NAME=\"English\","
+ "AUTOSELECT=YES,DEFAULT=YES,CHANNELS=\"6\",URI=\"a2/prog_index.m3u8\"\n";
@Test
public void testParseMasterPlaylist() throws IOException {
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_SIMPLE);
@ -129,9 +145,10 @@ public class HlsMasterPlaylistParserTest extends TestCase {
assertThat(variants.get(4).url).isEqualTo("http://example.com/audio-only.m3u8");
}
@Test
public void testMasterPlaylistWithBandwdithAverage() throws IOException {
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI,
PLAYLIST_WITH_AVG_BANDWIDTH);
HlsMasterPlaylist masterPlaylist =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AVG_BANDWIDTH);
List<HlsMasterPlaylist.HlsUrl> variants = masterPlaylist.variants;
@ -139,6 +156,7 @@ public class HlsMasterPlaylistParserTest extends TestCase {
assertThat(variants.get(1).format.bitrate).isEqualTo(1270000);
}
@Test
public void testPlaylistWithInvalidHeader() throws IOException {
try {
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_INVALID_HEADER);
@ -148,6 +166,7 @@ public class HlsMasterPlaylistParserTest extends TestCase {
}
}
@Test
public void testPlaylistWithClosedCaption() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CC);
assertThat(playlist.muxedCaptionFormats).hasSize(1);
@ -157,11 +176,13 @@ public class HlsMasterPlaylistParserTest extends TestCase {
assertThat(closedCaptionFormat.language).isEqualTo("es");
}
@Test
public void testPlaylistWithoutClosedCaptions() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITHOUT_CC);
assertThat(playlist.muxedCaptionFormats).isEmpty();
}
@Test
public void testCodecPropagation() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_AUDIO_MEDIA_TAG);
@ -177,9 +198,8 @@ public class HlsMasterPlaylistParserTest extends TestCase {
private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString)
throws IOException {
Uri playlistUri = Uri.parse(uri);
ByteArrayInputStream inputStream = new ByteArrayInputStream(
playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
ByteArrayInputStream inputStream =
new ByteArrayInputStream(playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
return (HlsMasterPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
}
}

View File

@ -26,49 +26,53 @@ import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Locale;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Test for {@link HlsMediaPlaylistParserTest}.
*/
public class HlsMediaPlaylistParserTest extends TestCase {
/** Test for {@link HlsMediaPlaylistParserTest}. */
@RunWith(RobolectricTestRunner.class)
public class HlsMediaPlaylistParserTest {
public void testParseMediaPlaylist() throws IOException {
@Test
public void testParseMediaPlaylist() throws Exception {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString = "#EXTM3U\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXT-X-START:TIME-OFFSET=-25"
+ "#EXT-X-TARGETDURATION:8\n"
+ "#EXT-X-MEDIA-SEQUENCE:2679\n"
+ "#EXT-X-DISCONTINUITY-SEQUENCE:4\n"
+ "#EXT-X-ALLOW-CACHE:YES\n"
+ "\n"
+ "#EXTINF:7.975,\n"
+ "#EXT-X-BYTERANGE:51370@0\n"
+ "https://priv.example.com/fileSequence2679.ts\n"
+ "\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2680\",IV=0x1566B\n"
+ "#EXTINF:7.975,\n"
+ "#EXT-X-BYTERANGE:51501@2147483648\n"
+ "https://priv.example.com/fileSequence2680.ts\n"
+ "\n"
+ "#EXT-X-KEY:METHOD=NONE\n"
+ "#EXTINF:7.941,\n"
+ "#EXT-X-BYTERANGE:51501\n" // @2147535149
+ "https://priv.example.com/fileSequence2681.ts\n"
+ "\n"
+ "#EXT-X-DISCONTINUITY\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2682\"\n"
+ "#EXTINF:7.975,\n"
+ "#EXT-X-BYTERANGE:51740\n" // @2147586650
+ "https://priv.example.com/fileSequence2682.ts\n"
+ "\n"
+ "#EXTINF:7.975,\n"
+ "https://priv.example.com/fileSequence2683.ts\n"
+ "#EXT-X-ENDLIST";
InputStream inputStream = new ByteArrayInputStream(
playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
String playlistString =
"#EXTM3U\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXT-X-START:TIME-OFFSET=-25"
+ "#EXT-X-TARGETDURATION:8\n"
+ "#EXT-X-MEDIA-SEQUENCE:2679\n"
+ "#EXT-X-DISCONTINUITY-SEQUENCE:4\n"
+ "#EXT-X-ALLOW-CACHE:YES\n"
+ "\n"
+ "#EXTINF:7.975,\n"
+ "#EXT-X-BYTERANGE:51370@0\n"
+ "https://priv.example.com/fileSequence2679.ts\n"
+ "\n"
+ "#EXT-X-KEY:METHOD=AES-128,"
+ "URI=\"https://priv.example.com/key.php?r=2680\",IV=0x1566B\n"
+ "#EXTINF:7.975,\n"
+ "#EXT-X-BYTERANGE:51501@2147483648\n"
+ "https://priv.example.com/fileSequence2680.ts\n"
+ "\n"
+ "#EXT-X-KEY:METHOD=NONE\n"
+ "#EXTINF:7.941,\n"
+ "#EXT-X-BYTERANGE:51501\n" // @2147535149
+ "https://priv.example.com/fileSequence2681.ts\n"
+ "\n"
+ "#EXT-X-DISCONTINUITY\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://priv.example.com/key.php?r=2682\"\n"
+ "#EXTINF:7.975,\n"
+ "#EXT-X-BYTERANGE:51740\n" // @2147586650
+ "https://priv.example.com/fileSequence2682.ts\n"
+ "\n"
+ "#EXTINF:7.975,\n"
+ "https://priv.example.com/fileSequence2683.ts\n"
+ "#EXT-X-ENDLIST";
InputStream inputStream =
new ByteArrayInputStream(playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
@ -136,6 +140,7 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts");
}
@Test
public void testGapTag() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test2.m3u8");
String playlistString =
@ -170,5 +175,4 @@ public class HlsMediaPlaylistParserTest extends TestCase {
assertThat(playlist.segments.get(2).hasGapTag).isTrue();
assertThat(playlist.segments.get(3).hasGapTag).isFalse();
}
}

View File

@ -0,0 +1 @@
manifest=src/test/AndroidManifest.xml

View File

@ -35,10 +35,7 @@ android {
dependencies {
compile project(modulePrefix + 'library-core')
compile 'com.android.support:support-annotations:' + supportLibraryVersion
androidTestCompile project(modulePrefix + 'testutils')
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion
testCompile project(modulePrefix + 'testutils-robolectric')
}
ext {

View File

@ -18,16 +18,6 @@
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.source.smoothstreaming.test">
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="27"/>
<application android:debuggable="true"
android:allowBackup="false"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
<uses-library android:name="android.test.runner"/>
</application>
<instrumentation
android:targetPackage="com.google.android.exoplayer2.source.smoothstreaming.test"
android:name="android.test.InstrumentationTestRunner"/>
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
</manifest>

View File

@ -16,27 +16,29 @@
package com.google.android.exoplayer2.source.smoothstreaming.manifest;
import android.net.Uri;
import android.test.InstrumentationTestCase;
import com.google.android.exoplayer2.testutil.TestUtil;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
/**
* Unit tests for {@link SsManifestParser}.
*/
public final class SsManifestParserTest extends InstrumentationTestCase {
/** Unit tests for {@link SsManifestParser}. */
@RunWith(RobolectricTestRunner.class)
public final class SsManifestParserTest {
private static final String SAMPLE_ISMC_1 = "sample_ismc_1";
private static final String SAMPLE_ISMC_2 = "sample_ismc_2";
/**
* Simple test to ensure the sample manifests parse without any exceptions being thrown.
*/
/** Simple test to ensure the sample manifests parse without any exceptions being thrown. */
@Test
public void testParseSmoothStreamingManifest() throws IOException {
SsManifestParser parser = new SsManifestParser();
parser.parse(Uri.parse("https://example.com/test.ismc"),
TestUtil.getInputStream(getInstrumentation(), SAMPLE_ISMC_1));
parser.parse(Uri.parse("https://example.com/test.ismc"),
TestUtil.getInputStream(getInstrumentation(), SAMPLE_ISMC_2));
parser.parse(
Uri.parse("https://example.com/test.ismc"),
TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_ISMC_1));
parser.parse(
Uri.parse("https://example.com/test.ismc"),
TestUtil.getInputStream(RuntimeEnvironment.application, SAMPLE_ISMC_2));
}
}

View File

@ -26,52 +26,49 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import junit.framework.TestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/**
* Unit tests for {@link SsManifest}.
*/
public class SsManifestTest extends TestCase {
/** Unit tests for {@link SsManifest}. */
@RunWith(RobolectricTestRunner.class)
public class SsManifestTest {
private static final ProtectionElement DUMMY_PROTECTION_ELEMENT =
new ProtectionElement(C.WIDEVINE_UUID, new byte[] {0, 1, 2});
@Test
public void testCopy() throws Exception {
Format[][] formats = newFormats(2, 3);
SsManifest sourceManifest = newSsManifest(
newStreamElement("1",formats[0]),
newStreamElement("2", formats[1]));
SsManifest sourceManifest =
newSsManifest(newStreamElement("1", formats[0]), newStreamElement("2", formats[1]));
List<TrackKey> keys = Arrays.asList(
new TrackKey(0, 0),
new TrackKey(0, 2),
new TrackKey(1, 0));
List<TrackKey> keys = Arrays.asList(new TrackKey(0, 0), new TrackKey(0, 2), new TrackKey(1, 0));
// Keys don't need to be in any particular order
Collections.shuffle(keys, new Random(0));
SsManifest copyManifest = sourceManifest.copy(keys);
SsManifest expectedManifest = newSsManifest(
newStreamElement("1", formats[0][0], formats[0][2]),
newStreamElement("2", formats[1][0]));
SsManifest expectedManifest =
newSsManifest(
newStreamElement("1", formats[0][0], formats[0][2]),
newStreamElement("2", formats[1][0]));
assertManifestEquals(expectedManifest, copyManifest);
}
@Test
public void testCopyRemoveStreamElement() throws Exception {
Format[][] formats = newFormats(2, 3);
SsManifest sourceManifest = newSsManifest(
newStreamElement("1", formats[0]),
newStreamElement("2", formats[1]));
SsManifest sourceManifest =
newSsManifest(newStreamElement("1", formats[0]), newStreamElement("2", formats[1]));
List<TrackKey> keys = Arrays.asList(
new TrackKey(1, 0));
List<TrackKey> keys = Arrays.asList(new TrackKey(1, 0));
// Keys don't need to be in any particular order
Collections.shuffle(keys, new Random(0));
SsManifest copyManifest = sourceManifest.copy(keys);
SsManifest expectedManifest = newSsManifest(
newStreamElement("2", formats[1][0]));
SsManifest expectedManifest = newSsManifest(newStreamElement("2", formats[1][0]));
assertManifestEquals(expectedManifest, copyManifest);
}
@ -117,13 +114,25 @@ public class SsManifestTest extends TestCase {
}
private static StreamElement newStreamElement(String name, Format... formats) {
return new StreamElement("baseUri", "chunkTemplate", C.TRACK_TYPE_VIDEO, "subType",
1000, name, 1024, 768, 1024, 768, null, formats, Collections.<Long>emptyList(), 0);
return new StreamElement(
"baseUri",
"chunkTemplate",
C.TRACK_TYPE_VIDEO,
"subType",
1000,
name,
1024,
768,
1024,
768,
null,
formats,
Collections.<Long>emptyList(),
0);
}
private static Format newFormat(String id) {
return Format.createContainerFormat(id, MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null,
Format.NO_VALUE, 0, null);
return Format.createContainerFormat(
id, MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, Format.NO_VALUE, 0, null);
}
}

View File

@ -0,0 +1 @@
manifest=src/test/AndroidManifest.xml

View File

@ -35,4 +35,5 @@ dependencies {
compile project(modulePrefix + 'library-core')
compile 'org.mockito:mockito-core:' + mockitoVersion
compile 'com.google.truth:truth:' + truthVersion
testCompile project(modulePrefix + 'testutils-robolectric')
}

View File

@ -1,54 +0,0 @@
/*
* Copyright (C) 2017 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.content.Context;
import android.test.InstrumentationTestCase;
import org.mockito.MockitoAnnotations;
/**
* Utility for setting up Mockito for instrumentation tests.
*/
public final class MockitoUtil {
/**
* Sets up Mockito for an instrumentation test.
*
* @param instrumentationTestCase The instrumentation test case class.
*/
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);
}
/**
* Sets up Mockito for a JUnit4 test.
*
* @param targetContext The target context. Usually obtained from
* {@code InstrumentationRegistry.getTargetContext()}
* @param testClass The JUnit4 test class.
*/
public static void setUpMockito(Context targetContext, Object testClass) {
// Workaround for https://code.google.com/p/dexmaker/issues/detail?id=2.
System.setProperty("dexmaker.dexcache", targetContext.getCacheDir().getPath());
MockitoAnnotations.initMocks(testClass);
}
private MockitoUtil() {}
}

View File

@ -18,7 +18,6 @@ package com.google.android.exoplayer2.testutil;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.app.Instrumentation;
import android.content.Context;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.extractor.Extractor;
@ -132,20 +131,10 @@ public class TestUtil {
return joined;
}
public static byte[] getByteArray(Instrumentation instrumentation, String fileName)
throws IOException {
return getByteArray(instrumentation.getContext(), fileName);
}
public static byte[] getByteArray(Context context, String fileName) throws IOException {
return Util.toByteArray(getInputStream(context, fileName));
}
public static InputStream getInputStream(Instrumentation instrumentation, String fileName)
throws IOException {
return getInputStream(instrumentation.getContext(), fileName);
}
public static InputStream getInputStream(Context context, String fileName) throws IOException {
return context.getResources().getAssets().open(fileName);
}

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2017 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.testutil.test">
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="26"/>
</manifest>

View File

@ -0,0 +1,147 @@
/*
* Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.List;
import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Unit test for {@link FakeAdaptiveDataSet}. */
@RunWith(RobolectricTestRunner.class)
public final class FakeAdaptiveDataSetTest {
private static final Format[] TEST_FORMATS = {
Format.createVideoSampleFormat(
null,
MimeTypes.VIDEO_H264,
null,
1000000,
Format.NO_VALUE,
1280,
720,
Format.NO_VALUE,
null,
null),
Format.createVideoSampleFormat(
null,
MimeTypes.VIDEO_H264,
null,
300000,
Format.NO_VALUE,
640,
360,
Format.NO_VALUE,
null,
null)
};
private static final TrackGroup TRACK_GROUP = new TrackGroup(TEST_FORMATS);
@Test
public void testAdaptiveDataSet() {
long chunkDuration = 2 * C.MICROS_PER_SECOND;
FakeAdaptiveDataSet dataSet =
new FakeAdaptiveDataSet(
TRACK_GROUP, 10 * C.MICROS_PER_SECOND, chunkDuration, 0.0, new Random(0));
assertThat(dataSet.getAllData().size()).isEqualTo(TEST_FORMATS.length);
assertThat(dataSet.getUri(0).equals(dataSet.getUri(1))).isFalse();
assertThat(dataSet.getChunkCount()).isEqualTo(5);
assertThat(dataSet.getChunkIndexByPosition(4 * C.MICROS_PER_SECOND)).isEqualTo(2);
assertThat(dataSet.getChunkIndexByPosition(9 * C.MICROS_PER_SECOND)).isEqualTo(4);
for (int i = 0; i < dataSet.getChunkCount(); i++) {
assertThat(dataSet.getChunkDuration(i)).isEqualTo(chunkDuration);
}
assertChunkData(dataSet, chunkDuration);
}
@Test
public void testAdaptiveDataSetTrailingSmallChunk() {
long chunkDuration = 3 * C.MICROS_PER_SECOND;
FakeAdaptiveDataSet dataSet =
new FakeAdaptiveDataSet(
TRACK_GROUP, 10 * C.MICROS_PER_SECOND, chunkDuration, 0.0, new Random(0));
assertThat(dataSet.getAllData().size()).isEqualTo(TEST_FORMATS.length);
assertThat(dataSet.getUri(0).equals(dataSet.getUri(1))).isFalse();
assertThat(dataSet.getChunkCount()).isEqualTo(4);
assertThat(dataSet.getChunkIndexByPosition(4 * C.MICROS_PER_SECOND)).isEqualTo(1);
assertThat(dataSet.getChunkIndexByPosition(9 * C.MICROS_PER_SECOND)).isEqualTo(3);
for (int i = 0; i < dataSet.getChunkCount() - 1; i++) {
assertThat(dataSet.getChunkDuration(i)).isEqualTo(chunkDuration);
}
assertThat(dataSet.getChunkDuration(3)).isEqualTo(1 * C.MICROS_PER_SECOND);
assertChunkData(dataSet, chunkDuration);
}
@Test
public void testAdaptiveDataSetChunkSizeDistribution() {
double expectedStdDev = 4.0;
FakeAdaptiveDataSet dataSet =
new FakeAdaptiveDataSet(
TRACK_GROUP,
100000 * C.MICROS_PER_SECOND,
1 * C.MICROS_PER_SECOND,
expectedStdDev,
new Random(0));
for (int i = 0; i < TEST_FORMATS.length; i++) {
FakeData data = dataSet.getData(dataSet.getUri(i));
double mean = computeSegmentSizeMean(data.getSegments());
double stddev = computeSegmentSizeStdDev(data.getSegments(), mean);
double relativePercentStdDev = stddev / mean * 100.0;
assertThat(relativePercentStdDev).isWithin(0.02).of(expectedStdDev);
assertThat(mean * 8 / TEST_FORMATS[i].bitrate).isWithin(0.01).of(1.0);
}
}
private void assertChunkData(FakeAdaptiveDataSet dataSet, long chunkDuration) {
for (int i = 0; i < dataSet.getChunkCount(); i++) {
assertThat(dataSet.getStartTime(i)).isEqualTo(chunkDuration * i);
}
for (int s = 0; s < TEST_FORMATS.length; s++) {
FakeData data = dataSet.getData(dataSet.getUri(s));
assertThat(data.getSegments().size()).isEqualTo(dataSet.getChunkCount());
for (int i = 0; i < data.getSegments().size(); i++) {
long expectedLength =
TEST_FORMATS[s].bitrate * dataSet.getChunkDuration(i) / (8 * C.MICROS_PER_SECOND);
assertThat(data.getSegments().get(i).length).isEqualTo(expectedLength);
}
}
}
private static double computeSegmentSizeMean(List<Segment> segments) {
double totalSize = 0.0;
for (Segment segment : segments) {
totalSize += segment.length;
}
return totalSize / segments.size();
}
private static double computeSegmentSizeStdDev(List<Segment> segments, double mean) {
double totalSquaredSize = 0.0;
for (Segment segment : segments) {
totalSquaredSize += (double) segment.length * segment.length;
}
return Math.sqrt(totalSquaredSize / segments.size() - mean * mean);
}
}

View File

@ -0,0 +1,201 @@
/*
* Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat;
import android.os.ConditionVariable;
import android.os.HandlerThread;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
/** Unit test for {@link FakeClock}. */
@RunWith(RobolectricTestRunner.class)
@Config(shadows = {RobolectricUtil.CustomLooper.class, RobolectricUtil.CustomMessageQueue.class})
public final class FakeClockTest {
private static final long TIMEOUT_MS = 10000;
@Test
public void testAdvanceTime() {
FakeClock fakeClock = new FakeClock(2000);
assertThat(fakeClock.elapsedRealtime()).isEqualTo(2000);
fakeClock.advanceTime(500);
assertThat(fakeClock.elapsedRealtime()).isEqualTo(2500);
fakeClock.advanceTime(0);
assertThat(fakeClock.elapsedRealtime()).isEqualTo(2500);
}
@Test
public void testSleep() throws InterruptedException {
FakeClock fakeClock = new FakeClock(0);
SleeperThread sleeperThread = new SleeperThread(fakeClock, 1000);
sleeperThread.start();
assertThat(sleeperThread.waitUntilAsleep(TIMEOUT_MS)).isTrue();
assertThat(sleeperThread.isSleeping()).isTrue();
fakeClock.advanceTime(1000);
sleeperThread.join(TIMEOUT_MS);
assertThat(sleeperThread.isSleeping()).isFalse();
sleeperThread = new SleeperThread(fakeClock, 0);
sleeperThread.start();
sleeperThread.join();
assertThat(sleeperThread.isSleeping()).isFalse();
SleeperThread[] sleeperThreads = new SleeperThread[5];
sleeperThreads[0] = new SleeperThread(fakeClock, 1000);
sleeperThreads[1] = new SleeperThread(fakeClock, 1000);
sleeperThreads[2] = new SleeperThread(fakeClock, 2000);
sleeperThreads[3] = new SleeperThread(fakeClock, 3000);
sleeperThreads[4] = new SleeperThread(fakeClock, 4000);
for (SleeperThread thread : sleeperThreads) {
thread.start();
assertThat(thread.waitUntilAsleep(TIMEOUT_MS)).isTrue();
}
assertSleepingStates(new boolean[] {true, true, true, true, true}, sleeperThreads);
fakeClock.advanceTime(1500);
assertThat(sleeperThreads[0].waitUntilAwake(TIMEOUT_MS)).isTrue();
assertThat(sleeperThreads[1].waitUntilAwake(TIMEOUT_MS)).isTrue();
assertSleepingStates(new boolean[] {false, false, true, true, true}, sleeperThreads);
fakeClock.advanceTime(2000);
assertThat(sleeperThreads[2].waitUntilAwake(TIMEOUT_MS)).isTrue();
assertThat(sleeperThreads[3].waitUntilAwake(TIMEOUT_MS)).isTrue();
assertSleepingStates(new boolean[] {false, false, false, false, true}, sleeperThreads);
fakeClock.advanceTime(2000);
for (SleeperThread thread : sleeperThreads) {
thread.join(TIMEOUT_MS);
}
assertSleepingStates(new boolean[] {false, false, false, false, false}, sleeperThreads);
}
@Test
public void testPostDelayed() {
HandlerThread handlerThread = new HandlerThread("FakeClockTest thread");
handlerThread.start();
FakeClock fakeClock = new FakeClock(0);
HandlerWrapper handler =
fakeClock.createHandler(handlerThread.getLooper(), /* callback= */ null);
TestRunnable[] testRunnables = {
new TestRunnable(),
new TestRunnable(),
new TestRunnable(),
new TestRunnable(),
new TestRunnable()
};
handler.postDelayed(testRunnables[0], 0);
handler.postDelayed(testRunnables[1], 100);
handler.postDelayed(testRunnables[2], 200);
waitForHandler(handler);
assertTestRunnableStates(new boolean[] {true, false, false, false, false}, testRunnables);
fakeClock.advanceTime(150);
handler.postDelayed(testRunnables[3], 50);
handler.postDelayed(testRunnables[4], 100);
waitForHandler(handler);
assertTestRunnableStates(new boolean[] {true, true, false, false, false}, testRunnables);
fakeClock.advanceTime(50);
waitForHandler(handler);
assertTestRunnableStates(new boolean[] {true, true, true, true, false}, testRunnables);
fakeClock.advanceTime(1000);
waitForHandler(handler);
assertTestRunnableStates(new boolean[] {true, true, true, true, true}, testRunnables);
}
private static void assertSleepingStates(boolean[] states, SleeperThread[] sleeperThreads) {
for (int i = 0; i < sleeperThreads.length; i++) {
assertThat(sleeperThreads[i].isSleeping()).isEqualTo(states[i]);
}
}
private static void waitForHandler(HandlerWrapper handler) {
final ConditionVariable handlerFinished = new ConditionVariable();
handler.post(
new Runnable() {
@Override
public void run() {
handlerFinished.open();
}
});
handlerFinished.block();
}
private static void assertTestRunnableStates(boolean[] states, TestRunnable[] testRunnables) {
for (int i = 0; i < testRunnables.length; i++) {
assertThat(testRunnables[i].hasRun).isEqualTo(states[i]);
}
}
private static final class SleeperThread extends Thread {
private final Clock clock;
private final long sleepDurationMs;
private final CountDownLatch fallAsleepCountDownLatch;
private final CountDownLatch wakeUpCountDownLatch;
private volatile boolean isSleeping;
public SleeperThread(Clock clock, long sleepDurationMs) {
this.clock = clock;
this.sleepDurationMs = sleepDurationMs;
this.fallAsleepCountDownLatch = new CountDownLatch(1);
this.wakeUpCountDownLatch = new CountDownLatch(1);
}
public boolean waitUntilAsleep(long timeoutMs) throws InterruptedException {
return fallAsleepCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
}
public boolean waitUntilAwake(long timeoutMs) throws InterruptedException {
return wakeUpCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS);
}
public boolean isSleeping() {
return isSleeping;
}
@Override
public void run() {
// This relies on the FakeClock's methods synchronizing on its own monitor to ensure that
// any interactions with it occur only after sleep() has called wait() or returned.
synchronized (clock) {
isSleeping = true;
fallAsleepCountDownLatch.countDown();
clock.sleep(sleepDurationMs);
isSleeping = false;
wakeUpCountDownLatch.countDown();
}
}
}
private static final class TestRunnable implements Runnable {
public boolean hasRun;
@Override
public void run() {
hasRun = true;
}
}
}

View File

@ -0,0 +1,119 @@
/*
* Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import com.google.android.exoplayer2.testutil.FakeDataSet.FakeData.Segment;
import java.io.IOException;
import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Unit test for {@link FakeDataSet} */
@RunWith(RobolectricTestRunner.class)
public final class FakeDataSetTest {
@Test
public void testMultipleDataSets() {
byte[][] testData = new byte[4][];
Uri[] uris = new Uri[3];
for (int i = 0; i < 4; i++) {
testData[i] = TestUtil.buildTestData(10, i);
if (i > 0) {
uris[i - 1] = Uri.parse("test_uri_" + i);
}
}
FakeDataSet fakeDataSet =
new FakeDataSet()
.newDefaultData()
.appendReadData(testData[0])
.endData()
.setData(uris[0], testData[1])
.newData(uris[1])
.appendReadData(testData[2])
.endData()
.setData(uris[2], testData[3]);
assertThat(fakeDataSet.getAllData().size()).isEqualTo(4);
assertThat(fakeDataSet.getData("unseen_uri")).isEqualTo(fakeDataSet.getData((Uri) null));
for (int i = 0; i < 3; i++) {
assertThat(fakeDataSet.getData(uris[i]).uri).isEqualTo(uris[i]);
}
assertThat(fakeDataSet.getData((Uri) null).getData()).isEqualTo(testData[0]);
for (int i = 1; i < 4; i++) {
assertThat(fakeDataSet.getData(uris[i - 1]).getData()).isEqualTo(testData[i]);
}
}
@Test
public void testSegmentTypes() {
byte[] testData = TestUtil.buildTestData(3);
Runnable runnable =
new Runnable() {
@Override
public void run() {
// Do nothing.
}
};
IOException exception = new IOException();
FakeDataSet fakeDataSet =
new FakeDataSet()
.newDefaultData()
.appendReadData(testData)
.appendReadData(testData)
.appendReadData(50)
.appendReadAction(runnable)
.appendReadError(exception)
.endData();
List<Segment> segments = fakeDataSet.getData((Uri) null).getSegments();
assertThat(segments.size()).isEqualTo(5);
assertSegment(segments.get(0), testData, 3, 0, null, null);
assertSegment(segments.get(1), testData, 3, 3, null, null);
assertSegment(segments.get(2), null, 50, 6, null, null);
assertSegment(segments.get(3), null, 0, 56, runnable, null);
assertSegment(segments.get(4), null, 0, 56, null, exception);
byte[] allData = new byte[6];
System.arraycopy(testData, 0, allData, 0, 3);
System.arraycopy(testData, 0, allData, 3, 3);
assertThat(fakeDataSet.getData((Uri) null).getData()).isEqualTo(allData);
}
private static void assertSegment(
Segment segment,
byte[] data,
int length,
long byteOffset,
Runnable runnable,
IOException exception) {
if (data != null) {
assertThat(segment.data).isEqualTo(data);
assertThat(data).hasLength(length);
} else {
assertThat(segment.data).isNull();
}
assertThat(segment.length).isEqualTo(length);
assertThat(segment.byteOffset).isEqualTo(byteOffset);
assertThat(segment.action).isEqualTo(runnable);
assertThat(segment.isActionSegment()).isEqualTo(runnable != null);
assertThat(segment.exception).isEqualTo(exception);
assertThat(segment.isErrorSegment()).isEqualTo(exception != null);
}
}

View File

@ -0,0 +1,251 @@
/*
* Copyright (C) 2017 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 static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail;
import android.net.Uri;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSpec;
import java.io.IOException;
import java.util.Arrays;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
/** Unit test for {@link FakeDataSource}. */
@RunWith(RobolectricTestRunner.class)
public final class FakeDataSourceTest {
private static final String URI_STRING = "test://test.test";
private static final byte[] BUFFER = new byte[500];
private static final byte[] TEST_DATA = TestUtil.buildTestData(15);
private static final byte[] TEST_DATA_PART_1 = Arrays.copyOf(TEST_DATA, 10);
private static final byte[] TEST_DATA_PART_2 = Arrays.copyOfRange(TEST_DATA, 10, 15);
private static Uri uri;
private static FakeDataSet fakeDataSet;
@Before
public void setUp() {
uri = Uri.parse(URI_STRING);
fakeDataSet =
new FakeDataSet()
.newData(uri.toString())
.appendReadData(TEST_DATA_PART_1)
.appendReadData(TEST_DATA_PART_2)
.endData();
}
@Test
public void testReadFull() throws IOException {
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
assertThat(dataSource.open(new DataSpec(uri))).isEqualTo(15);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(10);
assertBuffer(TEST_DATA_PART_1);
assertThat(dataSource.read(BUFFER, 10, BUFFER.length)).isEqualTo(5);
assertBuffer(TEST_DATA);
assertThat(dataSource.read(BUFFER, 15, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
assertBuffer(TEST_DATA);
assertThat(dataSource.read(BUFFER, 20, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
dataSource.close();
}
@Test
public void testReadPartialOpenEnded() throws IOException {
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
assertThat(dataSource.open(new DataSpec(uri, 7, C.LENGTH_UNSET, null))).isEqualTo(8);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(3);
assertBuffer(TEST_DATA_PART_1, 7, 3);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(5);
assertBuffer(TEST_DATA_PART_2);
assertThat(dataSource.read(BUFFER, 15, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
dataSource.close();
}
@Test
public void testReadPartialBounded() throws IOException {
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
assertThat(dataSource.open(new DataSpec(uri, 9, 3, null))).isEqualTo(3);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(1);
assertBuffer(TEST_DATA_PART_1, 9, 1);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(2);
assertBuffer(TEST_DATA_PART_2, 0, 2);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
dataSource.close();
assertThat(dataSource.open(new DataSpec(uri, 11, 4, null))).isEqualTo(4);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(4);
assertBuffer(TEST_DATA_PART_2, 1, 4);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
dataSource.close();
}
@Test
public void testDummyData() throws IOException {
FakeDataSource dataSource =
new FakeDataSource(
new FakeDataSet()
.newData(uri.toString())
.appendReadData(100)
.appendReadData(TEST_DATA)
.appendReadData(200)
.endData());
assertThat(dataSource.open(new DataSpec(uri))).isEqualTo(315);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(100);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15);
assertBuffer(TEST_DATA);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(200);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
dataSource.close();
}
@Test
public void testException() throws IOException {
String errorMessage = "error, error, error";
IOException exception = new IOException(errorMessage);
FakeDataSource dataSource =
new FakeDataSource(
new FakeDataSet()
.newData(uri.toString())
.appendReadData(TEST_DATA)
.appendReadError(exception)
.appendReadData(TEST_DATA)
.endData());
assertThat(dataSource.open(new DataSpec(uri))).isEqualTo(30);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15);
assertBuffer(TEST_DATA);
try {
dataSource.read(BUFFER, 0, BUFFER.length);
fail("IOException expected.");
} catch (IOException e) {
assertThat(e).hasMessageThat().isEqualTo(errorMessage);
}
try {
dataSource.read(BUFFER, 0, BUFFER.length);
fail("IOException expected.");
} catch (IOException e) {
assertThat(e).hasMessageThat().isEqualTo(errorMessage);
}
dataSource.close();
assertThat(dataSource.open(new DataSpec(uri, 15, 15, null))).isEqualTo(15);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15);
assertBuffer(TEST_DATA);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
dataSource.close();
}
@Test
public void testRunnable() throws IOException {
TestRunnable[] runnables = new TestRunnable[3];
for (int i = 0; i < 3; i++) {
runnables[i] = new TestRunnable();
}
FakeDataSource dataSource =
new FakeDataSource(
new FakeDataSet()
.newData(uri.toString())
.appendReadData(TEST_DATA)
.appendReadAction(runnables[0])
.appendReadData(TEST_DATA)
.appendReadAction(runnables[1])
.appendReadAction(runnables[2])
.appendReadData(TEST_DATA)
.endData());
assertThat(dataSource.open(new DataSpec(uri))).isEqualTo(45);
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15);
assertBuffer(TEST_DATA);
for (int i = 0; i < 3; i++) {
assertThat(runnables[i].ran).isFalse();
}
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15);
assertBuffer(TEST_DATA);
assertThat(runnables[0].ran).isTrue();
assertThat(runnables[1].ran).isFalse();
assertThat(runnables[2].ran).isFalse();
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(15);
assertBuffer(TEST_DATA);
for (int i = 0; i < 3; i++) {
assertThat(runnables[i].ran).isTrue();
}
assertThat(dataSource.read(BUFFER, 0, BUFFER.length)).isEqualTo(C.RESULT_END_OF_INPUT);
dataSource.close();
}
@Test
public void testOpenSourceFailures() throws IOException {
// Empty data.
FakeDataSource dataSource =
new FakeDataSource(new FakeDataSet().newData(uri.toString()).endData());
try {
dataSource.open(new DataSpec(uri));
fail("IOException expected.");
} catch (IOException e) {
// Expected.
} finally {
dataSource.close();
}
// Non-existent data
dataSource = new FakeDataSource(new FakeDataSet());
try {
dataSource.open(new DataSpec(uri));
fail("IOException expected.");
} catch (IOException e) {
// Expected.
} finally {
dataSource.close();
}
// DataSpec out of bounds.
dataSource =
new FakeDataSource(
new FakeDataSet()
.newDefaultData()
.appendReadData(TestUtil.buildTestData(10))
.endData());
try {
dataSource.open(new DataSpec(uri, 5, 10, null));
fail("IOException expected.");
} catch (IOException e) {
// Expected.
} finally {
dataSource.close();
}
}
private static void assertBuffer(byte[] expected) {
assertBuffer(expected, 0, expected.length);
}
private static void assertBuffer(byte[] expected, int expectedStart, int expectedLength) {
for (int i = 0; i < expectedLength; i++) {
assertThat(BUFFER[i]).isEqualTo(expected[i + expectedStart]);
}
}
private static final class TestRunnable implements Runnable {
public boolean ran;
@Override
public void run() {
ran = true;
}
}
}

View File

@ -0,0 +1 @@
manifest=src/test/AndroidManifest.xml

View File

@ -0,0 +1,37 @@
// Copyright (C) 2018 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.
apply from: '../constants.gradle'
apply plugin: 'com.android.library'
android {
compileSdkVersion project.ext.compileSdkVersion
buildToolsVersion project.ext.buildToolsVersion
defaultConfig {
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.targetSdkVersion
}
lintOptions {
// Truth depends on JUnit, which depends on java.lang.management, which
// is not part of Android. Remove this when JUnit 4.13 or later is used.
// See: https://github.com/junit-team/junit4/pull/1187.
disable 'InvalidPackage'
}
}
dependencies {
compile project(modulePrefix + 'testutils')
compile 'org.robolectric:robolectric:' + robolectricVersion
}

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2018 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.
-->
<manifest package="com.google.android.exoplayer2.testutil"/>

View File

@ -29,9 +29,7 @@ import com.google.android.exoplayer2.upstream.cache.CacheUtil;
import java.io.IOException;
import java.util.ArrayList;
/**
* Assertion methods for {@link Cache}.
*/
/** Assertion methods for {@link Cache}. */
public final class CacheAsserts {
/**
@ -135,5 +133,4 @@ public final class CacheAsserts {
}
private CacheAsserts() {}
}

View File

@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.os.ConditionVariable;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
/** Helper class to simulate main/UI thread in tests. */
public final class DummyMainThread {
@ -54,16 +55,20 @@ public final class DummyMainThread {
* @param runnable The {@link Runnable} to run.
*/
public void runOnMainThread(int timeoutMs, final Runnable runnable) {
final ConditionVariable finishedCondition = new ConditionVariable();
handler.post(
new Runnable() {
@Override
public void run() {
runnable.run();
finishedCondition.open();
}
});
assertThat(finishedCondition.block(timeoutMs)).isTrue();
if (Looper.myLooper() == handler.getLooper()) {
runnable.run();
} else {
final ConditionVariable finishedCondition = new ConditionVariable();
handler.post(
new Runnable() {
@Override
public void run() {
runnable.run();
finishedCondition.open();
}
});
assertThat(finishedCondition.block(timeoutMs)).isTrue();
}
}
public void release() {

View File

@ -19,9 +19,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.util.MediaClock;
/**
* Fake abstract {@link Renderer} which is also a {@link MediaClock}.
*/
/** Fake abstract {@link Renderer} which is also a {@link MediaClock}. */
public abstract class FakeMediaClockRenderer extends FakeRenderer implements MediaClock {
public FakeMediaClockRenderer(Format... expectedFormats) {
@ -32,5 +30,4 @@ public abstract class FakeMediaClockRenderer extends FakeRenderer implements Med
public MediaClock getMediaClock() {
return this;
}
}

View File

@ -25,8 +25,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
import java.util.List;
/**
* A fake {@link TrackSelection} that only returns 1 fixed track, and allows querying the number
* of calls to its methods.
* A fake {@link TrackSelection} that only returns 1 fixed track, and allows querying the number of
* calls to its methods.
*/
public final class FakeTrackSelection implements TrackSelection {
@ -118,8 +118,8 @@ public final class FakeTrackSelection implements TrackSelection {
}
@Override
public void updateSelectedTrack(long playbackPositionUs, long bufferedDurationUs,
long availableDurationUs) {
public void updateSelectedTrack(
long playbackPositionUs, long bufferedDurationUs, long availableDurationUs) {
assertThat(isEnabled).isTrue();
}
@ -134,5 +134,4 @@ public final class FakeTrackSelection implements TrackSelection {
assertThat(isEnabled).isTrue();
return false;
}
}

View File

@ -25,9 +25,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelection;
import java.util.ArrayList;
import java.util.List;
/**
* A fake {@link MappingTrackSelector} that returns {@link FakeTrackSelection}s.
*/
/** A fake {@link MappingTrackSelector} that returns {@link FakeTrackSelection}s. */
public class FakeTrackSelector extends MappingTrackSelector {
private final List<FakeTrackSelection> selectedTrackSelections = new ArrayList<>();
@ -38,17 +36,19 @@ public class FakeTrackSelector extends MappingTrackSelector {
}
/**
* @param mayReuseTrackSelection Whether this {@link FakeTrackSelector} will reuse
* {@link TrackSelection}s during track selection, when it finds previously-selected track
* selection using the same {@link TrackGroup}.
* @param mayReuseTrackSelection Whether this {@link FakeTrackSelector} will reuse {@link
* TrackSelection}s during track selection, when it finds previously-selected track selection
* using the same {@link TrackGroup}.
*/
public FakeTrackSelector(boolean mayReuseTrackSelection) {
this.mayReuseTrackSelection = mayReuseTrackSelection;
}
@Override
protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities,
TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports)
protected TrackSelection[] selectTracks(
RendererCapabilities[] rendererCapabilities,
TrackGroupArray[] rendererTrackGroupArrays,
int[][][] rendererFormatSupports)
throws ExoPlaybackException {
List<FakeTrackSelection> resultList = new ArrayList<>();
for (TrackGroupArray trackGroupArray : rendererTrackGroupArrays) {
@ -76,11 +76,8 @@ public class FakeTrackSelector extends MappingTrackSelector {
return trackSelectionForRenderer;
}
/**
* Returns list of all {@link FakeTrackSelection}s that this track selector has made so far.
*/
/** Returns list of all {@link FakeTrackSelection}s that this track selector has made so far. */
public List<FakeTrackSelection> getSelectedTrackSelections() {
return selectedTrackSelections;
}
}

View File

@ -37,9 +37,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.TimeUnit;
/**
* A runner for {@link MediaSource} tests.
*/
/** A runner for {@link MediaSource} tests. */
public class MediaSourceTestRunner {
public static final int TIMEOUT_MS = 10000;
@ -78,18 +76,19 @@ public class MediaSourceTestRunner {
public void runOnPlaybackThread(final Runnable runnable) {
final Throwable[] throwable = new Throwable[1];
final ConditionVariable finishedCondition = new ConditionVariable();
playbackHandler.post(new Runnable() {
@Override
public void run() {
try {
runnable.run();
} catch (Throwable e) {
throwable[0] = e;
} finally {
finishedCondition.open();
}
}
});
playbackHandler.post(
new Runnable() {
@Override
public void run() {
try {
runnable.run();
} catch (Throwable e) {
throwable[0] = e;
} finally {
finishedCondition.open();
}
}
});
assertThat(finishedCondition.block(TIMEOUT_MS)).isTrue();
if (throwable[0] != null) {
Util.sneakyThrow(throwable[0]);
@ -103,20 +102,21 @@ public class MediaSourceTestRunner {
*/
public Timeline prepareSource() throws IOException {
final IOException[] prepareError = new IOException[1];
runOnPlaybackThread(new Runnable() {
@Override
public void run() {
mediaSource.prepareSource(player, true, mediaSourceListener);
try {
// TODO: This only catches errors that are set synchronously in prepareSource. To capture
// async errors we'll need to poll maybeThrowSourceInfoRefreshError until the first call
// to onSourceInfoRefreshed.
mediaSource.maybeThrowSourceInfoRefreshError();
} catch (IOException e) {
prepareError[0] = e;
}
}
});
runOnPlaybackThread(
new Runnable() {
@Override
public void run() {
mediaSource.prepareSource(player, true, mediaSourceListener);
try {
// TODO: This only catches errors that are set synchronously in prepareSource. To
// capture async errors we'll need to poll maybeThrowSourceInfoRefreshError until the
// first call to onSourceInfoRefreshed.
mediaSource.maybeThrowSourceInfoRefreshError();
} catch (IOException e) {
prepareError[0] = e;
}
}
});
if (prepareError[0] != null) {
throw prepareError[0];
}
@ -132,12 +132,13 @@ public class MediaSourceTestRunner {
*/
public MediaPeriod createPeriod(final MediaPeriodId periodId) {
final MediaPeriod[] holder = new MediaPeriod[1];
runOnPlaybackThread(new Runnable() {
@Override
public void run() {
holder[0] = mediaSource.createPeriod(periodId, allocator);
}
});
runOnPlaybackThread(
new Runnable() {
@Override
public void run() {
holder[0] = mediaSource.createPeriod(periodId, allocator);
}
});
assertThat(holder[0]).isNotNull();
return holder[0];
}
@ -183,24 +184,24 @@ public class MediaSourceTestRunner {
* @param mediaPeriod The {@link MediaPeriod} to release.
*/
public void releasePeriod(final MediaPeriod mediaPeriod) {
runOnPlaybackThread(new Runnable() {
@Override
public void run() {
mediaSource.releasePeriod(mediaPeriod);
}
});
runOnPlaybackThread(
new Runnable() {
@Override
public void run() {
mediaSource.releasePeriod(mediaPeriod);
}
});
}
/**
* Calls {@link MediaSource#releaseSource()} on the playback thread.
*/
/** Calls {@link MediaSource#releaseSource()} on the playback thread. */
public void releaseSource() {
runOnPlaybackThread(new Runnable() {
@Override
public void run() {
mediaSource.releaseSource();
}
});
runOnPlaybackThread(
new Runnable() {
@Override
public void run() {
mediaSource.releaseSource();
}
});
}
/**
@ -276,9 +277,7 @@ public class MediaSourceTestRunner {
releasePeriod(secondMediaPeriod);
}
/**
* Releases the runner. Should be called when the runner is no longer required.
*/
/** Releases the runner. Should be called when the runner is no longer required. */
public void release() {
playbackThread.quit();
}
@ -290,7 +289,6 @@ public class MediaSourceTestRunner {
Assertions.checkState(Looper.myLooper() == playbackThread.getLooper());
timelines.addLast(timeline);
}
}
private static class EventHandlingExoPlayer extends StubExoPlayer
@ -326,5 +324,4 @@ public class MediaSourceTestRunner {
return true;
}
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2;
package com.google.android.exoplayer2.testutil;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.util.ReflectionHelpers.callInstanceMethod;

View File

@ -271,5 +271,4 @@ public abstract class StubExoPlayer implements ExoPlayer {
public long getContentPosition() {
throw new UnsupportedOperationException();
}
}

View File

@ -23,9 +23,7 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.Timeline.Period;
import com.google.android.exoplayer2.Timeline.Window;
/**
* Unit test for {@link Timeline}.
*/
/** Unit test for {@link Timeline}. */
public final class TimelineAsserts {
private static final int[] REPEAT_MODES = {
@ -34,9 +32,7 @@ public final class TimelineAsserts {
private TimelineAsserts() {}
/**
* Assert that timeline is empty (i.e. has no windows or periods).
*/
/** Assert that timeline is empty (i.e. has no windows or periods). */
public static void assertEmpty(Timeline timeline) {
assertWindowIds(timeline);
assertPeriodCounts(timeline);
@ -63,9 +59,7 @@ public final class TimelineAsserts {
}
}
/**
* Asserts that window properties {@link Window}.isDynamic are set correctly.
*/
/** Asserts that window properties {@link Window}.isDynamic are set correctly. */
public static void assertWindowIsDynamic(Timeline timeline, boolean... windowIsDynamic) {
Window window = new Window();
for (int i = 0; i < timeline.getWindowCount(); i++) {
@ -78,8 +72,10 @@ public final class TimelineAsserts {
* Asserts that previous window indices for each window depending on the repeat mode and the
* shuffle mode are equal to the given sequence.
*/
public static void assertPreviousWindowIndices(Timeline timeline,
@Player.RepeatMode int repeatMode, boolean shuffleModeEnabled,
public static void assertPreviousWindowIndices(
Timeline timeline,
@Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled,
int... expectedPreviousWindowIndices) {
for (int i = 0; i < timeline.getWindowCount(); i++) {
assertThat(timeline.getPreviousWindowIndex(i, repeatMode, shuffleModeEnabled))
@ -88,11 +84,14 @@ public final class TimelineAsserts {
}
/**
* Asserts that next window indices for each window depending on the repeat mode and the
* shuffle mode are equal to the given sequence.
* Asserts that next window indices for each window depending on the repeat mode and the shuffle
* mode are equal to the given sequence.
*/
public static void assertNextWindowIndices(Timeline timeline, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled, int... expectedNextWindowIndices) {
public static void assertNextWindowIndices(
Timeline timeline,
@Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled,
int... expectedNextWindowIndices) {
for (int i = 0; i < timeline.getWindowCount(); i++) {
assertThat(timeline.getNextWindowIndex(i, repeatMode, shuffleModeEnabled))
.isEqualTo(expectedNextWindowIndices[i]);
@ -113,9 +112,9 @@ public final class TimelineAsserts {
}
/**
* Asserts that period counts for each window are set correctly. Also asserts that
* {@link Window#firstPeriodIndex} and {@link Window#lastPeriodIndex} are set correctly, and it
* asserts the correct behavior of {@link Timeline#getNextWindowIndex(int, int, boolean)}.
* Asserts that period counts for each window are set correctly. Also asserts that {@link
* Window#firstPeriodIndex} and {@link Window#lastPeriodIndex} are set correctly, and it asserts
* the correct behavior of {@link Timeline#getNextWindowIndex(int, int, boolean)}.
*/
public static void assertPeriodCounts(Timeline timeline, int... expectedPeriodCounts) {
int windowCount = timeline.getWindowCount();
@ -147,8 +146,8 @@ public final class TimelineAsserts {
.isEqualTo(i + 1);
} else {
int nextWindow = timeline.getNextWindowIndex(expectedWindowIndex, repeatMode, false);
int nextPeriod = nextWindow == C.INDEX_UNSET ? C.INDEX_UNSET
: accumulatedPeriodCounts[nextWindow];
int nextPeriod =
nextWindow == C.INDEX_UNSET ? C.INDEX_UNSET : accumulatedPeriodCounts[nextWindow];
assertThat(timeline.getNextPeriodIndex(i, period, window, repeatMode, false))
.isEqualTo(nextPeriod);
}
@ -156,9 +155,7 @@ public final class TimelineAsserts {
}
}
/**
* Asserts that periods' {@link Period#getAdGroupCount()} are set correctly.
*/
/** Asserts that periods' {@link Period#getAdGroupCount()} are set correctly. */
public static void assertAdGroupCounts(Timeline timeline, int... expectedAdGroupCounts) {
Period period = new Period();
for (int i = 0; i < timeline.getPeriodCount(); i++) {
@ -166,5 +163,4 @@ public final class TimelineAsserts {
assertThat(period.getAdGroupCount()).isEqualTo(expectedAdGroupCounts[i]);
}
}
}