1 /* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2.ext.cronet; 17 18 import static com.google.common.truth.Truth.assertThat; 19 import static org.junit.Assert.fail; 20 import static org.mockito.ArgumentMatchers.any; 21 import static org.mockito.ArgumentMatchers.anyString; 22 import static org.mockito.ArgumentMatchers.eq; 23 import static org.mockito.Mockito.doAnswer; 24 import static org.mockito.Mockito.doThrow; 25 import static org.mockito.Mockito.mock; 26 import static org.mockito.Mockito.never; 27 import static org.mockito.Mockito.times; 28 import static org.mockito.Mockito.verify; 29 import static org.mockito.Mockito.when; 30 31 import android.net.Uri; 32 import android.os.ConditionVariable; 33 import android.os.SystemClock; 34 import androidx.test.ext.junit.runners.AndroidJUnit4; 35 import com.google.android.exoplayer2.C; 36 import com.google.android.exoplayer2.upstream.DataSpec; 37 import com.google.android.exoplayer2.upstream.HttpDataSource; 38 import com.google.android.exoplayer2.upstream.HttpDataSource.HttpDataSourceException; 39 import com.google.android.exoplayer2.upstream.TransferListener; 40 import com.google.android.exoplayer2.util.Clock; 41 import com.google.android.exoplayer2.util.Util; 42 import java.io.IOException; 43 import java.net.SocketTimeoutException; 44 import java.net.UnknownHostException; 45 import java.nio.ByteBuffer; 46 import java.util.ArrayList; 47 import java.util.Arrays; 48 import java.util.Collections; 49 import java.util.HashMap; 50 import java.util.Map; 51 import java.util.concurrent.CountDownLatch; 52 import java.util.concurrent.Executor; 53 import java.util.concurrent.atomic.AtomicInteger; 54 import java.util.concurrent.atomic.AtomicReference; 55 import org.chromium.net.CronetEngine; 56 import org.chromium.net.NetworkException; 57 import org.chromium.net.UrlRequest; 58 import org.chromium.net.UrlResponseInfo; 59 import org.chromium.net.impl.UrlResponseInfoImpl; 60 import org.junit.Before; 61 import org.junit.Test; 62 import org.junit.runner.RunWith; 63 import org.mockito.ArgumentMatchers; 64 import org.mockito.Mock; 65 import org.mockito.MockitoAnnotations; 66 import org.robolectric.annotation.LooperMode; 67 import org.robolectric.annotation.LooperMode.Mode; 68 import org.robolectric.shadows.ShadowLooper; 69 70 /** Tests for {@link CronetDataSource}. */ 71 @RunWith(AndroidJUnit4.class) 72 @LooperMode(Mode.PAUSED) 73 public final class CronetDataSourceTest { 74 75 private static final int TEST_CONNECT_TIMEOUT_MS = 100; 76 private static final int TEST_READ_TIMEOUT_MS = 100; 77 private static final String TEST_URL = "http://google.com"; 78 private static final String TEST_CONTENT_TYPE = "test/test"; 79 private static final byte[] TEST_POST_BODY = Util.getUtf8Bytes("test post body"); 80 private static final long TEST_CONTENT_LENGTH = 16000L; 81 private static final int TEST_CONNECTION_STATUS = 5; 82 private static final int TEST_INVALID_CONNECTION_STATUS = -1; 83 84 private DataSpec testDataSpec; 85 private DataSpec testPostDataSpec; 86 private DataSpec testHeadDataSpec; 87 private Map<String, String> testResponseHeader; 88 private UrlResponseInfo testUrlResponseInfo; 89 90 @Mock private UrlRequest.Builder mockUrlRequestBuilder; 91 @Mock private UrlRequest mockUrlRequest; 92 @Mock private TransferListener mockTransferListener; 93 @Mock private Executor mockExecutor; 94 @Mock private NetworkException mockNetworkException; 95 @Mock private CronetEngine mockCronetEngine; 96 97 private CronetDataSource dataSourceUnderTest; 98 private boolean redirectCalled; 99 100 @Before setUp()101 public void setUp() { 102 MockitoAnnotations.initMocks(this); 103 104 HttpDataSource.RequestProperties defaultRequestProperties = 105 new HttpDataSource.RequestProperties(); 106 defaultRequestProperties.set("defaultHeader1", "defaultValue1"); 107 defaultRequestProperties.set("defaultHeader2", "defaultValue2"); 108 109 dataSourceUnderTest = 110 new CronetDataSource( 111 mockCronetEngine, 112 mockExecutor, 113 TEST_CONNECT_TIMEOUT_MS, 114 TEST_READ_TIMEOUT_MS, 115 /* resetTimeoutOnRedirects= */ true, 116 Clock.DEFAULT, 117 defaultRequestProperties, 118 /* handleSetCookieRequests= */ false); 119 dataSourceUnderTest.addTransferListener(mockTransferListener); 120 when(mockCronetEngine.newUrlRequestBuilder( 121 anyString(), any(UrlRequest.Callback.class), any(Executor.class))) 122 .thenReturn(mockUrlRequestBuilder); 123 when(mockUrlRequestBuilder.allowDirectExecutor()).thenReturn(mockUrlRequestBuilder); 124 when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest); 125 mockStatusResponse(); 126 127 testDataSpec = new DataSpec(Uri.parse(TEST_URL)); 128 testPostDataSpec = 129 new DataSpec.Builder() 130 .setUri(TEST_URL) 131 .setHttpMethod(DataSpec.HTTP_METHOD_POST) 132 .setHttpBody(TEST_POST_BODY) 133 .build(); 134 testHeadDataSpec = 135 new DataSpec.Builder().setUri(TEST_URL).setHttpMethod(DataSpec.HTTP_METHOD_HEAD).build(); 136 testResponseHeader = new HashMap<>(); 137 testResponseHeader.put("Content-Type", TEST_CONTENT_TYPE); 138 // This value can be anything since the DataSpec is unset. 139 testResponseHeader.put("Content-Length", Long.toString(TEST_CONTENT_LENGTH)); 140 testUrlResponseInfo = createUrlResponseInfo(200); // statusCode 141 } 142 createUrlResponseInfo(int statusCode)143 private UrlResponseInfo createUrlResponseInfo(int statusCode) { 144 return createUrlResponseInfoWithUrl(TEST_URL, statusCode); 145 } 146 createUrlResponseInfoWithUrl(String url, int statusCode)147 private UrlResponseInfo createUrlResponseInfoWithUrl(String url, int statusCode) { 148 ArrayList<Map.Entry<String, String>> responseHeaderList = new ArrayList<>(); 149 responseHeaderList.addAll(testResponseHeader.entrySet()); 150 return new UrlResponseInfoImpl( 151 Collections.singletonList(url), 152 statusCode, 153 null, // httpStatusText 154 responseHeaderList, 155 false, // wasCached 156 null, // negotiatedProtocol 157 null); // proxyServer 158 } 159 160 @Test openingTwiceThrows()161 public void openingTwiceThrows() throws HttpDataSourceException { 162 mockResponseStartSuccess(); 163 dataSourceUnderTest.open(testDataSpec); 164 try { 165 dataSourceUnderTest.open(testDataSpec); 166 fail("Expected IllegalStateException."); 167 } catch (IllegalStateException e) { 168 // Expected. 169 } 170 } 171 172 @Test callbackFromPreviousRequest()173 public void callbackFromPreviousRequest() throws HttpDataSourceException { 174 mockResponseStartSuccess(); 175 176 dataSourceUnderTest.open(testDataSpec); 177 dataSourceUnderTest.close(); 178 // Prepare a mock UrlRequest to be used in the second open() call. 179 final UrlRequest mockUrlRequest2 = mock(UrlRequest.class); 180 when(mockUrlRequestBuilder.build()).thenReturn(mockUrlRequest2); 181 doAnswer( 182 invocation -> { 183 // Invoke the callback for the previous request. 184 dataSourceUnderTest.urlRequestCallback.onFailed( 185 mockUrlRequest, testUrlResponseInfo, mockNetworkException); 186 dataSourceUnderTest.urlRequestCallback.onResponseStarted( 187 mockUrlRequest2, testUrlResponseInfo); 188 return null; 189 }) 190 .when(mockUrlRequest2) 191 .start(); 192 dataSourceUnderTest.open(testDataSpec); 193 } 194 195 @Test requestStartCalled()196 public void requestStartCalled() throws HttpDataSourceException { 197 mockResponseStartSuccess(); 198 199 dataSourceUnderTest.open(testDataSpec); 200 verify(mockCronetEngine) 201 .newUrlRequestBuilder(eq(TEST_URL), any(UrlRequest.Callback.class), any(Executor.class)); 202 verify(mockUrlRequest).start(); 203 } 204 205 @Test requestSetsRangeHeader()206 public void requestSetsRangeHeader() throws HttpDataSourceException { 207 testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000); 208 mockResponseStartSuccess(); 209 210 dataSourceUnderTest.open(testDataSpec); 211 // The header value to add is current position to current position + length - 1. 212 verify(mockUrlRequestBuilder).addHeader("Range", "bytes=1000-5999"); 213 } 214 215 @Test requestHeadersSet()216 public void requestHeadersSet() throws HttpDataSourceException { 217 Map<String, String> headersSet = new HashMap<>(); 218 doAnswer( 219 (invocation) -> { 220 String key = invocation.getArgument(0); 221 String value = invocation.getArgument(1); 222 headersSet.put(key, value); 223 return null; 224 }) 225 .when(mockUrlRequestBuilder) 226 .addHeader(ArgumentMatchers.anyString(), ArgumentMatchers.anyString()); 227 228 dataSourceUnderTest.setRequestProperty("defaultHeader2", "dataSourceOverridesDefault"); 229 dataSourceUnderTest.setRequestProperty("dataSourceHeader1", "dataSourceValue1"); 230 dataSourceUnderTest.setRequestProperty("dataSourceHeader2", "dataSourceValue2"); 231 232 Map<String, String> dataSpecRequestProperties = new HashMap<>(); 233 dataSpecRequestProperties.put("defaultHeader3", "dataSpecOverridesAll"); 234 dataSpecRequestProperties.put("dataSourceHeader2", "dataSpecOverridesDataSource"); 235 dataSpecRequestProperties.put("dataSpecHeader1", "dataSpecValue1"); 236 237 testDataSpec = 238 new DataSpec.Builder() 239 .setUri(TEST_URL) 240 .setPosition(1000) 241 .setLength(5000) 242 .setHttpRequestHeaders(dataSpecRequestProperties) 243 .build(); 244 mockResponseStartSuccess(); 245 246 dataSourceUnderTest.open(testDataSpec); 247 248 assertThat(headersSet.get("defaultHeader1")).isEqualTo("defaultValue1"); 249 assertThat(headersSet.get("defaultHeader2")).isEqualTo("dataSourceOverridesDefault"); 250 assertThat(headersSet.get("defaultHeader3")).isEqualTo("dataSpecOverridesAll"); 251 assertThat(headersSet.get("dataSourceHeader1")).isEqualTo("dataSourceValue1"); 252 assertThat(headersSet.get("dataSourceHeader2")).isEqualTo("dataSpecOverridesDataSource"); 253 assertThat(headersSet.get("dataSpecHeader1")).isEqualTo("dataSpecValue1"); 254 255 verify(mockUrlRequest).start(); 256 } 257 258 @Test requestOpen()259 public void requestOpen() throws HttpDataSourceException { 260 mockResponseStartSuccess(); 261 assertThat(dataSourceUnderTest.open(testDataSpec)).isEqualTo(TEST_CONTENT_LENGTH); 262 verify(mockTransferListener) 263 .onTransferStart(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 264 } 265 266 @Test requestOpenGzippedCompressedReturnsDataSpecLength()267 public void requestOpenGzippedCompressedReturnsDataSpecLength() throws HttpDataSourceException { 268 testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, 5000); 269 testResponseHeader.put("Content-Encoding", "gzip"); 270 testResponseHeader.put("Content-Length", Long.toString(50L)); 271 mockResponseStartSuccess(); 272 273 assertThat(dataSourceUnderTest.open(testDataSpec)).isEqualTo(5000 /* contentLength */); 274 verify(mockTransferListener) 275 .onTransferStart(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 276 } 277 278 @Test requestOpenFail()279 public void requestOpenFail() { 280 mockResponseStartFailure(); 281 282 try { 283 dataSourceUnderTest.open(testDataSpec); 284 fail("HttpDataSource.HttpDataSourceException expected"); 285 } catch (HttpDataSourceException e) { 286 // Check for connection not automatically closed. 287 assertThat(e.getCause() instanceof UnknownHostException).isFalse(); 288 verify(mockUrlRequest, never()).cancel(); 289 verify(mockTransferListener, never()) 290 .onTransferStart(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 291 } 292 } 293 294 @Test open_ifBodyIsSetWithoutContentTypeHeader_fails()295 public void open_ifBodyIsSetWithoutContentTypeHeader_fails() { 296 testDataSpec = 297 new DataSpec.Builder() 298 .setUri(TEST_URL) 299 .setHttpMethod(DataSpec.HTTP_METHOD_POST) 300 .setHttpBody(new byte[1024]) 301 .setPosition(200) 302 .setLength(1024) 303 .setKey("key") 304 .build(); 305 306 try { 307 dataSourceUnderTest.open(testDataSpec); 308 fail(); 309 } catch (IOException expected) { 310 // Expected 311 } 312 } 313 314 @Test requestOpenFailDueToDnsFailure()315 public void requestOpenFailDueToDnsFailure() { 316 mockResponseStartFailure(); 317 when(mockNetworkException.getErrorCode()) 318 .thenReturn(NetworkException.ERROR_HOSTNAME_NOT_RESOLVED); 319 320 try { 321 dataSourceUnderTest.open(testDataSpec); 322 fail("HttpDataSource.HttpDataSourceException expected"); 323 } catch (HttpDataSourceException e) { 324 // Check for connection not automatically closed. 325 assertThat(e.getCause() instanceof UnknownHostException).isTrue(); 326 verify(mockUrlRequest, never()).cancel(); 327 verify(mockTransferListener, never()) 328 .onTransferStart(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 329 } 330 } 331 332 @Test requestOpenValidatesStatusCode()333 public void requestOpenValidatesStatusCode() { 334 mockResponseStartSuccess(); 335 testUrlResponseInfo = createUrlResponseInfo(500); // statusCode 336 337 try { 338 dataSourceUnderTest.open(testDataSpec); 339 fail("HttpDataSource.HttpDataSourceException expected"); 340 } catch (HttpDataSourceException e) { 341 assertThat(e instanceof HttpDataSource.InvalidResponseCodeException).isTrue(); 342 // Check for connection not automatically closed. 343 verify(mockUrlRequest, never()).cancel(); 344 verify(mockTransferListener, never()) 345 .onTransferStart(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 346 } 347 } 348 349 @Test requestOpenValidatesContentTypePredicate()350 public void requestOpenValidatesContentTypePredicate() { 351 mockResponseStartSuccess(); 352 353 ArrayList<String> testedContentTypes = new ArrayList<>(); 354 dataSourceUnderTest.setContentTypePredicate( 355 (String input) -> { 356 testedContentTypes.add(input); 357 return false; 358 }); 359 360 try { 361 dataSourceUnderTest.open(testDataSpec); 362 fail("HttpDataSource.HttpDataSourceException expected"); 363 } catch (HttpDataSourceException e) { 364 assertThat(e instanceof HttpDataSource.InvalidContentTypeException).isTrue(); 365 // Check for connection not automatically closed. 366 verify(mockUrlRequest, never()).cancel(); 367 assertThat(testedContentTypes).hasSize(1); 368 assertThat(testedContentTypes.get(0)).isEqualTo(TEST_CONTENT_TYPE); 369 } 370 } 371 372 @Test postRequestOpen()373 public void postRequestOpen() throws HttpDataSourceException { 374 mockResponseStartSuccess(); 375 376 dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); 377 assertThat(dataSourceUnderTest.open(testPostDataSpec)).isEqualTo(TEST_CONTENT_LENGTH); 378 verify(mockTransferListener) 379 .onTransferStart(dataSourceUnderTest, testPostDataSpec, /* isNetwork= */ true); 380 } 381 382 @Test postRequestOpenValidatesContentType()383 public void postRequestOpenValidatesContentType() { 384 mockResponseStartSuccess(); 385 386 try { 387 dataSourceUnderTest.open(testPostDataSpec); 388 fail("HttpDataSource.HttpDataSourceException expected"); 389 } catch (HttpDataSourceException e) { 390 verify(mockUrlRequest, never()).start(); 391 } 392 } 393 394 @Test postRequestOpenRejects307Redirects()395 public void postRequestOpenRejects307Redirects() { 396 mockResponseStartSuccess(); 397 mockResponseStartRedirect(); 398 399 try { 400 dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); 401 dataSourceUnderTest.open(testPostDataSpec); 402 fail("HttpDataSource.HttpDataSourceException expected"); 403 } catch (HttpDataSourceException e) { 404 verify(mockUrlRequest, never()).followRedirect(); 405 } 406 } 407 408 @Test headRequestOpen()409 public void headRequestOpen() throws HttpDataSourceException { 410 mockResponseStartSuccess(); 411 dataSourceUnderTest.open(testHeadDataSpec); 412 verify(mockTransferListener) 413 .onTransferStart(dataSourceUnderTest, testHeadDataSpec, /* isNetwork= */ true); 414 dataSourceUnderTest.close(); 415 } 416 417 @Test requestReadTwice()418 public void requestReadTwice() throws HttpDataSourceException { 419 mockResponseStartSuccess(); 420 mockReadSuccess(0, 16); 421 422 dataSourceUnderTest.open(testDataSpec); 423 424 byte[] returnedBuffer = new byte[8]; 425 int bytesRead = dataSourceUnderTest.read(returnedBuffer, 0, 8); 426 assertThat(returnedBuffer).isEqualTo(buildTestDataArray(0, 8)); 427 assertThat(bytesRead).isEqualTo(8); 428 429 returnedBuffer = new byte[8]; 430 bytesRead = dataSourceUnderTest.read(returnedBuffer, 0, 8); 431 assertThat(returnedBuffer).isEqualTo(buildTestDataArray(8, 8)); 432 assertThat(bytesRead).isEqualTo(8); 433 434 // Should have only called read on cronet once. 435 verify(mockUrlRequest, times(1)).read(any(ByteBuffer.class)); 436 verify(mockTransferListener, times(2)) 437 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 8); 438 } 439 440 @Test secondRequestNoContentLength()441 public void secondRequestNoContentLength() throws HttpDataSourceException { 442 mockResponseStartSuccess(); 443 testResponseHeader.put("Content-Length", Long.toString(1L)); 444 mockReadSuccess(0, 16); 445 446 // First request. 447 dataSourceUnderTest.open(testDataSpec); 448 byte[] returnedBuffer = new byte[8]; 449 dataSourceUnderTest.read(returnedBuffer, 0, 1); 450 dataSourceUnderTest.close(); 451 452 testResponseHeader.remove("Content-Length"); 453 mockReadSuccess(0, 16); 454 455 // Second request. 456 dataSourceUnderTest.open(testDataSpec); 457 returnedBuffer = new byte[16]; 458 int bytesRead = dataSourceUnderTest.read(returnedBuffer, 0, 10); 459 assertThat(bytesRead).isEqualTo(10); 460 bytesRead = dataSourceUnderTest.read(returnedBuffer, 0, 10); 461 assertThat(bytesRead).isEqualTo(6); 462 bytesRead = dataSourceUnderTest.read(returnedBuffer, 0, 10); 463 assertThat(bytesRead).isEqualTo(C.RESULT_END_OF_INPUT); 464 } 465 466 @Test readWithOffset()467 public void readWithOffset() throws HttpDataSourceException { 468 mockResponseStartSuccess(); 469 mockReadSuccess(0, 16); 470 471 dataSourceUnderTest.open(testDataSpec); 472 473 byte[] returnedBuffer = new byte[16]; 474 int bytesRead = dataSourceUnderTest.read(returnedBuffer, 8, 8); 475 assertThat(bytesRead).isEqualTo(8); 476 assertThat(returnedBuffer).isEqualTo(prefixZeros(buildTestDataArray(0, 8), 16)); 477 verify(mockTransferListener) 478 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 8); 479 } 480 481 @Test rangeRequestWith206Response()482 public void rangeRequestWith206Response() throws HttpDataSourceException { 483 mockResponseStartSuccess(); 484 mockReadSuccess(1000, 5000); 485 testUrlResponseInfo = createUrlResponseInfo(206); // Server supports range requests. 486 testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000); 487 488 dataSourceUnderTest.open(testDataSpec); 489 490 byte[] returnedBuffer = new byte[16]; 491 int bytesRead = dataSourceUnderTest.read(returnedBuffer, 0, 16); 492 assertThat(bytesRead).isEqualTo(16); 493 assertThat(returnedBuffer).isEqualTo(buildTestDataArray(1000, 16)); 494 verify(mockTransferListener) 495 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 16); 496 } 497 498 @Test rangeRequestWith200Response()499 public void rangeRequestWith200Response() throws HttpDataSourceException { 500 mockResponseStartSuccess(); 501 mockReadSuccess(0, 7000); 502 testUrlResponseInfo = createUrlResponseInfo(200); // Server does not support range requests. 503 testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000); 504 505 dataSourceUnderTest.open(testDataSpec); 506 507 byte[] returnedBuffer = new byte[16]; 508 int bytesRead = dataSourceUnderTest.read(returnedBuffer, 0, 16); 509 assertThat(bytesRead).isEqualTo(16); 510 assertThat(returnedBuffer).isEqualTo(buildTestDataArray(1000, 16)); 511 verify(mockTransferListener) 512 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 16); 513 } 514 515 @Test readWithUnsetLength()516 public void readWithUnsetLength() throws HttpDataSourceException { 517 testResponseHeader.remove("Content-Length"); 518 mockResponseStartSuccess(); 519 mockReadSuccess(0, 16); 520 521 dataSourceUnderTest.open(testDataSpec); 522 523 byte[] returnedBuffer = new byte[16]; 524 int bytesRead = dataSourceUnderTest.read(returnedBuffer, 8, 8); 525 assertThat(returnedBuffer).isEqualTo(prefixZeros(buildTestDataArray(0, 8), 16)); 526 assertThat(bytesRead).isEqualTo(8); 527 verify(mockTransferListener) 528 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 8); 529 } 530 531 @Test readReturnsWhatItCan()532 public void readReturnsWhatItCan() throws HttpDataSourceException { 533 mockResponseStartSuccess(); 534 mockReadSuccess(0, 16); 535 536 dataSourceUnderTest.open(testDataSpec); 537 538 byte[] returnedBuffer = new byte[24]; 539 int bytesRead = dataSourceUnderTest.read(returnedBuffer, 0, 24); 540 assertThat(returnedBuffer).isEqualTo(suffixZeros(buildTestDataArray(0, 16), 24)); 541 assertThat(bytesRead).isEqualTo(16); 542 verify(mockTransferListener) 543 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 16); 544 } 545 546 @Test closedMeansClosed()547 public void closedMeansClosed() throws HttpDataSourceException { 548 mockResponseStartSuccess(); 549 mockReadSuccess(0, 16); 550 551 int bytesRead = 0; 552 dataSourceUnderTest.open(testDataSpec); 553 554 byte[] returnedBuffer = new byte[8]; 555 bytesRead += dataSourceUnderTest.read(returnedBuffer, 0, 8); 556 assertThat(returnedBuffer).isEqualTo(buildTestDataArray(0, 8)); 557 assertThat(bytesRead).isEqualTo(8); 558 559 dataSourceUnderTest.close(); 560 verify(mockTransferListener) 561 .onTransferEnd(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 562 563 try { 564 bytesRead += dataSourceUnderTest.read(returnedBuffer, 0, 8); 565 fail(); 566 } catch (IllegalStateException e) { 567 // Expected. 568 } 569 570 // 16 bytes were attempted but only 8 should have been successfully read. 571 assertThat(bytesRead).isEqualTo(8); 572 } 573 574 @Test overread()575 public void overread() throws HttpDataSourceException { 576 testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, 16); 577 testResponseHeader.put("Content-Length", Long.toString(16L)); 578 mockResponseStartSuccess(); 579 mockReadSuccess(0, 16); 580 581 dataSourceUnderTest.open(testDataSpec); 582 583 byte[] returnedBuffer = new byte[8]; 584 int bytesRead = dataSourceUnderTest.read(returnedBuffer, 0, 8); 585 assertThat(bytesRead).isEqualTo(8); 586 assertThat(returnedBuffer).isEqualTo(buildTestDataArray(0, 8)); 587 588 // The current buffer is kept if not completely consumed by DataSource reader. 589 returnedBuffer = new byte[8]; 590 bytesRead += dataSourceUnderTest.read(returnedBuffer, 0, 6); 591 assertThat(bytesRead).isEqualTo(14); 592 assertThat(returnedBuffer).isEqualTo(suffixZeros(buildTestDataArray(8, 6), 8)); 593 594 // 2 bytes left at this point. 595 returnedBuffer = new byte[8]; 596 bytesRead += dataSourceUnderTest.read(returnedBuffer, 0, 8); 597 assertThat(bytesRead).isEqualTo(16); 598 assertThat(returnedBuffer).isEqualTo(suffixZeros(buildTestDataArray(14, 2), 8)); 599 600 // Should have only called read on cronet once. 601 verify(mockUrlRequest, times(1)).read(any(ByteBuffer.class)); 602 verify(mockTransferListener, times(1)) 603 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 8); 604 verify(mockTransferListener, times(1)) 605 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 6); 606 verify(mockTransferListener, times(1)) 607 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 2); 608 609 // Now we already returned the 16 bytes initially asked. 610 // Try to read again even though all requested 16 bytes are already returned. 611 // Return C.RESULT_END_OF_INPUT 612 returnedBuffer = new byte[16]; 613 int bytesOverRead = dataSourceUnderTest.read(returnedBuffer, 0, 16); 614 assertThat(bytesOverRead).isEqualTo(C.RESULT_END_OF_INPUT); 615 assertThat(returnedBuffer).isEqualTo(new byte[16]); 616 // C.RESULT_END_OF_INPUT should not be reported though the TransferListener. 617 verify(mockTransferListener, never()) 618 .onBytesTransferred( 619 dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, C.RESULT_END_OF_INPUT); 620 // There should still be only one call to read on cronet. 621 verify(mockUrlRequest, times(1)).read(any(ByteBuffer.class)); 622 // Check for connection not automatically closed. 623 verify(mockUrlRequest, never()).cancel(); 624 assertThat(bytesRead).isEqualTo(16); 625 } 626 627 @Test requestReadByteBufferTwice()628 public void requestReadByteBufferTwice() throws HttpDataSourceException { 629 mockResponseStartSuccess(); 630 mockReadSuccess(0, 16); 631 632 dataSourceUnderTest.open(testDataSpec); 633 634 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(8); 635 int bytesRead = dataSourceUnderTest.read(returnedBuffer); 636 assertThat(bytesRead).isEqualTo(8); 637 returnedBuffer.flip(); 638 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(0, 8)); 639 640 // Use a wrapped ByteBuffer instead of direct for coverage. 641 returnedBuffer.rewind(); 642 bytesRead = dataSourceUnderTest.read(returnedBuffer); 643 returnedBuffer.flip(); 644 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(8, 8)); 645 assertThat(bytesRead).isEqualTo(8); 646 647 // Separate cronet calls for each read. 648 verify(mockUrlRequest, times(2)).read(any(ByteBuffer.class)); 649 verify(mockTransferListener, times(2)) 650 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 8); 651 } 652 653 @Test requestIntermixRead()654 public void requestIntermixRead() throws HttpDataSourceException { 655 mockResponseStartSuccess(); 656 // Chunking reads into parts 6, 7, 8, 9. 657 mockReadSuccess(0, 30); 658 659 dataSourceUnderTest.open(testDataSpec); 660 661 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(6); 662 int bytesRead = dataSourceUnderTest.read(returnedBuffer); 663 returnedBuffer.flip(); 664 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(0, 6)); 665 assertThat(bytesRead).isEqualTo(6); 666 667 byte[] returnedBytes = new byte[7]; 668 bytesRead += dataSourceUnderTest.read(returnedBytes, 0, 7); 669 assertThat(returnedBytes).isEqualTo(buildTestDataArray(6, 7)); 670 assertThat(bytesRead).isEqualTo(6 + 7); 671 672 returnedBuffer = ByteBuffer.allocateDirect(8); 673 bytesRead += dataSourceUnderTest.read(returnedBuffer); 674 returnedBuffer.flip(); 675 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(13, 8)); 676 assertThat(bytesRead).isEqualTo(6 + 7 + 8); 677 678 returnedBytes = new byte[9]; 679 bytesRead += dataSourceUnderTest.read(returnedBytes, 0, 9); 680 assertThat(returnedBytes).isEqualTo(buildTestDataArray(21, 9)); 681 assertThat(bytesRead).isEqualTo(6 + 7 + 8 + 9); 682 683 // First ByteBuffer call. The first byte[] call populates enough bytes for the rest. 684 verify(mockUrlRequest, times(2)).read(any(ByteBuffer.class)); 685 verify(mockTransferListener, times(1)) 686 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 6); 687 verify(mockTransferListener, times(1)) 688 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 7); 689 verify(mockTransferListener, times(1)) 690 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 8); 691 verify(mockTransferListener, times(1)) 692 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 9); 693 } 694 695 @Test secondRequestNoContentLengthReadByteBuffer()696 public void secondRequestNoContentLengthReadByteBuffer() throws HttpDataSourceException { 697 mockResponseStartSuccess(); 698 testResponseHeader.put("Content-Length", Long.toString(1L)); 699 mockReadSuccess(0, 16); 700 701 // First request. 702 dataSourceUnderTest.open(testDataSpec); 703 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(8); 704 dataSourceUnderTest.read(returnedBuffer); 705 dataSourceUnderTest.close(); 706 707 testResponseHeader.remove("Content-Length"); 708 mockReadSuccess(0, 16); 709 710 // Second request. 711 dataSourceUnderTest.open(testDataSpec); 712 returnedBuffer = ByteBuffer.allocateDirect(16); 713 returnedBuffer.limit(10); 714 int bytesRead = dataSourceUnderTest.read(returnedBuffer); 715 assertThat(bytesRead).isEqualTo(10); 716 returnedBuffer.limit(returnedBuffer.capacity()); 717 bytesRead = dataSourceUnderTest.read(returnedBuffer); 718 assertThat(bytesRead).isEqualTo(6); 719 returnedBuffer.rewind(); 720 bytesRead = dataSourceUnderTest.read(returnedBuffer); 721 assertThat(bytesRead).isEqualTo(C.RESULT_END_OF_INPUT); 722 } 723 724 @Test rangeRequestWith206ResponseReadByteBuffer()725 public void rangeRequestWith206ResponseReadByteBuffer() throws HttpDataSourceException { 726 mockResponseStartSuccess(); 727 mockReadSuccess(1000, 5000); 728 testUrlResponseInfo = createUrlResponseInfo(206); // Server supports range requests. 729 testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000); 730 731 dataSourceUnderTest.open(testDataSpec); 732 733 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(16); 734 int bytesRead = dataSourceUnderTest.read(returnedBuffer); 735 assertThat(bytesRead).isEqualTo(16); 736 returnedBuffer.flip(); 737 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(1000, 16)); 738 verify(mockTransferListener) 739 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 16); 740 } 741 742 @Test rangeRequestWith200ResponseReadByteBuffer()743 public void rangeRequestWith200ResponseReadByteBuffer() throws HttpDataSourceException { 744 // Tests for skipping bytes. 745 mockResponseStartSuccess(); 746 mockReadSuccess(0, 7000); 747 testUrlResponseInfo = createUrlResponseInfo(200); // Server does not support range requests. 748 testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000); 749 750 dataSourceUnderTest.open(testDataSpec); 751 752 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(16); 753 int bytesRead = dataSourceUnderTest.read(returnedBuffer); 754 assertThat(bytesRead).isEqualTo(16); 755 returnedBuffer.flip(); 756 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(1000, 16)); 757 verify(mockTransferListener) 758 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 16); 759 } 760 761 @Test readByteBufferWithUnsetLength()762 public void readByteBufferWithUnsetLength() throws HttpDataSourceException { 763 testResponseHeader.remove("Content-Length"); 764 mockResponseStartSuccess(); 765 mockReadSuccess(0, 16); 766 767 dataSourceUnderTest.open(testDataSpec); 768 769 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(16); 770 returnedBuffer.limit(8); 771 int bytesRead = dataSourceUnderTest.read(returnedBuffer); 772 returnedBuffer.flip(); 773 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(0, 8)); 774 assertThat(bytesRead).isEqualTo(8); 775 verify(mockTransferListener) 776 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 8); 777 } 778 779 @Test readByteBufferReturnsWhatItCan()780 public void readByteBufferReturnsWhatItCan() throws HttpDataSourceException { 781 mockResponseStartSuccess(); 782 mockReadSuccess(0, 16); 783 784 dataSourceUnderTest.open(testDataSpec); 785 786 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(24); 787 int bytesRead = dataSourceUnderTest.read(returnedBuffer); 788 returnedBuffer.flip(); 789 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(0, 16)); 790 assertThat(bytesRead).isEqualTo(16); 791 verify(mockTransferListener) 792 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 16); 793 } 794 795 @Test overreadByteBuffer()796 public void overreadByteBuffer() throws HttpDataSourceException { 797 testDataSpec = new DataSpec(Uri.parse(TEST_URL), 0, 16); 798 testResponseHeader.put("Content-Length", Long.toString(16L)); 799 mockResponseStartSuccess(); 800 mockReadSuccess(0, 16); 801 802 dataSourceUnderTest.open(testDataSpec); 803 804 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(8); 805 int bytesRead = dataSourceUnderTest.read(returnedBuffer); 806 assertThat(bytesRead).isEqualTo(8); 807 returnedBuffer.flip(); 808 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(0, 8)); 809 810 // The current buffer is kept if not completely consumed by DataSource reader. 811 returnedBuffer = ByteBuffer.allocateDirect(6); 812 bytesRead += dataSourceUnderTest.read(returnedBuffer); 813 assertThat(bytesRead).isEqualTo(14); 814 returnedBuffer.flip(); 815 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(8, 6)); 816 817 // 2 bytes left at this point. 818 returnedBuffer = ByteBuffer.allocateDirect(8); 819 bytesRead += dataSourceUnderTest.read(returnedBuffer); 820 assertThat(bytesRead).isEqualTo(16); 821 returnedBuffer.flip(); 822 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(14, 2)); 823 824 // Called on each. 825 verify(mockUrlRequest, times(3)).read(any(ByteBuffer.class)); 826 verify(mockTransferListener, times(1)) 827 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 8); 828 verify(mockTransferListener, times(1)) 829 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 6); 830 verify(mockTransferListener, times(1)) 831 .onBytesTransferred(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, 2); 832 833 // Now we already returned the 16 bytes initially asked. 834 // Try to read again even though all requested 16 bytes are already returned. 835 // Return C.RESULT_END_OF_INPUT 836 returnedBuffer = ByteBuffer.allocateDirect(16); 837 int bytesOverRead = dataSourceUnderTest.read(returnedBuffer); 838 assertThat(bytesOverRead).isEqualTo(C.RESULT_END_OF_INPUT); 839 assertThat(returnedBuffer.position()).isEqualTo(0); 840 // C.RESULT_END_OF_INPUT should not be reported though the TransferListener. 841 verify(mockTransferListener, never()) 842 .onBytesTransferred( 843 dataSourceUnderTest, testDataSpec, /* isNetwork= */ true, C.RESULT_END_OF_INPUT); 844 // Number of calls to cronet should not have increased. 845 verify(mockUrlRequest, times(3)).read(any(ByteBuffer.class)); 846 // Check for connection not automatically closed. 847 verify(mockUrlRequest, never()).cancel(); 848 assertThat(bytesRead).isEqualTo(16); 849 } 850 851 @Test closedMeansClosedReadByteBuffer()852 public void closedMeansClosedReadByteBuffer() throws HttpDataSourceException { 853 mockResponseStartSuccess(); 854 mockReadSuccess(0, 16); 855 856 int bytesRead = 0; 857 dataSourceUnderTest.open(testDataSpec); 858 859 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(16); 860 returnedBuffer.limit(8); 861 bytesRead += dataSourceUnderTest.read(returnedBuffer); 862 returnedBuffer.flip(); 863 assertThat(copyByteBufferToArray(returnedBuffer)).isEqualTo(buildTestDataArray(0, 8)); 864 assertThat(bytesRead).isEqualTo(8); 865 866 dataSourceUnderTest.close(); 867 verify(mockTransferListener) 868 .onTransferEnd(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 869 870 try { 871 bytesRead += dataSourceUnderTest.read(returnedBuffer); 872 fail(); 873 } catch (IllegalStateException e) { 874 // Expected. 875 } 876 877 // 16 bytes were attempted but only 8 should have been successfully read. 878 assertThat(bytesRead).isEqualTo(8); 879 } 880 881 @Test connectTimeout()882 public void connectTimeout() throws InterruptedException { 883 long startTimeMs = SystemClock.elapsedRealtime(); 884 final ConditionVariable startCondition = buildUrlRequestStartedCondition(); 885 final CountDownLatch timedOutLatch = new CountDownLatch(1); 886 887 new Thread() { 888 @Override 889 public void run() { 890 try { 891 dataSourceUnderTest.open(testDataSpec); 892 fail(); 893 } catch (HttpDataSourceException e) { 894 // Expected. 895 assertThat(e instanceof CronetDataSource.OpenException).isTrue(); 896 assertThat(e.getCause() instanceof SocketTimeoutException).isTrue(); 897 assertThat(((CronetDataSource.OpenException) e).cronetConnectionStatus) 898 .isEqualTo(TEST_CONNECTION_STATUS); 899 timedOutLatch.countDown(); 900 } 901 } 902 }.start(); 903 startCondition.block(); 904 905 // We should still be trying to open. 906 assertNotCountedDown(timedOutLatch); 907 // We should still be trying to open as we approach the timeout. 908 setSystemClockInMsAndTriggerPendingMessages( 909 /* nowMs= */ startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); 910 assertNotCountedDown(timedOutLatch); 911 // Now we timeout. 912 setSystemClockInMsAndTriggerPendingMessages( 913 /* nowMs= */ startTimeMs + TEST_CONNECT_TIMEOUT_MS + 10); 914 timedOutLatch.await(); 915 916 verify(mockTransferListener, never()) 917 .onTransferStart(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 918 } 919 920 @Test connectInterrupted()921 public void connectInterrupted() throws InterruptedException { 922 long startTimeMs = SystemClock.elapsedRealtime(); 923 final ConditionVariable startCondition = buildUrlRequestStartedCondition(); 924 final CountDownLatch timedOutLatch = new CountDownLatch(1); 925 926 Thread thread = 927 new Thread() { 928 @Override 929 public void run() { 930 try { 931 dataSourceUnderTest.open(testDataSpec); 932 fail(); 933 } catch (HttpDataSourceException e) { 934 // Expected. 935 assertThat(e instanceof CronetDataSource.OpenException).isTrue(); 936 assertThat(e.getCause() instanceof CronetDataSource.InterruptedIOException).isTrue(); 937 assertThat(((CronetDataSource.OpenException) e).cronetConnectionStatus) 938 .isEqualTo(TEST_INVALID_CONNECTION_STATUS); 939 timedOutLatch.countDown(); 940 } 941 } 942 }; 943 thread.start(); 944 startCondition.block(); 945 946 // We should still be trying to open. 947 assertNotCountedDown(timedOutLatch); 948 // We should still be trying to open as we approach the timeout. 949 setSystemClockInMsAndTriggerPendingMessages( 950 /* nowMs= */ startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); 951 assertNotCountedDown(timedOutLatch); 952 // Now we interrupt. 953 thread.interrupt(); 954 timedOutLatch.await(); 955 956 verify(mockTransferListener, never()) 957 .onTransferStart(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 958 } 959 960 @Test connectResponseBeforeTimeout()961 public void connectResponseBeforeTimeout() throws Exception { 962 long startTimeMs = SystemClock.elapsedRealtime(); 963 final ConditionVariable startCondition = buildUrlRequestStartedCondition(); 964 final CountDownLatch openLatch = new CountDownLatch(1); 965 966 AtomicReference<Exception> exceptionOnTestThread = new AtomicReference<>(); 967 new Thread() { 968 @Override 969 public void run() { 970 try { 971 dataSourceUnderTest.open(testDataSpec); 972 } catch (HttpDataSourceException e) { 973 exceptionOnTestThread.set(e); 974 } finally { 975 openLatch.countDown(); 976 } 977 } 978 }.start(); 979 startCondition.block(); 980 981 // We should still be trying to open. 982 assertNotCountedDown(openLatch); 983 // We should still be trying to open as we approach the timeout. 984 setSystemClockInMsAndTriggerPendingMessages( 985 /* nowMs= */ startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); 986 assertNotCountedDown(openLatch); 987 // The response arrives just in time. 988 dataSourceUnderTest.urlRequestCallback.onResponseStarted(mockUrlRequest, testUrlResponseInfo); 989 openLatch.await(); 990 assertThat(exceptionOnTestThread.get()).isNull(); 991 } 992 993 @Test redirectIncreasesConnectionTimeout()994 public void redirectIncreasesConnectionTimeout() throws Exception { 995 long startTimeMs = SystemClock.elapsedRealtime(); 996 final ConditionVariable startCondition = buildUrlRequestStartedCondition(); 997 final CountDownLatch timedOutLatch = new CountDownLatch(1); 998 final AtomicInteger openExceptions = new AtomicInteger(0); 999 1000 new Thread() { 1001 @Override 1002 public void run() { 1003 try { 1004 dataSourceUnderTest.open(testDataSpec); 1005 fail(); 1006 } catch (HttpDataSourceException e) { 1007 // Expected. 1008 assertThat(e instanceof CronetDataSource.OpenException).isTrue(); 1009 assertThat(e.getCause() instanceof SocketTimeoutException).isTrue(); 1010 openExceptions.getAndIncrement(); 1011 timedOutLatch.countDown(); 1012 } 1013 } 1014 }.start(); 1015 startCondition.block(); 1016 1017 // We should still be trying to open. 1018 assertNotCountedDown(timedOutLatch); 1019 // We should still be trying to open as we approach the timeout. 1020 setSystemClockInMsAndTriggerPendingMessages( 1021 /* nowMs= */ startTimeMs + TEST_CONNECT_TIMEOUT_MS - 1); 1022 assertNotCountedDown(timedOutLatch); 1023 // A redirect arrives just in time. 1024 dataSourceUnderTest.urlRequestCallback.onRedirectReceived( 1025 mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl1"); 1026 1027 long newTimeoutMs = 2 * TEST_CONNECT_TIMEOUT_MS - 1; 1028 setSystemClockInMsAndTriggerPendingMessages(/* nowMs= */ startTimeMs + newTimeoutMs - 1); 1029 // We should still be trying to open as we approach the new timeout. 1030 assertNotCountedDown(timedOutLatch); 1031 // A redirect arrives just in time. 1032 dataSourceUnderTest.urlRequestCallback.onRedirectReceived( 1033 mockUrlRequest, testUrlResponseInfo, "RandomRedirectedUrl2"); 1034 1035 newTimeoutMs = 3 * TEST_CONNECT_TIMEOUT_MS - 2; 1036 setSystemClockInMsAndTriggerPendingMessages(/* nowMs= */ startTimeMs + newTimeoutMs - 1); 1037 // We should still be trying to open as we approach the new timeout. 1038 assertNotCountedDown(timedOutLatch); 1039 // Now we timeout. 1040 setSystemClockInMsAndTriggerPendingMessages(/* nowMs= */ startTimeMs + newTimeoutMs + 10); 1041 timedOutLatch.await(); 1042 1043 verify(mockTransferListener, never()) 1044 .onTransferStart(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 1045 assertThat(openExceptions.get()).isEqualTo(1); 1046 } 1047 1048 @Test redirectParseAndAttachCookie_dataSourceDoesNotHandleSetCookie_followsRedirect()1049 public void redirectParseAndAttachCookie_dataSourceDoesNotHandleSetCookie_followsRedirect() 1050 throws HttpDataSourceException { 1051 mockSingleRedirectSuccess(); 1052 mockFollowRedirectSuccess(); 1053 1054 testResponseHeader.put("Set-Cookie", "testcookie=testcookie; Path=/video"); 1055 1056 dataSourceUnderTest.open(testDataSpec); 1057 verify(mockUrlRequestBuilder, never()).addHeader(eq("Cookie"), any(String.class)); 1058 verify(mockUrlRequest).followRedirect(); 1059 } 1060 1061 @Test 1062 public void testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeaders()1063 testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeaders() 1064 throws HttpDataSourceException { 1065 dataSourceUnderTest = 1066 new CronetDataSource( 1067 mockCronetEngine, 1068 mockExecutor, 1069 TEST_CONNECT_TIMEOUT_MS, 1070 TEST_READ_TIMEOUT_MS, 1071 true, // resetTimeoutOnRedirects 1072 Clock.DEFAULT, 1073 null, 1074 true); 1075 dataSourceUnderTest.addTransferListener(mockTransferListener); 1076 dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); 1077 1078 mockSingleRedirectSuccess(); 1079 1080 testResponseHeader.put("Set-Cookie", "testcookie=testcookie; Path=/video"); 1081 1082 dataSourceUnderTest.open(testDataSpec); 1083 verify(mockUrlRequestBuilder).addHeader(eq("Cookie"), any(String.class)); 1084 verify(mockUrlRequestBuilder, never()).addHeader(eq("Range"), any(String.class)); 1085 verify(mockUrlRequestBuilder, times(2)).addHeader("Content-Type", TEST_CONTENT_TYPE); 1086 verify(mockUrlRequest, never()).followRedirect(); 1087 verify(mockUrlRequest, times(2)).start(); 1088 } 1089 1090 @Test 1091 public void testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeadersIncludingByteRangeHeader()1092 testRedirectParseAndAttachCookie_dataSourceHandlesSetCookie_andPreservesOriginalRequestHeadersIncludingByteRangeHeader() 1093 throws HttpDataSourceException { 1094 testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000); 1095 dataSourceUnderTest = 1096 new CronetDataSource( 1097 mockCronetEngine, 1098 mockExecutor, 1099 TEST_CONNECT_TIMEOUT_MS, 1100 TEST_READ_TIMEOUT_MS, 1101 /* resetTimeoutOnRedirects= */ true, 1102 Clock.DEFAULT, 1103 /* defaultRequestProperties= */ null, 1104 /* handleSetCookieRequests= */ true); 1105 dataSourceUnderTest.addTransferListener(mockTransferListener); 1106 dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE); 1107 1108 mockSingleRedirectSuccess(); 1109 1110 testResponseHeader.put("Set-Cookie", "testcookie=testcookie; Path=/video"); 1111 1112 dataSourceUnderTest.open(testDataSpec); 1113 verify(mockUrlRequestBuilder).addHeader(eq("Cookie"), any(String.class)); 1114 verify(mockUrlRequestBuilder, times(2)).addHeader("Range", "bytes=1000-5999"); 1115 verify(mockUrlRequestBuilder, times(2)).addHeader("Content-Type", TEST_CONTENT_TYPE); 1116 verify(mockUrlRequest, never()).followRedirect(); 1117 verify(mockUrlRequest, times(2)).start(); 1118 } 1119 1120 @Test redirectNoSetCookieFollowsRedirect()1121 public void redirectNoSetCookieFollowsRedirect() throws HttpDataSourceException { 1122 mockSingleRedirectSuccess(); 1123 mockFollowRedirectSuccess(); 1124 1125 dataSourceUnderTest.open(testDataSpec); 1126 verify(mockUrlRequestBuilder, never()).addHeader(eq("Cookie"), any(String.class)); 1127 verify(mockUrlRequest).followRedirect(); 1128 } 1129 1130 @Test redirectNoSetCookieFollowsRedirect_dataSourceHandlesSetCookie()1131 public void redirectNoSetCookieFollowsRedirect_dataSourceHandlesSetCookie() 1132 throws HttpDataSourceException { 1133 dataSourceUnderTest = 1134 new CronetDataSource( 1135 mockCronetEngine, 1136 mockExecutor, 1137 TEST_CONNECT_TIMEOUT_MS, 1138 TEST_READ_TIMEOUT_MS, 1139 /* resetTimeoutOnRedirects= */ true, 1140 Clock.DEFAULT, 1141 /* defaultRequestProperties= */ null, 1142 /* handleSetCookieRequests= */ true); 1143 dataSourceUnderTest.addTransferListener(mockTransferListener); 1144 mockSingleRedirectSuccess(); 1145 mockFollowRedirectSuccess(); 1146 1147 dataSourceUnderTest.open(testDataSpec); 1148 verify(mockUrlRequestBuilder, never()).addHeader(eq("Cookie"), any(String.class)); 1149 verify(mockUrlRequest).followRedirect(); 1150 } 1151 1152 @Test exceptionFromTransferListener()1153 public void exceptionFromTransferListener() throws HttpDataSourceException { 1154 mockResponseStartSuccess(); 1155 1156 // Make mockTransferListener throw an exception in CronetDataSource.close(). Ensure that 1157 // the subsequent open() call succeeds. 1158 doThrow(new NullPointerException()) 1159 .when(mockTransferListener) 1160 .onTransferEnd(dataSourceUnderTest, testDataSpec, /* isNetwork= */ true); 1161 dataSourceUnderTest.open(testDataSpec); 1162 try { 1163 dataSourceUnderTest.close(); 1164 fail("NullPointerException expected"); 1165 } catch (NullPointerException e) { 1166 // Expected. 1167 } 1168 // Open should return successfully. 1169 dataSourceUnderTest.open(testDataSpec); 1170 } 1171 1172 @Test readFailure()1173 public void readFailure() throws HttpDataSourceException { 1174 mockResponseStartSuccess(); 1175 mockReadFailure(); 1176 1177 dataSourceUnderTest.open(testDataSpec); 1178 byte[] returnedBuffer = new byte[8]; 1179 try { 1180 dataSourceUnderTest.read(returnedBuffer, 0, 8); 1181 fail("dataSourceUnderTest.read() returned, but IOException expected"); 1182 } catch (IOException e) { 1183 // Expected. 1184 } 1185 } 1186 1187 @Test readByteBufferFailure()1188 public void readByteBufferFailure() throws HttpDataSourceException { 1189 mockResponseStartSuccess(); 1190 mockReadFailure(); 1191 1192 dataSourceUnderTest.open(testDataSpec); 1193 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(8); 1194 try { 1195 dataSourceUnderTest.read(returnedBuffer); 1196 fail("dataSourceUnderTest.read() returned, but IOException expected"); 1197 } catch (IOException e) { 1198 // Expected. 1199 } 1200 } 1201 1202 @Test readNonDirectedByteBufferFailure()1203 public void readNonDirectedByteBufferFailure() throws HttpDataSourceException { 1204 mockResponseStartSuccess(); 1205 mockReadFailure(); 1206 1207 dataSourceUnderTest.open(testDataSpec); 1208 byte[] returnedBuffer = new byte[8]; 1209 try { 1210 dataSourceUnderTest.read(ByteBuffer.wrap(returnedBuffer)); 1211 fail("dataSourceUnderTest.read() returned, but IllegalArgumentException expected"); 1212 } catch (IllegalArgumentException e) { 1213 // Expected. 1214 } 1215 } 1216 1217 @Test readInterrupted()1218 public void readInterrupted() throws HttpDataSourceException, InterruptedException { 1219 mockResponseStartSuccess(); 1220 dataSourceUnderTest.open(testDataSpec); 1221 1222 final ConditionVariable startCondition = buildReadStartedCondition(); 1223 final CountDownLatch timedOutLatch = new CountDownLatch(1); 1224 byte[] returnedBuffer = new byte[8]; 1225 Thread thread = 1226 new Thread() { 1227 @Override 1228 public void run() { 1229 try { 1230 dataSourceUnderTest.read(returnedBuffer, 0, 8); 1231 fail(); 1232 } catch (HttpDataSourceException e) { 1233 // Expected. 1234 assertThat(e.getCause() instanceof CronetDataSource.InterruptedIOException).isTrue(); 1235 timedOutLatch.countDown(); 1236 } 1237 } 1238 }; 1239 thread.start(); 1240 startCondition.block(); 1241 1242 assertNotCountedDown(timedOutLatch); 1243 // Now we interrupt. 1244 thread.interrupt(); 1245 timedOutLatch.await(); 1246 } 1247 1248 @Test readByteBufferInterrupted()1249 public void readByteBufferInterrupted() throws HttpDataSourceException, InterruptedException { 1250 mockResponseStartSuccess(); 1251 dataSourceUnderTest.open(testDataSpec); 1252 1253 final ConditionVariable startCondition = buildReadStartedCondition(); 1254 final CountDownLatch timedOutLatch = new CountDownLatch(1); 1255 ByteBuffer returnedBuffer = ByteBuffer.allocateDirect(8); 1256 Thread thread = 1257 new Thread() { 1258 @Override 1259 public void run() { 1260 try { 1261 dataSourceUnderTest.read(returnedBuffer); 1262 fail(); 1263 } catch (HttpDataSourceException e) { 1264 // Expected. 1265 assertThat(e.getCause() instanceof CronetDataSource.InterruptedIOException).isTrue(); 1266 timedOutLatch.countDown(); 1267 } 1268 } 1269 }; 1270 thread.start(); 1271 startCondition.block(); 1272 1273 assertNotCountedDown(timedOutLatch); 1274 // Now we interrupt. 1275 thread.interrupt(); 1276 timedOutLatch.await(); 1277 } 1278 1279 @Test allowDirectExecutor()1280 public void allowDirectExecutor() throws HttpDataSourceException { 1281 testDataSpec = new DataSpec(Uri.parse(TEST_URL), 1000, 5000); 1282 mockResponseStartSuccess(); 1283 1284 dataSourceUnderTest.open(testDataSpec); 1285 verify(mockUrlRequestBuilder).allowDirectExecutor(); 1286 } 1287 1288 // Helper methods. 1289 mockStatusResponse()1290 private void mockStatusResponse() { 1291 doAnswer( 1292 invocation -> { 1293 UrlRequest.StatusListener statusListener = 1294 (UrlRequest.StatusListener) invocation.getArguments()[0]; 1295 statusListener.onStatus(TEST_CONNECTION_STATUS); 1296 return null; 1297 }) 1298 .when(mockUrlRequest) 1299 .getStatus(any(UrlRequest.StatusListener.class)); 1300 } 1301 mockResponseStartSuccess()1302 private void mockResponseStartSuccess() { 1303 doAnswer( 1304 invocation -> { 1305 dataSourceUnderTest.urlRequestCallback.onResponseStarted( 1306 mockUrlRequest, testUrlResponseInfo); 1307 return null; 1308 }) 1309 .when(mockUrlRequest) 1310 .start(); 1311 } 1312 mockResponseStartRedirect()1313 private void mockResponseStartRedirect() { 1314 doAnswer( 1315 invocation -> { 1316 dataSourceUnderTest.urlRequestCallback.onRedirectReceived( 1317 mockUrlRequest, 1318 createUrlResponseInfo(307), // statusCode 1319 "http://redirect.location.com"); 1320 return null; 1321 }) 1322 .when(mockUrlRequest) 1323 .start(); 1324 } 1325 mockSingleRedirectSuccess()1326 private void mockSingleRedirectSuccess() { 1327 doAnswer( 1328 invocation -> { 1329 if (!redirectCalled) { 1330 redirectCalled = true; 1331 dataSourceUnderTest.urlRequestCallback.onRedirectReceived( 1332 mockUrlRequest, 1333 createUrlResponseInfoWithUrl("http://example.com/video", 300), 1334 "http://example.com/video/redirect"); 1335 } else { 1336 dataSourceUnderTest.urlRequestCallback.onResponseStarted( 1337 mockUrlRequest, testUrlResponseInfo); 1338 } 1339 return null; 1340 }) 1341 .when(mockUrlRequest) 1342 .start(); 1343 } 1344 mockFollowRedirectSuccess()1345 private void mockFollowRedirectSuccess() { 1346 doAnswer( 1347 invocation -> { 1348 dataSourceUnderTest.urlRequestCallback.onResponseStarted( 1349 mockUrlRequest, testUrlResponseInfo); 1350 return null; 1351 }) 1352 .when(mockUrlRequest) 1353 .followRedirect(); 1354 } 1355 mockResponseStartFailure()1356 private void mockResponseStartFailure() { 1357 doAnswer( 1358 invocation -> { 1359 dataSourceUnderTest.urlRequestCallback.onFailed( 1360 mockUrlRequest, 1361 createUrlResponseInfo(500), // statusCode 1362 mockNetworkException); 1363 return null; 1364 }) 1365 .when(mockUrlRequest) 1366 .start(); 1367 } 1368 mockReadSuccess(int position, int length)1369 private void mockReadSuccess(int position, int length) { 1370 final int[] positionAndRemaining = new int[] {position, length}; 1371 doAnswer( 1372 invocation -> { 1373 if (positionAndRemaining[1] == 0) { 1374 dataSourceUnderTest.urlRequestCallback.onSucceeded( 1375 mockUrlRequest, testUrlResponseInfo); 1376 } else { 1377 ByteBuffer inputBuffer = (ByteBuffer) invocation.getArguments()[0]; 1378 int readLength = Math.min(positionAndRemaining[1], inputBuffer.remaining()); 1379 inputBuffer.put(buildTestDataBuffer(positionAndRemaining[0], readLength)); 1380 positionAndRemaining[0] += readLength; 1381 positionAndRemaining[1] -= readLength; 1382 dataSourceUnderTest.urlRequestCallback.onReadCompleted( 1383 mockUrlRequest, testUrlResponseInfo, inputBuffer); 1384 } 1385 return null; 1386 }) 1387 .when(mockUrlRequest) 1388 .read(any(ByteBuffer.class)); 1389 } 1390 mockReadFailure()1391 private void mockReadFailure() { 1392 doAnswer( 1393 invocation -> { 1394 dataSourceUnderTest.urlRequestCallback.onFailed( 1395 mockUrlRequest, 1396 createUrlResponseInfo(500), // statusCode 1397 mockNetworkException); 1398 return null; 1399 }) 1400 .when(mockUrlRequest) 1401 .read(any(ByteBuffer.class)); 1402 } 1403 buildReadStartedCondition()1404 private ConditionVariable buildReadStartedCondition() { 1405 final ConditionVariable startedCondition = new ConditionVariable(); 1406 doAnswer( 1407 invocation -> { 1408 startedCondition.open(); 1409 return null; 1410 }) 1411 .when(mockUrlRequest) 1412 .read(any(ByteBuffer.class)); 1413 return startedCondition; 1414 } 1415 buildUrlRequestStartedCondition()1416 private ConditionVariable buildUrlRequestStartedCondition() { 1417 final ConditionVariable startedCondition = new ConditionVariable(); 1418 doAnswer( 1419 invocation -> { 1420 startedCondition.open(); 1421 return null; 1422 }) 1423 .when(mockUrlRequest) 1424 .start(); 1425 return startedCondition; 1426 } 1427 assertNotCountedDown(CountDownLatch countDownLatch)1428 private void assertNotCountedDown(CountDownLatch countDownLatch) throws InterruptedException { 1429 // We are asserting that another thread does not count down the latch. We therefore sleep some 1430 // time to give the other thread the chance to fail this test. 1431 Thread.sleep(50); 1432 assertThat(countDownLatch.getCount()).isGreaterThan(0L); 1433 } 1434 buildTestDataArray(int position, int length)1435 private static byte[] buildTestDataArray(int position, int length) { 1436 return buildTestDataBuffer(position, length).array(); 1437 } 1438 prefixZeros(byte[] data, int requiredLength)1439 public static byte[] prefixZeros(byte[] data, int requiredLength) { 1440 byte[] prefixedData = new byte[requiredLength]; 1441 System.arraycopy(data, 0, prefixedData, requiredLength - data.length, data.length); 1442 return prefixedData; 1443 } 1444 suffixZeros(byte[] data, int requiredLength)1445 public static byte[] suffixZeros(byte[] data, int requiredLength) { 1446 return Arrays.copyOf(data, requiredLength); 1447 } 1448 buildTestDataBuffer(int position, int length)1449 private static ByteBuffer buildTestDataBuffer(int position, int length) { 1450 ByteBuffer testBuffer = ByteBuffer.allocate(length); 1451 for (int i = 0; i < length; i++) { 1452 testBuffer.put((byte) (position + i)); 1453 } 1454 testBuffer.flip(); 1455 return testBuffer; 1456 } 1457 1458 // Returns a copy of what is remaining in the src buffer from the current position to capacity. copyByteBufferToArray(ByteBuffer src)1459 private static byte[] copyByteBufferToArray(ByteBuffer src) { 1460 if (src == null) { 1461 return null; 1462 } 1463 byte[] copy = new byte[src.remaining()]; 1464 int index = 0; 1465 while (src.hasRemaining()) { 1466 copy[index++] = src.get(); 1467 } 1468 return copy; 1469 } 1470 setSystemClockInMsAndTriggerPendingMessages(long nowMs)1471 private static void setSystemClockInMsAndTriggerPendingMessages(long nowMs) { 1472 SystemClock.setCurrentTimeMillis(nowMs); 1473 ShadowLooper.idleMainLooper(); 1474 } 1475 } 1476