• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 package org.chromium.net;
6 
7 import static com.google.common.truth.Truth.assertThat;
8 import static com.google.common.truth.Truth.assertWithMessage;
9 import static com.google.common.truth.TruthJUnit.assume;
10 
11 import static org.junit.Assert.assertThrows;
12 import static org.junit.Assert.fail;
13 
14 import static org.chromium.net.truth.UrlResponseInfoSubject.assertThat;
15 
16 import android.net.Network;
17 import android.os.Build;
18 import android.os.ConditionVariable;
19 import android.os.Process;
20 import android.os.StrictMode;
21 
22 import androidx.test.ext.junit.runners.AndroidJUnit4;
23 import androidx.test.filters.SmallTest;
24 
25 import org.jni_zero.NativeMethods;
26 import org.junit.After;
27 import org.junit.Before;
28 import org.junit.Rule;
29 import org.junit.Test;
30 import org.junit.runner.RunWith;
31 
32 import org.chromium.base.Log;
33 import org.chromium.base.test.util.DoNotBatch;
34 import org.chromium.net.CronetTestRule.CronetImplementation;
35 import org.chromium.net.CronetTestRule.IgnoreFor;
36 import org.chromium.net.CronetTestRule.RequiresMinAndroidApi;
37 import org.chromium.net.CronetTestRule.RequiresMinApi;
38 import org.chromium.net.NetworkChangeNotifierAutoDetect.ConnectivityManagerDelegate;
39 import org.chromium.net.TestUrlRequestCallback.FailureType;
40 import org.chromium.net.TestUrlRequestCallback.ResponseStep;
41 import org.chromium.net.apihelpers.UploadDataProviders;
42 import org.chromium.net.impl.CronetExceptionImpl;
43 import org.chromium.net.impl.CronetUrlRequest;
44 import org.chromium.net.impl.NetworkExceptionImpl;
45 import org.chromium.net.impl.UrlResponseInfoImpl;
46 import org.chromium.net.test.FailurePhase;
47 
48 import java.io.IOException;
49 import java.net.ConnectException;
50 import java.nio.ByteBuffer;
51 import java.util.AbstractMap;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.List;
55 import java.util.Map;
56 import java.util.concurrent.Executor;
57 import java.util.concurrent.ExecutorService;
58 import java.util.concurrent.Executors;
59 import java.util.concurrent.atomic.AtomicBoolean;
60 import java.util.regex.Matcher;
61 import java.util.regex.Pattern;
62 
63 /** Test functionality of CronetUrlRequest. */
64 @DoNotBatch(reason = "crbug/1459563")
65 @RunWith(AndroidJUnit4.class)
66 public class CronetUrlRequestTest {
67     private static final String TAG = CronetUrlRequestTest.class.getSimpleName();
68 
69     // URL used for base tests.
70     private static final String TEST_URL = "http://127.0.0.1:8000";
71 
72     @Rule public final CronetTestRule mTestRule = CronetTestRule.withAutomaticEngineStartup();
73 
74     private MockUrlRequestJobFactory mMockUrlRequestJobFactory;
75 
76     @Before
setUp()77     public void setUp() throws Exception {
78         assertThat(
79                         NativeTestServer.startNativeTestServer(
80                                 mTestRule.getTestFramework().getContext()))
81                 .isTrue();
82     }
83 
84     @After
tearDown()85     public void tearDown() throws Exception {
86         if (mMockUrlRequestJobFactory != null) {
87             mMockUrlRequestJobFactory.shutdown();
88         }
89         NativeTestServer.shutdownNativeTestServer();
90     }
91 
startAndWaitForComplete(String url)92     private TestUrlRequestCallback startAndWaitForComplete(String url) throws Exception {
93         TestUrlRequestCallback callback = new TestUrlRequestCallback();
94         // Create request.
95         UrlRequest.Builder builder =
96                 mTestRule
97                         .getTestFramework()
98                         .getEngine()
99                         .newUrlRequestBuilder(url, callback, callback.getExecutor());
100         UrlRequest urlRequest = builder.build();
101         urlRequest.start();
102         callback.blockForDone();
103         // Wait for all posted tasks to be executed to ensure there is no unhandled exception.
104         callback.shutdownExecutorAndWait();
105         assertThat(urlRequest.isDone()).isTrue();
106         return callback;
107     }
108 
checkResponseInfo( UrlResponseInfo responseInfo, String expectedUrl, int expectedHttpStatusCode, String expectedHttpStatusText)109     private void checkResponseInfo(
110             UrlResponseInfo responseInfo,
111             String expectedUrl,
112             int expectedHttpStatusCode,
113             String expectedHttpStatusText) {
114         assertThat(responseInfo).hasUrlThat().isEqualTo(expectedUrl);
115         assertThat(responseInfo.getUrlChain().get(responseInfo.getUrlChain().size() - 1))
116                 .isEqualTo(expectedUrl);
117         assertThat(responseInfo).hasHttpStatusCodeThat().isEqualTo(expectedHttpStatusCode);
118         assertThat(responseInfo).hasHttpStatusTextThat().isEqualTo(expectedHttpStatusText);
119         assertThat(responseInfo).wasNotCached();
120         assertThat(responseInfo.toString()).isNotEmpty();
121     }
122 
checkResponseInfoHeader( UrlResponseInfo responseInfo, String headerName, String headerValue)123     private void checkResponseInfoHeader(
124             UrlResponseInfo responseInfo, String headerName, String headerValue) {
125         Map<String, List<String>> responseHeaders = responseInfo.getAllHeaders();
126         List<String> header = responseHeaders.get(headerName);
127         assertThat(header).contains(headerValue);
128     }
129 
130     @Test
131     @SmallTest
testBuilderChecks()132     public void testBuilderChecks() throws Exception {
133         TestUrlRequestCallback callback = new TestUrlRequestCallback();
134 
135         NullPointerException e =
136                 assertThrows(
137                         NullPointerException.class,
138                         () ->
139                                 mTestRule
140                                         .getTestFramework()
141                                         .getEngine()
142                                         .newUrlRequestBuilder(
143                                                 null, callback, callback.getExecutor()));
144         assertThat(e).hasMessageThat().isEqualTo("URL is required.");
145 
146         e =
147                 assertThrows(
148                         NullPointerException.class,
149                         () ->
150                                 mTestRule
151                                         .getTestFramework()
152                                         .getEngine()
153                                         .newUrlRequestBuilder(
154                                                 NativeTestServer.getRedirectURL(),
155                                                 null,
156                                                 callback.getExecutor()));
157         assertThat(e).hasMessageThat().isEqualTo("Callback is required.");
158 
159         e =
160                 assertThrows(
161                         NullPointerException.class,
162                         () ->
163                                 mTestRule
164                                         .getTestFramework()
165                                         .getEngine()
166                                         .newUrlRequestBuilder(
167                                                 NativeTestServer.getRedirectURL(), callback, null));
168         assertThat(e).hasMessageThat().isEqualTo("Executor is required.");
169 
170         // Verify successful creation doesn't throw.
171         mTestRule
172                 .getTestFramework()
173                 .getEngine()
174                 .newUrlRequestBuilder(
175                         NativeTestServer.getRedirectURL(), callback, callback.getExecutor());
176     }
177 
178     @Test
179     @SmallTest
testSimpleGet()180     public void testSimpleGet() throws Exception {
181         String url = NativeTestServer.getEchoMethodURL();
182         TestUrlRequestCallback callback = startAndWaitForComplete(url);
183         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
184         // Default method is 'GET'.
185         assertThat(callback.mResponseAsString).isEqualTo("GET");
186         assertThat(callback.mRedirectCount).isEqualTo(0);
187         assertThat(ResponseStep.ON_SUCCEEDED).isEqualTo(callback.mResponseStep);
188         UrlResponseInfo urlResponseInfo =
189                 createUrlResponseInfo(
190                         new String[] {url},
191                         "OK",
192                         200,
193                         86,
194                         "Connection",
195                         "close",
196                         "Content-Length",
197                         "3",
198                         "Content-Type",
199                         "text/plain");
200         mTestRule.assertResponseEquals(urlResponseInfo, callback.getResponseInfoWithChecks());
201         checkResponseInfo(
202                 callback.getResponseInfoWithChecks(),
203                 NativeTestServer.getEchoMethodURL(),
204                 200,
205                 "OK");
206     }
207 
createUrlResponseInfo( String[] urls, String message, int statusCode, int receivedBytes, String... headers)208     UrlResponseInfo createUrlResponseInfo(
209             String[] urls, String message, int statusCode, int receivedBytes, String... headers) {
210         ArrayList<Map.Entry<String, String>> headersList = new ArrayList<>();
211         for (int i = 0; i < headers.length; i += 2) {
212             headersList.add(
213                     new AbstractMap.SimpleImmutableEntry<String, String>(
214                             headers[i], headers[i + 1]));
215         }
216         UrlResponseInfoImpl unknown =
217                 new UrlResponseInfoImpl(
218                         Arrays.asList(urls),
219                         statusCode,
220                         message,
221                         headersList,
222                         false,
223                         "unknown",
224                         ":0",
225                         receivedBytes);
226         return unknown;
227     }
228 
runConnectionMigrationTest(boolean disableConnectionMigration)229     void runConnectionMigrationTest(boolean disableConnectionMigration) {
230         // URLRequest load flags at net/base/load_flags_list.h.
231         int connectionMigrationLoadFlag =
232                 CronetUrlRequestTestJni.get().getConnectionMigrationDisableLoadFlag();
233         TestUrlRequestCallback callback = new TestUrlRequestCallback();
234         callback.setAutoAdvance(false);
235         // Create builder, start a request, and check if default load_flags are set correctly.
236         ExperimentalUrlRequest.Builder builder =
237                 (ExperimentalUrlRequest.Builder)
238                         mTestRule
239                                 .getTestFramework()
240                                 .getEngine()
241                                 .newUrlRequestBuilder(
242                                         NativeTestServer.getFileURL("/success.txt"),
243                                         callback,
244                                         callback.getExecutor());
245         // Disable connection migration.
246         if (disableConnectionMigration) builder.disableConnectionMigration();
247         UrlRequest urlRequest = builder.build();
248         urlRequest.start();
249         callback.waitForNextStep();
250         int loadFlags = CronetTestUtil.getLoadFlags(urlRequest);
251         if (disableConnectionMigration) {
252             assertThat(loadFlags & connectionMigrationLoadFlag)
253                     .isEqualTo(connectionMigrationLoadFlag);
254         } else {
255             assertThat(loadFlags & connectionMigrationLoadFlag).isEqualTo(0);
256         }
257         callback.setAutoAdvance(true);
258         callback.startNextRead(urlRequest);
259         callback.blockForDone();
260     }
261 
262     /** Tests that disabling connection migration sets the URLRequest load flag correctly. */
263     @Test
264     @SmallTest
265     @IgnoreFor(
266             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
267             reason = "bug.com/1494845: tests native implementation internals")
testLoadFlagsWithConnectionMigration()268     public void testLoadFlagsWithConnectionMigration() throws Exception {
269         runConnectionMigrationTest(/* disableConnectionMigration= */ false);
270         runConnectionMigrationTest(/* disableConnectionMigration= */ true);
271     }
272 
273     /**
274      * Tests a redirect by running it step-by-step. Also tests that delaying a request works as
275      * expected. To make sure there are no unexpected pending messages, does a GET between
276      * UrlRequest.Callback callbacks.
277      */
278     @Test
279     @SmallTest
testRedirectAsync()280     public void testRedirectAsync() throws Exception {
281         // Start the request and wait to see the redirect.
282         TestUrlRequestCallback callback = new TestUrlRequestCallback();
283         callback.setAutoAdvance(false);
284         UrlRequest.Builder builder =
285                 mTestRule
286                         .getTestFramework()
287                         .getEngine()
288                         .newUrlRequestBuilder(
289                                 NativeTestServer.getRedirectURL(),
290                                 callback,
291                                 callback.getExecutor());
292         UrlRequest urlRequest = builder.build();
293         urlRequest.start();
294         callback.waitForNextStep();
295 
296         // Check the redirect.
297         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RECEIVED_REDIRECT);
298         assertThat(callback.mRedirectResponseInfoList).hasSize(1);
299         checkResponseInfo(
300                 callback.mRedirectResponseInfoList.get(0),
301                 NativeTestServer.getRedirectURL(),
302                 302,
303                 "Found");
304         assertThat(callback.mRedirectResponseInfoList.get(0).getUrlChain()).hasSize(1);
305         assertThat(callback.mRedirectUrlList.get(0)).isEqualTo(NativeTestServer.getSuccessURL());
306         checkResponseInfoHeader(
307                 callback.mRedirectResponseInfoList.get(0), "redirect-header", "header-value");
308 
309         UrlResponseInfo expected =
310                 createUrlResponseInfo(
311                         new String[] {NativeTestServer.getRedirectURL()},
312                         "Found",
313                         302,
314                         73,
315                         "Location",
316                         "/success.txt",
317                         "redirect-header",
318                         "header-value");
319         mTestRule.assertResponseEquals(expected, callback.mRedirectResponseInfoList.get(0));
320 
321         // Wait for an unrelated request to finish. The request should not
322         // advance until followRedirect is invoked.
323         testSimpleGet();
324         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RECEIVED_REDIRECT);
325         assertThat(callback.mRedirectResponseInfoList).hasSize(1);
326 
327         // Follow the redirect and wait for the next set of headers.
328         urlRequest.followRedirect();
329         callback.waitForNextStep();
330 
331         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED);
332         assertThat(callback.mRedirectResponseInfoList).hasSize(1);
333         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
334         checkResponseInfo(
335                 callback.getResponseInfoWithChecks(), NativeTestServer.getSuccessURL(), 200, "OK");
336         assertThat(callback.getResponseInfoWithChecks())
337                 .hasUrlChainThat()
338                 .containsExactly(
339                         NativeTestServer.getRedirectURL(), NativeTestServer.getSuccessURL())
340                 .inOrder();
341 
342         // Wait for an unrelated request to finish. The request should not
343         // advance until read is invoked.
344         testSimpleGet();
345         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED);
346 
347         // One read should get all the characters, but best not to depend on
348         // how much is actually read from the socket at once.
349         while (!callback.isDone()) {
350             callback.startNextRead(urlRequest);
351             callback.waitForNextStep();
352             String response = callback.mResponseAsString;
353             ResponseStep step = callback.mResponseStep;
354             if (!callback.isDone()) {
355                 assertThat(step).isEqualTo(ResponseStep.ON_READ_COMPLETED);
356             }
357             // Should not receive any messages while waiting for another get,
358             // as the next read has not been started.
359             testSimpleGet();
360             assertThat(callback.mResponseAsString).isEqualTo(response);
361             assertThat(callback.mResponseStep).isEqualTo(step);
362         }
363         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_SUCCEEDED);
364         assertThat(callback.mResponseAsString).isEqualTo(NativeTestServer.SUCCESS_BODY);
365 
366         UrlResponseInfo urlResponseInfo =
367                 createUrlResponseInfo(
368                         new String[] {
369                             NativeTestServer.getRedirectURL(), NativeTestServer.getSuccessURL()
370                         },
371                         "OK",
372                         200,
373                         258,
374                         "Content-Type",
375                         "text/plain",
376                         "Access-Control-Allow-Origin",
377                         "*",
378                         "header-name",
379                         "header-value",
380                         "multi-header-name",
381                         "header-value1",
382                         "multi-header-name",
383                         "header-value2");
384 
385         mTestRule.assertResponseEquals(urlResponseInfo, callback.getResponseInfoWithChecks());
386         // Make sure there are no other pending messages, which would trigger
387         // asserts in TestUrlRequestCallback.
388         testSimpleGet();
389     }
390 
391     /** Tests redirect without location header doesn't cause a crash. */
392     @Test
393     @SmallTest
testRedirectWithNullLocationHeader()394     public void testRedirectWithNullLocationHeader() throws Exception {
395         String url = NativeTestServer.getFileURL("/redirect_broken_header.html");
396         TestUrlRequestCallback callback = new TestUrlRequestCallback();
397 
398         UrlRequest.Builder builder =
399                 mTestRule
400                         .getTestFramework()
401                         .getEngine()
402                         .newUrlRequestBuilder(url, callback, callback.getExecutor());
403         final UrlRequest urlRequest = builder.build();
404         urlRequest.start();
405         callback.blockForDone();
406         assertThat(callback.mResponseAsString)
407                 .isEqualTo(
408                         "<!DOCTYPE html>\n<html>\n<head>\n<title>Redirect</title>\n"
409                                 + "<p>Redirecting...</p>\n</head>\n</html>\n");
410         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_SUCCEEDED);
411         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(302);
412         assertThat(callback.mError).isNull();
413         assertThat(callback.mOnErrorCalled).isFalse();
414     }
415 
416     /** Tests onRedirectReceived after cancel doesn't cause a crash. */
417     @Test
418     @SmallTest
testOnRedirectReceivedAfterCancel()419     public void testOnRedirectReceivedAfterCancel() throws Exception {
420         final AtomicBoolean failedExpectation = new AtomicBoolean();
421         TestUrlRequestCallback callback =
422                 new TestUrlRequestCallback() {
423                     @Override
424                     public void onRedirectReceived(
425                             UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
426                         assertThat(mRedirectCount).isEqualTo(0);
427                         failedExpectation.compareAndSet(false, 0 != mRedirectCount);
428                         super.onRedirectReceived(request, info, newLocationUrl);
429                         // Cancel the request, so the second redirect will not be received.
430                         request.cancel();
431                     }
432 
433                     @Override
434                     public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
435                         failedExpectation.set(true);
436                         fail();
437                     }
438 
439                     @Override
440                     public void onReadCompleted(
441                             UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
442                         failedExpectation.set(true);
443                         fail();
444                     }
445 
446                     @Override
447                     public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
448                         failedExpectation.set(true);
449                         fail();
450                     }
451 
452                     @Override
453                     public void onFailed(
454                             UrlRequest request, UrlResponseInfo info, CronetException error) {
455                         failedExpectation.set(true);
456                         fail();
457                     }
458 
459                     @Override
460                     public void onCanceled(UrlRequest request, UrlResponseInfo info) {
461                         assertThat(mRedirectCount).isEqualTo(1);
462                         failedExpectation.compareAndSet(false, 1 != mRedirectCount);
463                         super.onCanceled(request, info);
464                     }
465                 };
466 
467         UrlRequest.Builder builder =
468                 mTestRule
469                         .getTestFramework()
470                         .getEngine()
471                         .newUrlRequestBuilder(
472                                 NativeTestServer.getMultiRedirectURL(),
473                                 callback,
474                                 callback.getExecutor());
475 
476         final UrlRequest urlRequest = builder.build();
477         urlRequest.start();
478         callback.blockForDone();
479         assertThat(failedExpectation.get()).isFalse();
480         // Check that only one redirect is received.
481         assertThat(callback.mRedirectCount).isEqualTo(1);
482         // Check that onCanceled is called.
483         assertThat(callback.mOnCanceledCalled).isTrue();
484     }
485 
486     @Test
487     @SmallTest
testNotFound()488     public void testNotFound() throws Exception {
489         String url = NativeTestServer.getFileURL("/notfound.html");
490         TestUrlRequestCallback callback = startAndWaitForComplete(url);
491         checkResponseInfo(callback.getResponseInfoWithChecks(), url, 404, "Not Found");
492         assertThat(callback.mResponseAsString)
493                 .isEqualTo(
494                         "<!DOCTYPE html>\n<html>\n<head>\n<title>Not found</title>\n"
495                                 + "<p>Test page loaded.</p>\n</head>\n</html>\n");
496         assertThat(callback.mRedirectCount).isEqualTo(0);
497         assertThat(ResponseStep.ON_SUCCEEDED).isEqualTo(callback.mResponseStep);
498     }
499 
500     // Checks that UrlRequest.Callback.onFailed is only called once in the case
501     // of ERR_CONTENT_LENGTH_MISMATCH, which has an unusual failure path.
502     // See http://crbug.com/468803.
503     @Test
504     @SmallTest
505     @IgnoreFor(
506             implementations = {CronetImplementation.FALLBACK},
507             reason = "No canonical exception to assert on")
testContentLengthMismatchFailsOnce()508     public void testContentLengthMismatchFailsOnce() throws Exception {
509         String url = NativeTestServer.getFileURL("/content_length_mismatch.html");
510         TestUrlRequestCallback callback = startAndWaitForComplete(url);
511         assertThat(callback.getResponseInfo()).hasHttpStatusCodeThat().isEqualTo(200);
512         // The entire response body will be read before the error is returned.
513         // This is because the network stack returns data as it's read from the
514         // socket, and the socket close message which triggers the error will
515         // only be passed along after all data has been read.
516         assertThat(callback.mResponseAsString)
517                 .isEqualTo("Response that lies about content length.");
518         assertThat(callback.mError)
519                 .hasMessageThat()
520                 .contains("Exception in CronetUrlRequest: net::ERR_CONTENT_LENGTH_MISMATCH");
521         // Wait for a couple round trips to make sure there are no pending
522         // onFailed messages. This test relies on checks in
523         // TestUrlRequestCallback catching a second onFailed call.
524         testSimpleGet();
525     }
526 
527     @Test
528     @SmallTest
testSetHttpMethod()529     public void testSetHttpMethod() throws Exception {
530         TestUrlRequestCallback callback = new TestUrlRequestCallback();
531         String methodName = "HEAD";
532         UrlRequest.Builder builder =
533                 mTestRule
534                         .getTestFramework()
535                         .getEngine()
536                         .newUrlRequestBuilder(
537                                 NativeTestServer.getEchoMethodURL(),
538                                 callback,
539                                 callback.getExecutor());
540         // Try to set 'null' method.
541         NullPointerException e =
542                 assertThrows(NullPointerException.class, () -> builder.setHttpMethod(null));
543         assertThat(e).hasMessageThat().isEqualTo("Method is required.");
544 
545         builder.setHttpMethod(methodName);
546         builder.build().start();
547         callback.blockForDone();
548         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
549         assertThat(callback.mHttpResponseDataLength).isEqualTo(0);
550     }
551 
552     @Test
553     @SmallTest
testBadMethod()554     public void testBadMethod() throws Exception {
555         TestUrlRequestCallback callback = new TestUrlRequestCallback();
556         UrlRequest.Builder builder =
557                 mTestRule
558                         .getTestFramework()
559                         .getEngine()
560                         .newUrlRequestBuilder(TEST_URL, callback, callback.getExecutor());
561         builder.setHttpMethod("bad:method!");
562         IllegalArgumentException e =
563                 assertThrows(IllegalArgumentException.class, () -> builder.build().start());
564         assertThat(e).hasMessageThat().isEqualTo("Invalid http method bad:method!");
565     }
566 
567     @Test
568     @SmallTest
testBadHeaderName()569     public void testBadHeaderName() throws Exception {
570         TestUrlRequestCallback callback = new TestUrlRequestCallback();
571         UrlRequest.Builder builder =
572                 mTestRule
573                         .getTestFramework()
574                         .getEngine()
575                         .newUrlRequestBuilder(TEST_URL, callback, callback.getExecutor());
576         builder.addHeader("header:name", "headervalue");
577         IllegalArgumentException e =
578                 assertThrows(IllegalArgumentException.class, () -> builder.build().start());
579         if (mTestRule.implementationUnderTest() == CronetImplementation.AOSP_PLATFORM &&
580                 !mTestRule.isRunningInAOSP()) {
581             // TODO(b/307234565): Remove check once chromium emulator has latest changes.
582             assertThat(e).hasMessageThat().isEqualTo("Invalid header header:name=headervalue");
583         } else {
584             assertThat(e).hasMessageThat().isEqualTo("Invalid header with headername: header:name");
585         }
586     }
587 
588     @Test
589     @SmallTest
testAcceptEncodingIgnored()590     public void testAcceptEncodingIgnored() throws Exception {
591         TestUrlRequestCallback callback = new TestUrlRequestCallback();
592         UrlRequest.Builder builder =
593                 mTestRule
594                         .getTestFramework()
595                         .getEngine()
596                         .newUrlRequestBuilder(
597                                 NativeTestServer.getEchoAllHeadersURL(),
598                                 callback,
599                                 callback.getExecutor());
600         // This line should eventually throw an exception, once callers have migrated
601         builder.addHeader("accept-encoding", "foozip");
602         builder.build().start();
603         callback.blockForDone();
604         assertThat(callback.mResponseAsString).doesNotContain("foozip");
605     }
606 
607     @Test
608     @SmallTest
testBadHeaderValue()609     public void testBadHeaderValue() throws Exception {
610         TestUrlRequestCallback callback = new TestUrlRequestCallback();
611         UrlRequest.Builder builder =
612                 mTestRule
613                         .getTestFramework()
614                         .getEngine()
615                         .newUrlRequestBuilder(TEST_URL, callback, callback.getExecutor());
616         builder.addHeader("headername", "bad header\r\nvalue");
617         IllegalArgumentException e =
618                 assertThrows(IllegalArgumentException.class, () -> builder.build().start());
619         if (mTestRule.implementationUnderTest() == CronetImplementation.AOSP_PLATFORM &&
620                 !mTestRule.isRunningInAOSP()) {
621             // TODO(b/307234565): Remove check once chromium emulator has latest changes.
622             assertThat(e)
623                     .hasMessageThat()
624                     .isEqualTo("Invalid header headername=bad header\r\nvalue");
625         } else {
626             assertThat(e).hasMessageThat().isEqualTo("Invalid header with headername: headername");
627         }
628     }
629 
630     @Test
631     @SmallTest
testAddHeader()632     public void testAddHeader() throws Exception {
633         TestUrlRequestCallback callback = new TestUrlRequestCallback();
634         String headerName = "header-name";
635         String headerValue = "header-value";
636         UrlRequest.Builder builder =
637                 mTestRule
638                         .getTestFramework()
639                         .getEngine()
640                         .newUrlRequestBuilder(
641                                 NativeTestServer.getEchoHeaderURL(headerName),
642                                 callback,
643                                 callback.getExecutor());
644 
645         builder.addHeader(headerName, headerValue);
646         builder.build().start();
647         callback.blockForDone();
648         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
649         assertThat(callback.mResponseAsString).isEqualTo(headerValue);
650     }
651 
652     @Test
653     @SmallTest
testMultiRequestHeaders()654     public void testMultiRequestHeaders() throws Exception {
655         TestUrlRequestCallback callback = new TestUrlRequestCallback();
656         String headerName = "header-name";
657         String headerValue1 = "header-value1";
658         String headerValue2 = "header-value2";
659         UrlRequest.Builder builder =
660                 mTestRule
661                         .getTestFramework()
662                         .getEngine()
663                         .newUrlRequestBuilder(
664                                 NativeTestServer.getEchoAllHeadersURL(),
665                                 callback,
666                                 callback.getExecutor());
667         builder.addHeader(headerName, headerValue1);
668         builder.addHeader(headerName, headerValue2);
669         builder.build().start();
670         callback.blockForDone();
671         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
672         String headers = callback.mResponseAsString;
673         Pattern pattern = Pattern.compile(headerName + ":\\s(.*)\\r\\n");
674         Matcher matcher = pattern.matcher(headers);
675         List<String> actualValues = new ArrayList<String>();
676         while (matcher.find()) {
677             actualValues.add(matcher.group(1));
678         }
679         assertThat(actualValues).containsExactly("header-value2");
680     }
681 
682     @Test
683     @SmallTest
testCustomReferer_verbatim()684     public void testCustomReferer_verbatim() throws Exception {
685         TestUrlRequestCallback callback = new TestUrlRequestCallback();
686         String refererName = "Referer";
687         String refererValue = "http://example.com/";
688         UrlRequest.Builder builder =
689                 mTestRule
690                         .getTestFramework()
691                         .getEngine()
692                         .newUrlRequestBuilder(
693                                 NativeTestServer.getEchoHeaderURL(refererName),
694                                 callback,
695                                 callback.getExecutor());
696         builder.addHeader(refererName, refererValue);
697         builder.build().start();
698         callback.blockForDone();
699         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
700         assertThat(callback.mResponseAsString).isEqualTo(refererValue);
701     }
702 
703     @Test
704     @SmallTest
705     @IgnoreFor(
706             implementations = {CronetImplementation.FALLBACK},
707             reason = "This is not the case for the fallback implementation")
testCustomReferer_changeToCanonical()708     public void testCustomReferer_changeToCanonical() throws Exception {
709         TestUrlRequestCallback callback = new TestUrlRequestCallback();
710         String refererName = "Referer";
711         String refererValueNoTrailingSlash = "http://example.com";
712         UrlRequest.Builder builder =
713                 mTestRule
714                         .getTestFramework()
715                         .getEngine()
716                         .newUrlRequestBuilder(
717                                 NativeTestServer.getEchoHeaderURL(refererName),
718                                 callback,
719                                 callback.getExecutor());
720         builder.addHeader(refererName, refererValueNoTrailingSlash);
721         builder.build().start();
722         callback.blockForDone();
723         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
724         assertThat(callback.mResponseAsString).isEqualTo(refererValueNoTrailingSlash + "/");
725     }
726 
727     @Test
728     @SmallTest
729     @IgnoreFor(
730             implementations = {CronetImplementation.FALLBACK},
731             reason = "This is not the case for the fallback implementation")
testCustomReferer_discardInvalid()732     public void testCustomReferer_discardInvalid() throws Exception {
733         TestUrlRequestCallback callback = new TestUrlRequestCallback();
734         String refererName = "Referer";
735         String invalidRefererValue = "foobar";
736         UrlRequest.Builder builder =
737                 mTestRule
738                         .getTestFramework()
739                         .getEngine()
740                         .newUrlRequestBuilder(
741                                 NativeTestServer.getEchoHeaderURL(refererName),
742                                 callback,
743                                 callback.getExecutor());
744         builder.addHeader(refererName, invalidRefererValue);
745         builder.build().start();
746         callback.blockForDone();
747         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
748         assertThat(callback.mResponseAsString).isEqualTo("Header not found. :(");
749     }
750 
751     @Test
752     @SmallTest
testCustomUserAgent()753     public void testCustomUserAgent() throws Exception {
754         TestUrlRequestCallback callback = new TestUrlRequestCallback();
755         String userAgentName = "User-Agent";
756         String userAgentValue = "User-Agent-Value";
757         UrlRequest.Builder builder =
758                 mTestRule
759                         .getTestFramework()
760                         .getEngine()
761                         .newUrlRequestBuilder(
762                                 NativeTestServer.getEchoHeaderURL(userAgentName),
763                                 callback,
764                                 callback.getExecutor());
765         builder.addHeader(userAgentName, userAgentValue);
766         builder.build().start();
767         callback.blockForDone();
768         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
769         assertThat(callback.mResponseAsString).isEqualTo(userAgentValue);
770     }
771 
772     @Test
773     @SmallTest
testDefaultUserAgent()774     public void testDefaultUserAgent() throws Exception {
775         TestUrlRequestCallback callback = new TestUrlRequestCallback();
776         String headerName = "User-Agent";
777         UrlRequest.Builder builder =
778                 mTestRule
779                         .getTestFramework()
780                         .getEngine()
781                         .newUrlRequestBuilder(
782                                 NativeTestServer.getEchoHeaderURL(headerName),
783                                 callback,
784                                 callback.getExecutor());
785         builder.build().start();
786         callback.blockForDone();
787         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
788         assertWithMessage(
789                         "Default User-Agent should contain Cronet/n.n.n.n but is "
790                                 + callback.mResponseAsString)
791                 .that(callback.mResponseAsString)
792                 .matches(Pattern.compile(".+Cronet/\\d+\\.\\d+\\.\\d+\\.\\d+.+"));
793     }
794 
795     @Test
796     @SmallTest
797     @IgnoreFor(
798             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
799             reason = "crbug.com/1495309: Enable once we drop MockUrlRequestJobFactory")
testMockSuccess()800     public void testMockSuccess() throws Exception {
801         mMockUrlRequestJobFactory =
802                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
803         TestUrlRequestCallback callback = startAndWaitForComplete(NativeTestServer.getSuccessURL());
804         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
805         assertThat(callback.mRedirectResponseInfoList).isEmpty();
806         assertThat(callback.mHttpResponseDataLength).isNotEqualTo(0);
807         assertThat(ResponseStep.ON_SUCCEEDED).isEqualTo(callback.mResponseStep);
808         Map<String, List<String>> responseHeaders =
809                 callback.getResponseInfoWithChecks().getAllHeaders();
810         assertThat(responseHeaders).containsEntry("header-name", Arrays.asList("header-value"));
811         assertThat(responseHeaders)
812                 .containsEntry(
813                         "multi-header-name", Arrays.asList("header-value1", "header-value2"));
814     }
815 
816     @Test
817     @SmallTest
testResponseHeadersList()818     public void testResponseHeadersList() throws Exception {
819         TestUrlRequestCallback callback = startAndWaitForComplete(NativeTestServer.getSuccessURL());
820         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
821         List<Map.Entry<String, String>> responseHeaders =
822                 callback.getResponseInfoWithChecks().getAllHeadersAsList();
823 
824         assertThat(new AbstractMap.SimpleEntry<>("Content-Type", "text/plain"))
825                 .isEqualTo(responseHeaders.get(0));
826         assertThat(new AbstractMap.SimpleEntry<>("Access-Control-Allow-Origin", "*"))
827                 .isEqualTo(responseHeaders.get(1));
828         assertThat(new AbstractMap.SimpleEntry<>("header-name", "header-value"))
829                 .isEqualTo(responseHeaders.get(2));
830         assertThat(new AbstractMap.SimpleEntry<>("multi-header-name", "header-value1"))
831                 .isEqualTo(responseHeaders.get(3));
832         assertThat(new AbstractMap.SimpleEntry<>("multi-header-name", "header-value2"))
833                 .isEqualTo(responseHeaders.get(4));
834     }
835 
836     @Test
837     @SmallTest
838     @IgnoreFor(
839             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
840             reason = "crbug.com/1495309: Enable once we drop MockUrlRequestJobFactory")
testMockMultiRedirect()841     public void testMockMultiRedirect() throws Exception {
842         mMockUrlRequestJobFactory =
843                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
844         TestUrlRequestCallback callback =
845                 startAndWaitForComplete(NativeTestServer.getMultiRedirectURL());
846         UrlResponseInfo mResponseInfo = callback.getResponseInfoWithChecks();
847         assertThat(callback.mRedirectCount).isEqualTo(2);
848         assertThat(mResponseInfo).hasHttpStatusCodeThat().isEqualTo(200);
849         assertThat(callback.mRedirectResponseInfoList).hasSize(2);
850 
851         // Check first redirect (multiredirect.html -> redirect.html)
852         UrlResponseInfo firstExpectedResponseInfo =
853                 createUrlResponseInfo(
854                         new String[] {NativeTestServer.getMultiRedirectURL()},
855                         "Found",
856                         302,
857                         76,
858                         "Location",
859                         "/redirect.html",
860                         "redirect-header0",
861                         "header-value");
862         UrlResponseInfo firstRedirectResponseInfo = callback.mRedirectResponseInfoList.get(0);
863         mTestRule.assertResponseEquals(firstExpectedResponseInfo, firstRedirectResponseInfo);
864 
865         // Check second redirect (redirect.html -> success.txt)
866         UrlResponseInfo secondExpectedResponseInfo =
867                 createUrlResponseInfo(
868                         new String[] {
869                             NativeTestServer.getMultiRedirectURL(),
870                             NativeTestServer.getRedirectURL(),
871                             NativeTestServer.getSuccessURL()
872                         },
873                         "OK",
874                         200,
875                         334,
876                         "Content-Type",
877                         "text/plain",
878                         "Access-Control-Allow-Origin",
879                         "*",
880                         "header-name",
881                         "header-value",
882                         "multi-header-name",
883                         "header-value1",
884                         "multi-header-name",
885                         "header-value2");
886 
887         mTestRule.assertResponseEquals(secondExpectedResponseInfo, mResponseInfo);
888         assertThat(callback.mHttpResponseDataLength).isNotEqualTo(0);
889         assertThat(callback.mRedirectCount).isEqualTo(2);
890         assertThat(ResponseStep.ON_SUCCEEDED).isEqualTo(callback.mResponseStep);
891     }
892 
893     @Test
894     @SmallTest
895     @IgnoreFor(
896             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
897             reason = "crbug.com/1495309: Enable once we drop MockUrlRequestJobFactory")
testMockNotFound()898     public void testMockNotFound() throws Exception {
899         mMockUrlRequestJobFactory =
900                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
901         TestUrlRequestCallback callback =
902                 startAndWaitForComplete(NativeTestServer.getNotFoundURL());
903         UrlResponseInfo expected =
904                 createUrlResponseInfo(
905                         new String[] {NativeTestServer.getNotFoundURL()}, "Not Found", 404, 120);
906         mTestRule.assertResponseEquals(expected, callback.getResponseInfoWithChecks());
907         assertThat(callback.mHttpResponseDataLength).isNotEqualTo(0);
908         assertThat(callback.mRedirectCount).isEqualTo(0);
909         assertThat(callback.mOnErrorCalled).isFalse();
910         assertThat(ResponseStep.ON_SUCCEEDED).isEqualTo(callback.mResponseStep);
911     }
912 
913     @Test
914     @SmallTest
915     @IgnoreFor(
916             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
917             reason = "crbug.com/1495309: Enable once we drop MockUrlRequestJobFactory")
testMockStartAsyncError()918     public void testMockStartAsyncError() throws Exception {
919         mMockUrlRequestJobFactory =
920                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
921         final int arbitraryNetError = -3;
922         TestUrlRequestCallback callback =
923                 startAndWaitForComplete(
924                         MockUrlRequestJobFactory.getMockUrlWithFailure(
925                                 FailurePhase.START, arbitraryNetError));
926         assertThat(callback.getResponseInfo()).isNull();
927         assertThat(callback.mError).isNotNull();
928         assertThat(((NetworkException) callback.mError).getCronetInternalErrorCode())
929                 .isEqualTo(arbitraryNetError);
930         assertThat(callback.mRedirectCount).isEqualTo(0);
931         assertThat(callback.mOnErrorCalled).isTrue();
932         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_FAILED);
933     }
934 
935     @Test
936     @SmallTest
937     @IgnoreFor(
938             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
939             reason = "crbug.com/1495309: Enable once we drop MockUrlRequestJobFactory")
testMockReadDataSyncError()940     public void testMockReadDataSyncError() throws Exception {
941         mMockUrlRequestJobFactory =
942                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
943         final int arbitraryNetError = -4;
944         TestUrlRequestCallback callback =
945                 startAndWaitForComplete(
946                         MockUrlRequestJobFactory.getMockUrlWithFailure(
947                                 FailurePhase.READ_SYNC, arbitraryNetError));
948         assertThat(callback.getResponseInfo()).hasHttpStatusCodeThat().isEqualTo(200);
949         assertThat(callback.getResponseInfo()).hasReceivedByteCountThat().isEqualTo(15);
950         assertThat(callback.mError).isNotNull();
951         assertThat(((NetworkException) callback.mError).getCronetInternalErrorCode())
952                 .isEqualTo(arbitraryNetError);
953         assertThat(callback.mRedirectCount).isEqualTo(0);
954         assertThat(callback.mOnErrorCalled).isTrue();
955         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_FAILED);
956     }
957 
958     @Test
959     @SmallTest
960     @IgnoreFor(
961             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
962             reason = "crbug.com/1495309: Enable once we drop MockUrlRequestJobFactory")
testMockReadDataAsyncError()963     public void testMockReadDataAsyncError() throws Exception {
964         mMockUrlRequestJobFactory =
965                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
966         final int arbitraryNetError = -5;
967         TestUrlRequestCallback callback =
968                 startAndWaitForComplete(
969                         MockUrlRequestJobFactory.getMockUrlWithFailure(
970                                 FailurePhase.READ_ASYNC, arbitraryNetError));
971         assertThat(callback.getResponseInfo()).hasHttpStatusCodeThat().isEqualTo(200);
972         assertThat(callback.getResponseInfo()).hasReceivedByteCountThat().isEqualTo(15);
973         assertThat(callback.mError).isNotNull();
974         assertThat(((NetworkException) callback.mError).getCronetInternalErrorCode())
975                 .isEqualTo(arbitraryNetError);
976         assertThat(callback.mRedirectCount).isEqualTo(0);
977         assertThat(callback.mOnErrorCalled).isTrue();
978         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_FAILED);
979     }
980 
981     /** Tests that request continues when client certificate is requested. */
982     @Test
983     @SmallTest
984     @IgnoreFor(
985             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
986             reason = "crbug.com/1495309: Enable once we drop MockUrlRequestJobFactory")
testMockClientCertificateRequested()987     public void testMockClientCertificateRequested() throws Exception {
988         mMockUrlRequestJobFactory =
989                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
990         TestUrlRequestCallback callback =
991                 startAndWaitForComplete(
992                         MockUrlRequestJobFactory.getMockUrlForClientCertificateRequest());
993         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
994         assertThat(callback.mResponseAsString).isEqualTo("data");
995         assertThat(callback.mRedirectCount).isEqualTo(0);
996         assertThat(callback.mError).isNull();
997         assertThat(callback.mOnErrorCalled).isFalse();
998     }
999 
1000     /** Tests that an SSL cert error will be reported via {@link UrlRequest.Callback#onFailed}. */
1001     @Test
1002     @SmallTest
1003     @IgnoreFor(
1004             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1005             reason = "crbug.com/1495309: Enable once we drop MockUrlRequestJobFactory")
testMockSSLCertificateError()1006     public void testMockSSLCertificateError() throws Exception {
1007         mMockUrlRequestJobFactory =
1008                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
1009         TestUrlRequestCallback callback =
1010                 startAndWaitForComplete(
1011                         MockUrlRequestJobFactory.getMockUrlForSSLCertificateError());
1012         assertThat(callback.getResponseInfo()).isNull();
1013         assertThat(callback.mOnErrorCalled).isTrue();
1014         assertThat(callback.mError)
1015                 .hasMessageThat()
1016                 .contains("Exception in CronetUrlRequest: net::ERR_CERT_DATE_INVALID");
1017         assertThat(((NetworkException) callback.mError).getCronetInternalErrorCode())
1018                 .isEqualTo(-201);
1019         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_FAILED);
1020     }
1021 
1022     /** Checks that the buffer is updated correctly, when starting at an offset. */
1023     @Test
1024     @SmallTest
1025     @IgnoreFor(
1026             implementations = {CronetImplementation.FALLBACK},
1027             reason = "No canonical exception to assert on")
testSimpleGetBufferUpdates()1028     public void testSimpleGetBufferUpdates() throws Exception {
1029         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1030         callback.setAutoAdvance(false);
1031         // Since the default method is "GET", the expected response body is also
1032         // "GET".
1033         UrlRequest.Builder builder =
1034                 mTestRule
1035                         .getTestFramework()
1036                         .getEngine()
1037                         .newUrlRequestBuilder(
1038                                 NativeTestServer.getEchoMethodURL(),
1039                                 callback,
1040                                 callback.getExecutor());
1041         UrlRequest urlRequest = builder.build();
1042         urlRequest.start();
1043         callback.waitForNextStep();
1044 
1045         ByteBuffer readBuffer = ByteBuffer.allocateDirect(5);
1046         readBuffer.put("FOR".getBytes());
1047         assertThat(readBuffer.position()).isEqualTo(3);
1048 
1049         // Read first two characters of the response ("GE"). It's theoretically
1050         // possible to need one read per character, though in practice,
1051         // shouldn't happen.
1052         while (callback.mResponseAsString.length() < 2) {
1053             assertThat(callback.isDone()).isFalse();
1054             callback.startNextRead(urlRequest, readBuffer);
1055             callback.waitForNextStep();
1056         }
1057 
1058         // Make sure the two characters were read.
1059         assertThat(callback.mResponseAsString).isEqualTo("GE");
1060 
1061         // Check the contents of the entire buffer. The first 3 characters
1062         // should not have been changed, and the last two should be the first
1063         // two characters from the response.
1064         assertThat(bufferContentsToString(readBuffer, 0, 5)).isEqualTo("FORGE");
1065         // The limit and position should be 5.
1066         assertThat(readBuffer.limit()).isEqualTo(5);
1067         assertThat(readBuffer.position()).isEqualTo(5);
1068 
1069         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_READ_COMPLETED);
1070 
1071         // Start reading from position 3. Since the only remaining character
1072         // from the response is a "T", when the read completes, the buffer
1073         // should contain "FORTE", with a position() of 4 and a limit() of 5.
1074         readBuffer.position(3);
1075         callback.startNextRead(urlRequest, readBuffer);
1076         callback.waitForNextStep();
1077 
1078         // Make sure all three characters of the response have now been read.
1079         assertThat(callback.mResponseAsString).isEqualTo("GET");
1080 
1081         // Check the entire contents of the buffer. Only the third character
1082         // should have been modified.
1083         assertThat(bufferContentsToString(readBuffer, 0, 5)).isEqualTo("FORTE");
1084 
1085         // Make sure position and limit were updated correctly.
1086         assertThat(readBuffer.position()).isEqualTo(4);
1087         assertThat(readBuffer.limit()).isEqualTo(5);
1088 
1089         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_READ_COMPLETED);
1090 
1091         // One more read attempt. The request should complete.
1092         readBuffer.position(1);
1093         readBuffer.limit(5);
1094         callback.startNextRead(urlRequest, readBuffer);
1095         callback.waitForNextStep();
1096 
1097         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1098         assertThat(callback.mResponseAsString).isEqualTo("GET");
1099         checkResponseInfo(
1100                 callback.getResponseInfoWithChecks(),
1101                 NativeTestServer.getEchoMethodURL(),
1102                 200,
1103                 "OK");
1104 
1105         // Check that buffer contents were not modified.
1106         assertThat(bufferContentsToString(readBuffer, 0, 5)).isEqualTo("FORTE");
1107 
1108         // Position should not have been modified, since nothing was read.
1109         assertThat(readBuffer.position()).isEqualTo(1);
1110         // Limit should be unchanged as always.
1111         assertThat(readBuffer.limit()).isEqualTo(5);
1112 
1113         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_SUCCEEDED);
1114 
1115         // Make sure there are no other pending messages, which would trigger
1116         // asserts in TestUrlRequestCallback.
1117         testSimpleGet();
1118     }
1119 
1120     @Test
1121     @SmallTest
testBadBuffers()1122     public void testBadBuffers() throws Exception {
1123         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1124         callback.setAutoAdvance(false);
1125         UrlRequest.Builder builder =
1126                 mTestRule
1127                         .getTestFramework()
1128                         .getEngine()
1129                         .newUrlRequestBuilder(
1130                                 NativeTestServer.getEchoMethodURL(),
1131                                 callback,
1132                                 callback.getExecutor());
1133         UrlRequest urlRequest = builder.build();
1134         urlRequest.start();
1135         callback.waitForNextStep();
1136 
1137         // Try to read using a full buffer.
1138         ByteBuffer readBuffer = ByteBuffer.allocateDirect(4);
1139         readBuffer.put("full".getBytes());
1140         IllegalArgumentException e =
1141                 assertThrows(IllegalArgumentException.class, () -> urlRequest.read(readBuffer));
1142         assertThat(e).hasMessageThat().isEqualTo("ByteBuffer is already full.");
1143 
1144         // Try to read using a non-direct buffer.
1145         ByteBuffer readBuffer1 = ByteBuffer.allocate(5);
1146         e = assertThrows(IllegalArgumentException.class, () -> urlRequest.read(readBuffer1));
1147         assertThat(e).hasMessageThat().isEqualTo("byteBuffer must be a direct ByteBuffer.");
1148 
1149         // Finish the request with a direct ByteBuffer.
1150         callback.setAutoAdvance(true);
1151         ByteBuffer readBuffer2 = ByteBuffer.allocateDirect(5);
1152         urlRequest.read(readBuffer2);
1153         callback.blockForDone();
1154         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1155         assertThat(callback.mResponseAsString).isEqualTo("GET");
1156     }
1157 
1158     @Test
1159     @SmallTest
testNoIoInCancel()1160     public void testNoIoInCancel() throws Exception {
1161         final TestUrlRequestCallback callback = new TestUrlRequestCallback();
1162         callback.setAutoAdvance(false);
1163         final UrlRequest urlRequest =
1164                 mTestRule
1165                         .getTestFramework()
1166                         .getEngine()
1167                         .newUrlRequestBuilder(
1168                                 NativeTestServer.getEchoHeaderURL("blah-header"),
1169                                 callback,
1170                                 callback.getExecutor())
1171                         .addHeader("blah-header", "blahblahblah")
1172                         .build();
1173         urlRequest.start();
1174         callback.waitForNextStep();
1175         callback.startNextRead(urlRequest, ByteBuffer.allocateDirect(4));
1176         callback.waitForNextStep();
1177         StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
1178         StrictMode.setThreadPolicy(
1179                 new StrictMode.ThreadPolicy.Builder()
1180                         .detectAll()
1181                         .penaltyDeath()
1182                         .penaltyLog()
1183                         .build());
1184         try {
1185             urlRequest.cancel();
1186         } finally {
1187             StrictMode.setThreadPolicy(oldPolicy);
1188         }
1189         callback.blockForDone();
1190         assertThat(callback.mOnCanceledCalled).isEqualTo(true);
1191     }
1192 
1193     @Test
1194     @SmallTest
testUnexpectedReads()1195     public void testUnexpectedReads() throws Exception {
1196         final TestUrlRequestCallback callback = new TestUrlRequestCallback();
1197         callback.setAutoAdvance(false);
1198         final UrlRequest urlRequest =
1199                 mTestRule
1200                         .getTestFramework()
1201                         .getEngine()
1202                         .newUrlRequestBuilder(
1203                                 NativeTestServer.getRedirectURL(), callback, callback.getExecutor())
1204                         .build();
1205 
1206         // Try to read before starting request.
1207         assertThrows(IllegalStateException.class, () -> callback.startNextRead(urlRequest));
1208 
1209         // Verify reading right after start throws an assertion. Both must be
1210         // invoked on the Executor thread, to prevent receiving data until after
1211         // startNextRead has been invoked.
1212         Runnable startAndRead =
1213                 new Runnable() {
1214                     @Override
1215                     public void run() {
1216                         urlRequest.start();
1217                         assertThrows(
1218                                 IllegalStateException.class,
1219                                 () -> callback.startNextRead(urlRequest));
1220                     }
1221                 };
1222         callback.getExecutor().submit(startAndRead).get();
1223         callback.waitForNextStep();
1224 
1225         assertThat(ResponseStep.ON_RECEIVED_REDIRECT).isEqualTo(callback.mResponseStep);
1226         // Try to read after the redirect.
1227         assertThrows(IllegalStateException.class, () -> callback.startNextRead(urlRequest));
1228         urlRequest.followRedirect();
1229         callback.waitForNextStep();
1230 
1231         assertThat(ResponseStep.ON_RESPONSE_STARTED).isEqualTo(callback.mResponseStep);
1232         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1233 
1234         while (!callback.isDone()) {
1235             Runnable readTwice =
1236                     new Runnable() {
1237                         @Override
1238                         public void run() {
1239                             callback.startNextRead(urlRequest);
1240                             // Try to read again before the last read completes.
1241                             assertThrows(
1242                                     IllegalStateException.class,
1243                                     () -> callback.startNextRead(urlRequest));
1244                         }
1245                     };
1246             callback.getExecutor().submit(readTwice).get();
1247             callback.waitForNextStep();
1248         }
1249 
1250         assertThat(ResponseStep.ON_SUCCEEDED).isEqualTo(callback.mResponseStep);
1251         assertThat(callback.mResponseAsString).isEqualTo(NativeTestServer.SUCCESS_BODY);
1252 
1253         // Try to read after request is complete.
1254         assertThrows(IllegalStateException.class, () -> callback.startNextRead(urlRequest));
1255     }
1256 
1257     @Test
1258     @SmallTest
testUnexpectedFollowRedirects()1259     public void testUnexpectedFollowRedirects() throws Exception {
1260         final TestUrlRequestCallback callback = new TestUrlRequestCallback();
1261         callback.setAutoAdvance(false);
1262         final UrlRequest urlRequest =
1263                 mTestRule
1264                         .getTestFramework()
1265                         .getEngine()
1266                         .newUrlRequestBuilder(
1267                                 NativeTestServer.getRedirectURL(), callback, callback.getExecutor())
1268                         .build();
1269 
1270         // Try to follow a redirect before starting the request.
1271         assertThrows(IllegalStateException.class, urlRequest::followRedirect);
1272 
1273         // Try to follow a redirect just after starting the request. Has to be
1274         // done on the executor thread to avoid a race.
1275         Runnable startAndRead =
1276                 new Runnable() {
1277                     @Override
1278                     public void run() {
1279                         urlRequest.start();
1280                         assertThrows(IllegalStateException.class, urlRequest::followRedirect);
1281                     }
1282                 };
1283         callback.getExecutor().execute(startAndRead);
1284         callback.waitForNextStep();
1285 
1286         assertThat(ResponseStep.ON_RECEIVED_REDIRECT).isEqualTo(callback.mResponseStep);
1287         // Try to follow the redirect twice. Second attempt should fail.
1288         Runnable followRedirectTwice =
1289                 new Runnable() {
1290                     @Override
1291                     public void run() {
1292                         urlRequest.followRedirect();
1293                         assertThrows(IllegalStateException.class, urlRequest::followRedirect);
1294                     }
1295                 };
1296         callback.getExecutor().execute(followRedirectTwice);
1297         callback.waitForNextStep();
1298 
1299         assertThat(ResponseStep.ON_RESPONSE_STARTED).isEqualTo(callback.mResponseStep);
1300         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1301 
1302         while (!callback.isDone()) {
1303             assertThrows(IllegalStateException.class, urlRequest::followRedirect);
1304             callback.startNextRead(urlRequest);
1305             callback.waitForNextStep();
1306         }
1307 
1308         assertThat(ResponseStep.ON_SUCCEEDED).isEqualTo(callback.mResponseStep);
1309         assertThat(callback.mResponseAsString).isEqualTo(NativeTestServer.SUCCESS_BODY);
1310 
1311         // Try to follow redirect after request is complete.
1312         assertThrows(IllegalStateException.class, urlRequest::followRedirect);
1313     }
1314 
1315     @Test
1316     @SmallTest
testUploadSetDataProvider()1317     public void testUploadSetDataProvider() throws Exception {
1318         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1319         UrlRequest.Builder builder =
1320                 mTestRule
1321                         .getTestFramework()
1322                         .getEngine()
1323                         .newUrlRequestBuilder(
1324                                 NativeTestServer.getEchoBodyURL(),
1325                                 callback,
1326                                 callback.getExecutor());
1327 
1328         NullPointerException e =
1329                 assertThrows(
1330                         NullPointerException.class,
1331                         () -> builder.setUploadDataProvider(null, callback.getExecutor()));
1332         assertThat(e).hasMessageThat().isEqualTo("Invalid UploadDataProvider.");
1333 
1334         TestUploadDataProvider dataProvider =
1335                 new TestUploadDataProvider(
1336                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1337         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1338         assertThrows(IllegalArgumentException.class, () -> builder.build().start());
1339     }
1340 
1341     @Test
1342     @SmallTest
testUploadEmptyBodySync()1343     public void testUploadEmptyBodySync() throws Exception {
1344         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1345         UrlRequest.Builder builder =
1346                 mTestRule
1347                         .getTestFramework()
1348                         .getEngine()
1349                         .newUrlRequestBuilder(
1350                                 NativeTestServer.getEchoBodyURL(),
1351                                 callback,
1352                                 callback.getExecutor());
1353 
1354         TestUploadDataProvider dataProvider =
1355                 new TestUploadDataProvider(
1356                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1357         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1358         builder.addHeader("Content-Type", "useless/string");
1359         builder.build().start();
1360         callback.blockForDone();
1361 
1362         assertThat(dataProvider.getUploadedLength()).isEqualTo(0);
1363         assertThat(dataProvider.getNumReadCalls()).isEqualTo(0);
1364         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0);
1365 
1366         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1367         assertThat(callback.mResponseAsString).isEmpty();
1368         dataProvider.assertClosed();
1369     }
1370 
1371     @Test
1372     @SmallTest
testUploadSync()1373     public void testUploadSync() throws Exception {
1374         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1375         UrlRequest.Builder builder =
1376                 mTestRule
1377                         .getTestFramework()
1378                         .getEngine()
1379                         .newUrlRequestBuilder(
1380                                 NativeTestServer.getEchoBodyURL(),
1381                                 callback,
1382                                 callback.getExecutor());
1383 
1384         TestUploadDataProvider dataProvider =
1385                 new TestUploadDataProvider(
1386                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1387         dataProvider.addRead("test".getBytes());
1388         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1389         builder.addHeader("Content-Type", "useless/string");
1390         builder.build().start();
1391         callback.blockForDone();
1392         dataProvider.assertClosed();
1393 
1394         assertThat(dataProvider.getUploadedLength()).isEqualTo(4);
1395         assertThat(dataProvider.getNumReadCalls()).isEqualTo(1);
1396         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0);
1397 
1398         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1399         assertThat(callback.mResponseAsString).isEqualTo("test");
1400     }
1401 
1402     @Test
1403     @SmallTest
testUploadMultiplePiecesSync()1404     public void testUploadMultiplePiecesSync() throws Exception {
1405         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1406         UrlRequest.Builder builder =
1407                 mTestRule
1408                         .getTestFramework()
1409                         .getEngine()
1410                         .newUrlRequestBuilder(
1411                                 NativeTestServer.getEchoBodyURL(),
1412                                 callback,
1413                                 callback.getExecutor());
1414 
1415         TestUploadDataProvider dataProvider =
1416                 new TestUploadDataProvider(
1417                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1418         dataProvider.addRead("Y".getBytes());
1419         dataProvider.addRead("et ".getBytes());
1420         dataProvider.addRead("another ".getBytes());
1421         dataProvider.addRead("test".getBytes());
1422 
1423         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1424         builder.addHeader("Content-Type", "useless/string");
1425         builder.build().start();
1426         callback.blockForDone();
1427         dataProvider.assertClosed();
1428 
1429         assertThat(dataProvider.getUploadedLength()).isEqualTo(16);
1430         assertThat(dataProvider.getNumReadCalls()).isEqualTo(4);
1431         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0);
1432 
1433         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1434         assertThat(callback.mResponseAsString).isEqualTo("Yet another test");
1435     }
1436 
1437     @Test
1438     @SmallTest
testUploadMultiplePiecesAsync()1439     public void testUploadMultiplePiecesAsync() throws Exception {
1440         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1441         UrlRequest.Builder builder =
1442                 mTestRule
1443                         .getTestFramework()
1444                         .getEngine()
1445                         .newUrlRequestBuilder(
1446                                 NativeTestServer.getEchoBodyURL(),
1447                                 callback,
1448                                 callback.getExecutor());
1449 
1450         TestUploadDataProvider dataProvider =
1451                 new TestUploadDataProvider(
1452                         TestUploadDataProvider.SuccessCallbackMode.ASYNC, callback.getExecutor());
1453         dataProvider.addRead("Y".getBytes());
1454         dataProvider.addRead("et ".getBytes());
1455         dataProvider.addRead("another ".getBytes());
1456         dataProvider.addRead("test".getBytes());
1457 
1458         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1459         builder.addHeader("Content-Type", "useless/string");
1460         builder.build().start();
1461         callback.blockForDone();
1462         dataProvider.assertClosed();
1463 
1464         assertThat(dataProvider.getUploadedLength()).isEqualTo(16);
1465         assertThat(dataProvider.getNumReadCalls()).isEqualTo(4);
1466         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0);
1467 
1468         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1469         assertThat(callback.mResponseAsString).isEqualTo("Yet another test");
1470     }
1471 
1472     @Test
1473     @SmallTest
testUploadChangesDefaultMethod()1474     public void testUploadChangesDefaultMethod() throws Exception {
1475         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1476         UrlRequest.Builder builder =
1477                 mTestRule
1478                         .getTestFramework()
1479                         .getEngine()
1480                         .newUrlRequestBuilder(
1481                                 NativeTestServer.getEchoMethodURL(),
1482                                 callback,
1483                                 callback.getExecutor());
1484 
1485         TestUploadDataProvider dataProvider =
1486                 new TestUploadDataProvider(
1487                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1488         dataProvider.addRead("test".getBytes());
1489         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1490         builder.addHeader("Content-Type", "useless/string");
1491         builder.build().start();
1492         callback.blockForDone();
1493         dataProvider.assertClosed();
1494 
1495         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1496         assertThat(callback.mResponseAsString).isEqualTo("POST");
1497     }
1498 
1499     @Test
1500     @SmallTest
testUploadWithSetMethod()1501     public void testUploadWithSetMethod() throws Exception {
1502         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1503         UrlRequest.Builder builder =
1504                 mTestRule
1505                         .getTestFramework()
1506                         .getEngine()
1507                         .newUrlRequestBuilder(
1508                                 NativeTestServer.getEchoMethodURL(),
1509                                 callback,
1510                                 callback.getExecutor());
1511 
1512         final String method = "PUT";
1513         builder.setHttpMethod(method);
1514 
1515         TestUploadDataProvider dataProvider =
1516                 new TestUploadDataProvider(
1517                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1518         dataProvider.addRead("test".getBytes());
1519         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1520         builder.addHeader("Content-Type", "useless/string");
1521         builder.build().start();
1522         callback.blockForDone();
1523         dataProvider.assertClosed();
1524 
1525         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1526         assertThat(callback.mResponseAsString).isEqualTo("PUT");
1527     }
1528 
1529     @Test
1530     @SmallTest
testUploadRedirectSync()1531     public void testUploadRedirectSync() throws Exception {
1532         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1533         UrlRequest.Builder builder =
1534                 mTestRule
1535                         .getTestFramework()
1536                         .getEngine()
1537                         .newUrlRequestBuilder(
1538                                 NativeTestServer.getRedirectToEchoBody(),
1539                                 callback,
1540                                 callback.getExecutor());
1541 
1542         TestUploadDataProvider dataProvider =
1543                 new TestUploadDataProvider(
1544                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1545         dataProvider.addRead("test".getBytes());
1546         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1547         builder.addHeader("Content-Type", "useless/string");
1548         builder.build().start();
1549         callback.blockForDone();
1550         dataProvider.assertClosed();
1551 
1552         // 1 read call before the rewind, 1 after.
1553         assertThat(dataProvider.getNumReadCalls()).isEqualTo(2);
1554         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(1);
1555 
1556         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1557         assertThat(callback.mResponseAsString).isEqualTo("test");
1558     }
1559 
1560     @Test
1561     @SmallTest
testUploadRedirectAsync()1562     public void testUploadRedirectAsync() throws Exception {
1563         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1564         UrlRequest.Builder builder =
1565                 mTestRule
1566                         .getTestFramework()
1567                         .getEngine()
1568                         .newUrlRequestBuilder(
1569                                 NativeTestServer.getRedirectToEchoBody(),
1570                                 callback,
1571                                 callback.getExecutor());
1572 
1573         TestUploadDataProvider dataProvider =
1574                 new TestUploadDataProvider(
1575                         TestUploadDataProvider.SuccessCallbackMode.ASYNC, callback.getExecutor());
1576         dataProvider.addRead("test".getBytes());
1577         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1578         builder.addHeader("Content-Type", "useless/string");
1579         builder.build().start();
1580         dataProvider.assertClosed();
1581         callback.blockForDone();
1582 
1583         // 1 read call before the rewind, 1 after.
1584         assertThat(dataProvider.getNumReadCalls()).isEqualTo(2);
1585         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(1);
1586 
1587         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1588         assertThat(callback.mResponseAsString).isEqualTo("test");
1589     }
1590 
1591     @Test
1592     @SmallTest
testUploadWithBadLength()1593     public void testUploadWithBadLength() throws Exception {
1594         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1595         UrlRequest.Builder builder =
1596                 mTestRule
1597                         .getTestFramework()
1598                         .getEngine()
1599                         .newUrlRequestBuilder(
1600                                 NativeTestServer.getEchoBodyURL(),
1601                                 callback,
1602                                 callback.getExecutor());
1603 
1604         TestUploadDataProvider dataProvider =
1605                 new TestUploadDataProvider(
1606                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor()) {
1607                     @Override
1608                     public long getLength() throws IOException {
1609                         return 1;
1610                     }
1611 
1612                     @Override
1613                     public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer)
1614                             throws IOException {
1615                         byteBuffer.put("12".getBytes());
1616                         uploadDataSink.onReadSucceeded(false);
1617                     }
1618                 };
1619         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1620         builder.addHeader("Content-Type", "useless/string");
1621         builder.build().start();
1622         callback.blockForDone();
1623         dataProvider.assertClosed();
1624 
1625         assertThat(callback.mError)
1626                 .hasMessageThat()
1627                 .contains("Exception received from UploadDataProvider");
1628         assertThat(callback.mError)
1629                 .hasCauseThat()
1630                 .hasMessageThat()
1631                 .contains("Read upload data length 2 exceeds expected length 1");
1632         assertThat(callback.getResponseInfo()).isNull();
1633     }
1634 
1635     @Test
1636     @SmallTest
testUploadWithBadLengthBufferAligned()1637     public void testUploadWithBadLengthBufferAligned() throws Exception {
1638         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1639         UrlRequest.Builder builder =
1640                 mTestRule
1641                         .getTestFramework()
1642                         .getEngine()
1643                         .newUrlRequestBuilder(
1644                                 NativeTestServer.getEchoBodyURL(),
1645                                 callback,
1646                                 callback.getExecutor());
1647 
1648         TestUploadDataProvider dataProvider =
1649                 new TestUploadDataProvider(
1650                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor()) {
1651                     @Override
1652                     public long getLength() throws IOException {
1653                         return 8191;
1654                     }
1655 
1656                     @Override
1657                     public void read(UploadDataSink uploadDataSink, ByteBuffer byteBuffer)
1658                             throws IOException {
1659                         byteBuffer.put("0123456789abcdef".getBytes());
1660                         uploadDataSink.onReadSucceeded(false);
1661                     }
1662                 };
1663         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1664         builder.addHeader("Content-Type", "useless/string");
1665         builder.build().start();
1666         callback.blockForDone();
1667         dataProvider.assertClosed();
1668         assertThat(callback.mError)
1669                 .hasMessageThat()
1670                 .contains("Exception received from UploadDataProvider");
1671         assertThat(callback.mError)
1672                 .hasCauseThat()
1673                 .hasMessageThat()
1674                 .contains("Read upload data length 8192 exceeds expected length 8191");
1675         assertThat(callback.getResponseInfo()).isNull();
1676     }
1677 
1678     @Test
1679     @SmallTest
testUploadReadFailSync()1680     public void testUploadReadFailSync() throws Exception {
1681         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1682         UrlRequest.Builder builder =
1683                 mTestRule
1684                         .getTestFramework()
1685                         .getEngine()
1686                         .newUrlRequestBuilder(
1687                                 NativeTestServer.getEchoBodyURL(),
1688                                 callback,
1689                                 callback.getExecutor());
1690 
1691         TestUploadDataProvider dataProvider =
1692                 new TestUploadDataProvider(
1693                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1694         dataProvider.setReadFailure(0, TestUploadDataProvider.FailMode.CALLBACK_SYNC);
1695         // This will never be read, but if the length is 0, read may never be
1696         // called.
1697         dataProvider.addRead("test".getBytes());
1698         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1699         builder.addHeader("Content-Type", "useless/string");
1700         builder.build().start();
1701         callback.blockForDone();
1702         dataProvider.assertClosed();
1703 
1704         assertThat(dataProvider.getNumReadCalls()).isEqualTo(1);
1705         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0);
1706 
1707         assertThat(callback.mError)
1708                 .hasMessageThat()
1709                 .contains("Exception received from UploadDataProvider");
1710         assertThat(callback.mError).hasCauseThat().hasMessageThat().contains("Sync read failure");
1711         assertThat(callback.getResponseInfo()).isNull();
1712     }
1713 
1714     @Test
1715     @SmallTest
testUploadLengthFailSync()1716     public void testUploadLengthFailSync() throws Exception {
1717         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1718         UrlRequest.Builder builder =
1719                 mTestRule
1720                         .getTestFramework()
1721                         .getEngine()
1722                         .newUrlRequestBuilder(
1723                                 NativeTestServer.getEchoBodyURL(),
1724                                 callback,
1725                                 callback.getExecutor());
1726 
1727         TestUploadDataProvider dataProvider =
1728                 new TestUploadDataProvider(
1729                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1730         dataProvider.setLengthFailure();
1731         // This will never be read, but if the length is 0, read may never be
1732         // called.
1733         dataProvider.addRead("test".getBytes());
1734         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1735         builder.addHeader("Content-Type", "useless/string");
1736         builder.build().start();
1737         callback.blockForDone();
1738         dataProvider.assertClosed();
1739 
1740         assertThat(dataProvider.getNumReadCalls()).isEqualTo(0);
1741         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0);
1742 
1743         assertThat(callback.mError)
1744                 .hasMessageThat()
1745                 .contains("Exception received from UploadDataProvider");
1746         assertThat(callback.mError).hasCauseThat().hasMessageThat().contains("Sync length failure");
1747         assertThat(callback.getResponseInfo()).isNull();
1748     }
1749 
1750     @Test
1751     @SmallTest
testUploadReadFailAsync()1752     public void testUploadReadFailAsync() throws Exception {
1753         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1754         UrlRequest.Builder builder =
1755                 mTestRule
1756                         .getTestFramework()
1757                         .getEngine()
1758                         .newUrlRequestBuilder(
1759                                 NativeTestServer.getEchoBodyURL(),
1760                                 callback,
1761                                 callback.getExecutor());
1762 
1763         TestUploadDataProvider dataProvider =
1764                 new TestUploadDataProvider(
1765                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1766         dataProvider.setReadFailure(0, TestUploadDataProvider.FailMode.CALLBACK_ASYNC);
1767         // This will never be read, but if the length is 0, read may never be
1768         // called.
1769         dataProvider.addRead("test".getBytes());
1770         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1771         builder.addHeader("Content-Type", "useless/string");
1772         builder.build().start();
1773         callback.blockForDone();
1774         dataProvider.assertClosed();
1775 
1776         assertThat(dataProvider.getNumReadCalls()).isEqualTo(1);
1777         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0);
1778 
1779         assertThat(callback.mError)
1780                 .hasMessageThat()
1781                 .contains("Exception received from UploadDataProvider");
1782         assertThat(callback.mError).hasCauseThat().hasMessageThat().contains("Async read failure");
1783         assertThat(callback.getResponseInfo()).isNull();
1784     }
1785 
1786     /** This test uses a direct executor for upload, and non direct for callbacks */
1787     @Test
1788     @SmallTest
testDirectExecutorUploadProhibitedByDefault()1789     public void testDirectExecutorUploadProhibitedByDefault() throws Exception {
1790         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1791         Executor myExecutor =
1792                 new Executor() {
1793                     @Override
1794                     public void execute(Runnable command) {
1795                         command.run();
1796                     }
1797                 };
1798         UrlRequest.Builder builder =
1799                 mTestRule
1800                         .getTestFramework()
1801                         .getEngine()
1802                         .newUrlRequestBuilder(
1803                                 NativeTestServer.getEchoBodyURL(),
1804                                 callback,
1805                                 callback.getExecutor());
1806 
1807         TestUploadDataProvider dataProvider =
1808                 new TestUploadDataProvider(
1809                         TestUploadDataProvider.SuccessCallbackMode.SYNC, myExecutor);
1810         // This will never be read, but if the length is 0, read may never be
1811         // called.
1812         dataProvider.addRead("test".getBytes());
1813         builder.setUploadDataProvider(dataProvider, myExecutor);
1814         builder.addHeader("Content-Type", "useless/string");
1815         builder.build().start();
1816         callback.blockForDone();
1817 
1818         assertThat(dataProvider.getNumReadCalls()).isEqualTo(0);
1819         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0);
1820 
1821         assertThat(callback.mError)
1822                 .hasMessageThat()
1823                 .contains("Exception received from UploadDataProvider");
1824         assertThat(callback.mError)
1825                 .hasCauseThat()
1826                 .hasMessageThat()
1827                 .contains("Inline execution is prohibited for this request");
1828         assertThat(callback.getResponseInfo()).isNull();
1829     }
1830 
1831     /** This test uses a direct executor for callbacks, and non direct for upload */
1832     @Test
1833     @SmallTest
1834     @IgnoreFor(
1835             implementations = {CronetImplementation.AOSP_PLATFORM},
1836             reason = "b/311163531: Re-enable once HttpEngine propagates UploadDataProvider#close")
testDirectExecutorProhibitedByDefault()1837     public void testDirectExecutorProhibitedByDefault() throws Exception {
1838         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1839         Executor myExecutor =
1840                 new Executor() {
1841                     @Override
1842                     public void execute(Runnable command) {
1843                         command.run();
1844                     }
1845                 };
1846         UrlRequest.Builder builder =
1847                 mTestRule
1848                         .getTestFramework()
1849                         .getEngine()
1850                         .newUrlRequestBuilder(
1851                                 NativeTestServer.getEchoBodyURL(), callback, myExecutor);
1852 
1853         TestUploadDataProvider dataProvider =
1854                 new TestUploadDataProvider(
1855                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1856         // This will never be read, but if the length is 0, read may never be
1857         // called.
1858         dataProvider.addRead("test".getBytes());
1859         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1860         builder.addHeader("Content-Type", "useless/string");
1861         builder.build().start();
1862         callback.blockForDone();
1863 
1864         assertThat(dataProvider.getNumReadCalls()).isEqualTo(1);
1865         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0);
1866 
1867         assertThat(callback.mError).hasMessageThat().contains("Exception posting task to executor");
1868         assertThat(callback.mError)
1869                 .hasCauseThat()
1870                 .hasMessageThat()
1871                 .contains("Inline execution is prohibited for this request");
1872         assertThat(callback.getResponseInfo()).isNull();
1873         dataProvider.assertClosed();
1874     }
1875 
1876     @Test
1877     @SmallTest
testDirectExecutorAllowed()1878     public void testDirectExecutorAllowed() throws Exception {
1879         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1880         callback.setAllowDirectExecutor(true);
1881         Executor myExecutor =
1882                 new Executor() {
1883                     @Override
1884                     public void execute(Runnable command) {
1885                         command.run();
1886                     }
1887                 };
1888         UrlRequest.Builder builder =
1889                 mTestRule
1890                         .getTestFramework()
1891                         .getEngine()
1892                         .newUrlRequestBuilder(
1893                                 NativeTestServer.getEchoBodyURL(), callback, myExecutor);
1894         UploadDataProvider dataProvider = UploadDataProviders.create("test".getBytes());
1895         builder.setUploadDataProvider(dataProvider, myExecutor);
1896         builder.addHeader("Content-Type", "useless/string");
1897         builder.allowDirectExecutor();
1898         builder.build().start();
1899         callback.blockForDone();
1900 
1901         if (callback.mOnErrorCalled) {
1902             throw callback.mError;
1903         }
1904 
1905         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1906         assertThat(callback.mResponseAsString).isEqualTo("test");
1907     }
1908 
1909     @Test
1910     @SmallTest
testUploadReadFailThrown()1911     public void testUploadReadFailThrown() throws Exception {
1912         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1913         UrlRequest.Builder builder =
1914                 mTestRule
1915                         .getTestFramework()
1916                         .getEngine()
1917                         .newUrlRequestBuilder(
1918                                 NativeTestServer.getEchoBodyURL(),
1919                                 callback,
1920                                 callback.getExecutor());
1921 
1922         TestUploadDataProvider dataProvider =
1923                 new TestUploadDataProvider(
1924                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1925         dataProvider.setReadFailure(0, TestUploadDataProvider.FailMode.THROWN);
1926         // This will never be read, but if the length is 0, read may never be
1927         // called.
1928         dataProvider.addRead("test".getBytes());
1929         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1930         builder.addHeader("Content-Type", "useless/string");
1931         builder.build().start();
1932         callback.blockForDone();
1933         dataProvider.assertClosed();
1934 
1935         assertThat(dataProvider.getNumReadCalls()).isEqualTo(1);
1936         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(0);
1937 
1938         assertThat(callback.mError)
1939                 .hasMessageThat()
1940                 .contains("Exception received from UploadDataProvider");
1941         assertThat(callback.mError).hasCauseThat().hasMessageThat().contains("Thrown read failure");
1942         assertThat(callback.getResponseInfo()).isNull();
1943     }
1944 
1945     @Test
1946     @SmallTest
testUploadRewindFailSync()1947     public void testUploadRewindFailSync() throws Exception {
1948         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1949         UrlRequest.Builder builder =
1950                 mTestRule
1951                         .getTestFramework()
1952                         .getEngine()
1953                         .newUrlRequestBuilder(
1954                                 NativeTestServer.getRedirectToEchoBody(),
1955                                 callback,
1956                                 callback.getExecutor());
1957 
1958         TestUploadDataProvider dataProvider =
1959                 new TestUploadDataProvider(
1960                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
1961         dataProvider.setRewindFailure(TestUploadDataProvider.FailMode.CALLBACK_SYNC);
1962         dataProvider.addRead("test".getBytes());
1963         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1964         builder.addHeader("Content-Type", "useless/string");
1965         builder.build().start();
1966         callback.blockForDone();
1967         dataProvider.assertClosed();
1968 
1969         assertThat(dataProvider.getNumReadCalls()).isEqualTo(1);
1970         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(1);
1971 
1972         assertThat(callback.mError)
1973                 .hasMessageThat()
1974                 .contains("Exception received from UploadDataProvider");
1975         assertThat(callback.mError).hasCauseThat().hasMessageThat().contains("Sync rewind failure");
1976         assertThat(callback.getResponseInfo()).isNull();
1977     }
1978 
1979     @Test
1980     @SmallTest
testUploadRewindFailAsync()1981     public void testUploadRewindFailAsync() throws Exception {
1982         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1983         UrlRequest.Builder builder =
1984                 mTestRule
1985                         .getTestFramework()
1986                         .getEngine()
1987                         .newUrlRequestBuilder(
1988                                 NativeTestServer.getRedirectToEchoBody(),
1989                                 callback,
1990                                 callback.getExecutor());
1991 
1992         TestUploadDataProvider dataProvider =
1993                 new TestUploadDataProvider(
1994                         TestUploadDataProvider.SuccessCallbackMode.ASYNC, callback.getExecutor());
1995         dataProvider.setRewindFailure(TestUploadDataProvider.FailMode.CALLBACK_ASYNC);
1996         dataProvider.addRead("test".getBytes());
1997         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
1998         builder.addHeader("Content-Type", "useless/string");
1999         builder.build().start();
2000         callback.blockForDone();
2001         dataProvider.assertClosed();
2002 
2003         assertThat(dataProvider.getNumReadCalls()).isEqualTo(1);
2004         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(1);
2005 
2006         assertThat(callback.mError)
2007                 .hasMessageThat()
2008                 .contains("Exception received from UploadDataProvider");
2009         assertThat(callback.mError)
2010                 .hasCauseThat()
2011                 .hasMessageThat()
2012                 .contains("Async rewind failure");
2013         assertThat(callback.getResponseInfo()).isNull();
2014     }
2015 
2016     @Test
2017     @SmallTest
testUploadRewindFailThrown()2018     public void testUploadRewindFailThrown() throws Exception {
2019         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2020         UrlRequest.Builder builder =
2021                 mTestRule
2022                         .getTestFramework()
2023                         .getEngine()
2024                         .newUrlRequestBuilder(
2025                                 NativeTestServer.getRedirectToEchoBody(),
2026                                 callback,
2027                                 callback.getExecutor());
2028 
2029         TestUploadDataProvider dataProvider =
2030                 new TestUploadDataProvider(
2031                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
2032         dataProvider.setRewindFailure(TestUploadDataProvider.FailMode.THROWN);
2033         dataProvider.addRead("test".getBytes());
2034         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
2035         builder.addHeader("Content-Type", "useless/string");
2036         builder.build().start();
2037         callback.blockForDone();
2038         dataProvider.assertClosed();
2039 
2040         assertThat(dataProvider.getNumReadCalls()).isEqualTo(1);
2041         assertThat(dataProvider.getNumRewindCalls()).isEqualTo(1);
2042 
2043         assertThat(callback.mError)
2044                 .hasMessageThat()
2045                 .contains("Exception received from UploadDataProvider");
2046         assertThat(callback.mError)
2047                 .hasCauseThat()
2048                 .hasMessageThat()
2049                 .contains("Thrown rewind failure");
2050         assertThat(callback.getResponseInfo()).isNull();
2051     }
2052 
2053     @Test
2054     @SmallTest
testUploadChunked()2055     public void testUploadChunked() throws Exception {
2056         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2057         UrlRequest.Builder builder =
2058                 mTestRule
2059                         .getTestFramework()
2060                         .getEngine()
2061                         .newUrlRequestBuilder(
2062                                 NativeTestServer.getEchoBodyURL(),
2063                                 callback,
2064                                 callback.getExecutor());
2065 
2066         TestUploadDataProvider dataProvider =
2067                 new TestUploadDataProvider(
2068                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
2069         dataProvider.addRead("test hello".getBytes());
2070         dataProvider.setChunked(true);
2071         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
2072         builder.addHeader("Content-Type", "useless/string");
2073 
2074         assertThat(dataProvider.getUploadedLength()).isEqualTo(-1);
2075 
2076         builder.build().start();
2077         callback.blockForDone();
2078         dataProvider.assertClosed();
2079 
2080         // 1 read call for one data chunk.
2081         assertThat(dataProvider.getNumReadCalls()).isEqualTo(1);
2082         assertThat(callback.mResponseAsString).isEqualTo("test hello");
2083     }
2084 
2085     @Test
2086     @SmallTest
testUploadChunkedLastReadZeroLengthBody()2087     public void testUploadChunkedLastReadZeroLengthBody() throws Exception {
2088         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2089         UrlRequest.Builder builder =
2090                 mTestRule
2091                         .getTestFramework()
2092                         .getEngine()
2093                         .newUrlRequestBuilder(
2094                                 NativeTestServer.getEchoBodyURL(),
2095                                 callback,
2096                                 callback.getExecutor());
2097 
2098         TestUploadDataProvider dataProvider =
2099                 new TestUploadDataProvider(
2100                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
2101         // Add 3 reads. The last read has a 0-length body.
2102         dataProvider.addRead("hello there".getBytes());
2103         dataProvider.addRead("!".getBytes());
2104         dataProvider.addRead("".getBytes());
2105         dataProvider.setChunked(true);
2106         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
2107         builder.addHeader("Content-Type", "useless/string");
2108 
2109         assertThat(dataProvider.getUploadedLength()).isEqualTo(-1);
2110 
2111         builder.build().start();
2112         callback.blockForDone();
2113         dataProvider.assertClosed();
2114 
2115         // 2 read call for the first two data chunks, and 1 for final chunk.
2116         assertThat(dataProvider.getNumReadCalls()).isEqualTo(3);
2117         assertThat(callback.mResponseAsString).isEqualTo("hello there!");
2118     }
2119 
2120     // Test where an upload fails without ever initializing the
2121     // UploadDataStream, because it can't connect to the server.
2122     @Test
2123     @SmallTest
testUploadFailsWithoutInitializingStream()2124     public void testUploadFailsWithoutInitializingStream() throws Exception {
2125         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2126         // The port for PTP will always refuse a TCP connection
2127         UrlRequest.Builder builder =
2128                 mTestRule
2129                         .getTestFramework()
2130                         .getEngine()
2131                         .newUrlRequestBuilder(
2132                                 "http://127.0.0.1:319", callback, callback.getExecutor());
2133 
2134         TestUploadDataProvider dataProvider =
2135                 new TestUploadDataProvider(
2136                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
2137         dataProvider.addRead("test".getBytes());
2138         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
2139         builder.addHeader("Content-Type", "useless/string");
2140         builder.build().start();
2141         callback.blockForDone();
2142         dataProvider.assertClosed();
2143 
2144         assertThat(callback.getResponseInfo()).isNull();
2145         if (mTestRule.testingJavaImpl()) {
2146             assertThat(callback.mError).hasCauseThat().isInstanceOf(ConnectException.class);
2147         } else {
2148             assertThat(callback.mError)
2149                     .hasMessageThat()
2150                     .contains("Exception in CronetUrlRequest: net::ERR_CONNECTION_REFUSED");
2151         }
2152     }
2153 
throwOrCancel( FailureType failureType, ResponseStep failureStep, boolean expectResponseInfo, boolean expectError)2154     private void throwOrCancel(
2155             FailureType failureType,
2156             ResponseStep failureStep,
2157             boolean expectResponseInfo,
2158             boolean expectError) {
2159         if (Log.isLoggable("TESTING", Log.VERBOSE)) {
2160             Log.v("TESTING", "Testing " + failureType + " during " + failureStep);
2161         }
2162         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2163         callback.setFailure(failureType, failureStep);
2164         UrlRequest.Builder builder =
2165                 mTestRule
2166                         .getTestFramework()
2167                         .getEngine()
2168                         .newUrlRequestBuilder(
2169                                 NativeTestServer.getRedirectURL(),
2170                                 callback,
2171                                 callback.getExecutor());
2172         UrlRequest urlRequest = builder.build();
2173         urlRequest.start();
2174         callback.blockForDone();
2175         // Wait for all posted tasks to be executed to ensure there is no unhandled exception.
2176         callback.shutdownExecutorAndWait();
2177         assertThat(callback.mRedirectCount).isEqualTo(1);
2178         if (failureType == FailureType.CANCEL_SYNC || failureType == FailureType.CANCEL_ASYNC) {
2179             assertResponseStepCanceled(callback);
2180         } else if (failureType == FailureType.THROW_SYNC) {
2181             assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_FAILED);
2182         }
2183         assertThat(urlRequest.isDone()).isTrue();
2184         assertThat(callback.getResponseInfo() != null).isEqualTo(expectResponseInfo);
2185         assertThat(callback.mError != null).isEqualTo(expectError);
2186         assertThat(callback.mOnErrorCalled).isEqualTo(expectError);
2187         // When failureType is FailureType.CANCEL_ASYNC_WITHOUT_PAUSE and failureStep is
2188         // ResponseStep.ON_READ_COMPLETED, there might be an onSucceeded() task already posted. If
2189         // that's the case, onCanceled() will not be invoked. See crbug.com/657415.
2190         if (!(failureType == FailureType.CANCEL_ASYNC_WITHOUT_PAUSE
2191                 && failureStep == ResponseStep.ON_READ_COMPLETED)) {
2192             assertThat(callback.mOnCanceledCalled)
2193                     .isEqualTo(
2194                             failureType == FailureType.CANCEL_SYNC
2195                                     || failureType == FailureType.CANCEL_ASYNC
2196                                     || failureType == FailureType.CANCEL_ASYNC_WITHOUT_PAUSE);
2197         }
2198     }
2199 
2200     @Test
2201     @SmallTest
testFailures()2202     public void testFailures() throws Exception {
2203         throwOrCancel(FailureType.CANCEL_SYNC, ResponseStep.ON_RECEIVED_REDIRECT, false, false);
2204         throwOrCancel(FailureType.CANCEL_ASYNC, ResponseStep.ON_RECEIVED_REDIRECT, false, false);
2205         throwOrCancel(
2206                 FailureType.CANCEL_ASYNC_WITHOUT_PAUSE,
2207                 ResponseStep.ON_RECEIVED_REDIRECT,
2208                 false,
2209                 false);
2210         throwOrCancel(FailureType.THROW_SYNC, ResponseStep.ON_RECEIVED_REDIRECT, false, true);
2211 
2212         throwOrCancel(FailureType.CANCEL_SYNC, ResponseStep.ON_RESPONSE_STARTED, true, false);
2213         throwOrCancel(FailureType.CANCEL_ASYNC, ResponseStep.ON_RESPONSE_STARTED, true, false);
2214         throwOrCancel(
2215                 FailureType.CANCEL_ASYNC_WITHOUT_PAUSE,
2216                 ResponseStep.ON_RESPONSE_STARTED,
2217                 true,
2218                 false);
2219         throwOrCancel(FailureType.THROW_SYNC, ResponseStep.ON_RESPONSE_STARTED, true, true);
2220 
2221         throwOrCancel(FailureType.CANCEL_SYNC, ResponseStep.ON_READ_COMPLETED, true, false);
2222         throwOrCancel(FailureType.CANCEL_ASYNC, ResponseStep.ON_READ_COMPLETED, true, false);
2223         throwOrCancel(
2224                 FailureType.CANCEL_ASYNC_WITHOUT_PAUSE,
2225                 ResponseStep.ON_READ_COMPLETED,
2226                 true,
2227                 false);
2228         throwOrCancel(FailureType.THROW_SYNC, ResponseStep.ON_READ_COMPLETED, true, true);
2229     }
2230 
2231     @Test
2232     @SmallTest
testThrowOrCancelInOnSucceeded()2233     public void testThrowOrCancelInOnSucceeded() {
2234         FailureType[] testTypes =
2235                 new FailureType[] {
2236                     FailureType.THROW_SYNC, FailureType.CANCEL_SYNC, FailureType.CANCEL_ASYNC
2237                 };
2238         for (FailureType type : testTypes) {
2239             TestUrlRequestCallback callback = new TestUrlRequestCallback();
2240             callback.setFailure(type, ResponseStep.ON_SUCCEEDED);
2241             UrlRequest.Builder builder =
2242                     mTestRule
2243                             .getTestFramework()
2244                             .getEngine()
2245                             .newUrlRequestBuilder(
2246                                     NativeTestServer.getEchoMethodURL(),
2247                                     callback,
2248                                     callback.getExecutor());
2249             UrlRequest urlRequest = builder.build();
2250             urlRequest.start();
2251             callback.blockForDone();
2252             // Wait for all posted tasks to be executed to ensure there is no unhandled exception.
2253             callback.shutdownExecutorAndWait();
2254             assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_SUCCEEDED);
2255             assertThat(urlRequest.isDone()).isTrue();
2256             assertThat(callback.getResponseInfoWithChecks()).isNotNull();
2257             assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
2258             assertThat(callback.mResponseAsString).isEqualTo("GET");
2259         }
2260     }
2261 
2262     @Test
2263     @SmallTest
testThrowOrCancelInOnFailed()2264     public void testThrowOrCancelInOnFailed() {
2265         FailureType[] testTypes =
2266                 new FailureType[] {
2267                     FailureType.THROW_SYNC, FailureType.CANCEL_SYNC, FailureType.CANCEL_ASYNC
2268                 };
2269         for (FailureType type : testTypes) {
2270             String url = NativeTestServer.getEchoBodyURL();
2271             // Shut down NativeTestServer so request will fail.
2272             NativeTestServer.shutdownNativeTestServer();
2273             TestUrlRequestCallback callback = new TestUrlRequestCallback();
2274             callback.setFailure(type, ResponseStep.ON_FAILED);
2275             UrlRequest.Builder builder =
2276                     mTestRule
2277                             .getTestFramework()
2278                             .getEngine()
2279                             .newUrlRequestBuilder(url, callback, callback.getExecutor());
2280             UrlRequest urlRequest = builder.build();
2281             urlRequest.start();
2282             callback.blockForDone();
2283             // Wait for all posted tasks to be executed to ensure there is no unhandled exception.
2284             callback.shutdownExecutorAndWait();
2285             assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_FAILED);
2286             assertThat(callback.mOnErrorCalled).isTrue();
2287             assertThat(callback.mError).isNotNull();
2288             assertThat(urlRequest.isDone()).isTrue();
2289             // Start NativeTestServer again to run the test for a second time.
2290             assertThat(
2291                             NativeTestServer.startNativeTestServer(
2292                                     mTestRule.getTestFramework().getContext()))
2293                     .isTrue();
2294         }
2295     }
2296 
2297     @Test
2298     @SmallTest
testThrowOrCancelInOnCanceled()2299     public void testThrowOrCancelInOnCanceled() {
2300         FailureType[] testTypes =
2301                 new FailureType[] {
2302                     FailureType.THROW_SYNC, FailureType.CANCEL_SYNC, FailureType.CANCEL_ASYNC
2303                 };
2304         for (FailureType type : testTypes) {
2305             TestUrlRequestCallback callback =
2306                     new TestUrlRequestCallback() {
2307                         @Override
2308                         public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
2309                             super.onResponseStarted(request, info);
2310                             request.cancel();
2311                         }
2312                     };
2313             callback.setFailure(type, ResponseStep.ON_CANCELED);
2314             UrlRequest.Builder builder =
2315                     mTestRule
2316                             .getTestFramework()
2317                             .getEngine()
2318                             .newUrlRequestBuilder(
2319                                     NativeTestServer.getEchoBodyURL(),
2320                                     callback,
2321                                     callback.getExecutor());
2322             UrlRequest urlRequest = builder.build();
2323             urlRequest.start();
2324             callback.blockForDone();
2325             // Wait for all posted tasks to be executed to ensure there is no unhandled exception.
2326             callback.shutdownExecutorAndWait();
2327             assertResponseStepCanceled(callback);
2328             assertThat(urlRequest.isDone()).isTrue();
2329             assertThat(callback.getResponseInfoWithChecks()).isNotNull();
2330             assertThat(callback.mOnCanceledCalled).isTrue();
2331         }
2332     }
2333 
2334     @Test
2335     @SmallTest
2336     @IgnoreFor(
2337             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
2338             reason = "crbug.com/1494846: tests native-specific internals")
testExecutorShutdown()2339     public void testExecutorShutdown() {
2340         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2341 
2342         callback.setAutoAdvance(false);
2343         UrlRequest.Builder builder =
2344                 mTestRule
2345                         .getTestFramework()
2346                         .getEngine()
2347                         .newUrlRequestBuilder(
2348                                 NativeTestServer.getEchoBodyURL(),
2349                                 callback,
2350                                 callback.getExecutor());
2351         CronetUrlRequest urlRequest = (CronetUrlRequest) builder.build();
2352         urlRequest.start();
2353         callback.waitForNextStep();
2354         assertThat(callback.isDone()).isFalse();
2355         assertThat(urlRequest.isDone()).isFalse();
2356 
2357         final ConditionVariable requestDestroyed = new ConditionVariable(false);
2358         urlRequest.setOnDestroyedCallbackForTesting(
2359                 new Runnable() {
2360                     @Override
2361                     public void run() {
2362                         requestDestroyed.open();
2363                     }
2364                 });
2365 
2366         // Shutdown the executor, so posting the task will throw an exception.
2367         callback.shutdownExecutor();
2368         ByteBuffer readBuffer = ByteBuffer.allocateDirect(5);
2369         urlRequest.read(readBuffer);
2370         // Callback will never be called again because executor is shutdown,
2371         // but request will be destroyed from network thread.
2372         requestDestroyed.block();
2373 
2374         assertThat(callback.isDone()).isFalse();
2375         assertThat(urlRequest.isDone()).isTrue();
2376     }
2377 
2378     @Test
2379     @SmallTest
testUploadExecutorShutdown()2380     public void testUploadExecutorShutdown() throws Exception {
2381         class HangingUploadDataProvider extends UploadDataProvider {
2382             UploadDataSink mUploadDataSink;
2383             ByteBuffer mByteBuffer;
2384             ConditionVariable mReadCalled = new ConditionVariable(false);
2385 
2386             @Override
2387             public long getLength() {
2388                 return 69;
2389             }
2390 
2391             @Override
2392             public void read(final UploadDataSink uploadDataSink, final ByteBuffer byteBuffer) {
2393                 mUploadDataSink = uploadDataSink;
2394                 mByteBuffer = byteBuffer;
2395                 mReadCalled.open();
2396             }
2397 
2398             @Override
2399             public void rewind(final UploadDataSink uploadDataSink) {}
2400         }
2401 
2402         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2403         UrlRequest.Builder builder =
2404                 mTestRule
2405                         .getTestFramework()
2406                         .getEngine()
2407                         .newUrlRequestBuilder(
2408                                 NativeTestServer.getEchoBodyURL(),
2409                                 callback,
2410                                 callback.getExecutor());
2411 
2412         ExecutorService uploadExecutor = Executors.newSingleThreadExecutor();
2413         HangingUploadDataProvider dataProvider = new HangingUploadDataProvider();
2414         builder.setUploadDataProvider(dataProvider, uploadExecutor);
2415         builder.addHeader("Content-Type", "useless/string");
2416         UrlRequest urlRequest = builder.build();
2417         urlRequest.start();
2418         // Wait for read to be called on executor.
2419         dataProvider.mReadCalled.block();
2420         // Shutdown the executor, so posting next task will throw an exception.
2421         uploadExecutor.shutdown();
2422         // Continue the upload.
2423         dataProvider.mByteBuffer.putInt(42);
2424         dataProvider.mUploadDataSink.onReadSucceeded(false);
2425         // Callback.onFailed will be called on request executor even though upload
2426         // executor is shutdown.
2427         callback.blockForDone();
2428         assertThat(callback.isDone()).isTrue();
2429         assertThat(callback.mOnErrorCalled).isTrue();
2430         assertThat(callback.mError)
2431                 .hasMessageThat()
2432                 .contains("Exception received from UploadDataProvider");
2433         assertThat(urlRequest.isDone()).isTrue();
2434     }
2435 
2436     /** A TestUrlRequestCallback that shuts down executor upon receiving onSucceeded callback. */
2437     private static class QuitOnSuccessCallback extends TestUrlRequestCallback {
2438         @Override
onSucceeded(UrlRequest request, UrlResponseInfo info)2439         public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
2440             // Stop accepting new tasks.
2441             shutdownExecutor();
2442             super.onSucceeded(request, info);
2443         }
2444     }
2445 
2446     @Test
2447     @SmallTest
2448     @IgnoreFor(
2449             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
2450             reason = "crbug.com/1494846: tests native-specific internals")
2451     // Regression test for crbug.com/564946.
testDestroyUploadDataStreamAdapterOnSucceededCallback()2452     public void testDestroyUploadDataStreamAdapterOnSucceededCallback() throws Exception {
2453         TestUrlRequestCallback callback = new QuitOnSuccessCallback();
2454         UrlRequest.Builder builder =
2455                 mTestRule
2456                         .getTestFramework()
2457                         .getEngine()
2458                         .newUrlRequestBuilder(
2459                                 NativeTestServer.getEchoBodyURL(),
2460                                 callback,
2461                                 callback.getExecutor());
2462 
2463         TestUploadDataProvider dataProvider =
2464                 new TestUploadDataProvider(
2465                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
2466         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
2467         builder.addHeader("Content-Type", "useless/string");
2468         CronetUrlRequest request = (CronetUrlRequest) builder.build();
2469         final ConditionVariable uploadDataStreamAdapterDestroyed = new ConditionVariable();
2470         request.setOnDestroyedUploadCallbackForTesting(
2471                 new Runnable() {
2472                     @Override
2473                     public void run() {
2474                         uploadDataStreamAdapterDestroyed.open();
2475                     }
2476                 });
2477 
2478         request.start();
2479         uploadDataStreamAdapterDestroyed.block();
2480         callback.blockForDone();
2481 
2482         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
2483         assertThat(callback.mResponseAsString).isEmpty();
2484     }
2485 
2486     /*
2487      * Verifies error codes are passed through correctly.
2488      */
2489     @Test
2490     @SmallTest
2491     @IgnoreFor(
2492             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
2493             reason = "crbug.com/1494846: tests native-specific internals")
testErrorCodes()2494     public void testErrorCodes() throws Exception {
2495         mMockUrlRequestJobFactory =
2496                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
2497         checkSpecificErrorCode(
2498                 -105, NetworkException.ERROR_HOSTNAME_NOT_RESOLVED, "NAME_NOT_RESOLVED", false);
2499         checkSpecificErrorCode(
2500                 -106, NetworkException.ERROR_INTERNET_DISCONNECTED, "INTERNET_DISCONNECTED", false);
2501         checkSpecificErrorCode(
2502                 -21, NetworkException.ERROR_NETWORK_CHANGED, "NETWORK_CHANGED", true);
2503         checkSpecificErrorCode(
2504                 -100, NetworkException.ERROR_CONNECTION_CLOSED, "CONNECTION_CLOSED", true);
2505         checkSpecificErrorCode(
2506                 -102, NetworkException.ERROR_CONNECTION_REFUSED, "CONNECTION_REFUSED", false);
2507         checkSpecificErrorCode(
2508                 -101, NetworkException.ERROR_CONNECTION_RESET, "CONNECTION_RESET", true);
2509         checkSpecificErrorCode(
2510                 -118, NetworkException.ERROR_CONNECTION_TIMED_OUT, "CONNECTION_TIMED_OUT", true);
2511         checkSpecificErrorCode(-7, NetworkException.ERROR_TIMED_OUT, "TIMED_OUT", true);
2512         checkSpecificErrorCode(
2513                 -109, NetworkException.ERROR_ADDRESS_UNREACHABLE, "ADDRESS_UNREACHABLE", false);
2514         checkSpecificErrorCode(-2, NetworkException.ERROR_OTHER, "FAILED", false);
2515     }
2516 
2517     /*
2518      * Verifies no cookies are saved or sent by default.
2519      */
2520     @Test
2521     @SmallTest
testCookiesArentSavedOrSent()2522     public void testCookiesArentSavedOrSent() throws Exception {
2523         // Make a request to a url that sets the cookie
2524         String url = NativeTestServer.getFileURL("/set_cookie.html");
2525         TestUrlRequestCallback callback = startAndWaitForComplete(url);
2526         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
2527         assertThat(callback.getResponseInfoWithChecks())
2528                 .hasHeadersThat()
2529                 .containsEntry("Set-Cookie", Arrays.asList("A=B"));
2530 
2531         // Make a request that check that cookie header isn't sent.
2532         String headerName = "Cookie";
2533         String url2 = NativeTestServer.getEchoHeaderURL(headerName);
2534         TestUrlRequestCallback callback2 = startAndWaitForComplete(url2);
2535         assertThat(callback2.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
2536         assertThat(callback2.mResponseAsString).isEqualTo("Header not found. :(");
2537     }
2538 
2539     @Test
2540     @SmallTest
2541     @IgnoreFor(
2542             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
2543             reason = "crbug.com/1494846: tests native-specific internals")
testQuicErrorCode()2544     public void testQuicErrorCode() throws Exception {
2545         mMockUrlRequestJobFactory =
2546                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
2547         TestUrlRequestCallback callback =
2548                 startAndWaitForComplete(
2549                         MockUrlRequestJobFactory.getMockUrlWithFailure(
2550                                 FailurePhase.START, NetError.ERR_QUIC_PROTOCOL_ERROR));
2551         assertThat(callback.getResponseInfo()).isNull();
2552         assertThat(callback.mError).isInstanceOf(QuicException.class);
2553         QuicException quicException = (QuicException) callback.mError;
2554         // 1 is QUIC_INTERNAL_ERROR
2555         assertThat(quicException.getQuicDetailedErrorCode()).isEqualTo(1);
2556         assertThat(quicException.getErrorCode())
2557                 .isEqualTo(NetworkException.ERROR_QUIC_PROTOCOL_FAILED);
2558     }
2559 
2560     @Test
2561     @SmallTest
2562     @IgnoreFor(
2563             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
2564             reason = "crbug.com/1494846: tests native-specific internals")
testQuicErrorCodeForNetworkChanged()2565     public void testQuicErrorCodeForNetworkChanged() throws Exception {
2566         mMockUrlRequestJobFactory =
2567                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
2568         TestUrlRequestCallback callback =
2569                 startAndWaitForComplete(
2570                         MockUrlRequestJobFactory.getMockUrlWithFailure(
2571                                 FailurePhase.START, NetError.ERR_NETWORK_CHANGED));
2572         assertThat(callback.getResponseInfo()).isNull();
2573         assertThat(callback.mError).isInstanceOf(QuicException.class);
2574         QuicException quicException = (QuicException) callback.mError;
2575         // QUIC_CONNECTION_MIGRATION_NO_NEW_NETWORK(83) is set in
2576         // URLRequestFailedJob::PopulateNetErrorDetails for this test.
2577         final int quicErrorCode = 83;
2578         assertThat(quicException.getQuicDetailedErrorCode()).isEqualTo(quicErrorCode);
2579         assertThat(quicException.getErrorCode()).isEqualTo(NetworkException.ERROR_NETWORK_CHANGED);
2580     }
2581 
2582     /**
2583      * Tests that legacy onFailed callback is invoked with UrlRequestException if there is no
2584      * onFailed callback implementation that takes CronetException.
2585      */
2586     @Test
2587     @SmallTest
2588     @IgnoreFor(
2589             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
2590             reason = "crbug.com/1494846: tests native-specific internals")
testLegacyOnFailedCallback()2591     public void testLegacyOnFailedCallback() throws Exception {
2592         mMockUrlRequestJobFactory =
2593                 new MockUrlRequestJobFactory(mTestRule.getTestFramework().getEngine());
2594         final int netError = -123;
2595         final AtomicBoolean failedExpectation = new AtomicBoolean();
2596         final ConditionVariable done = new ConditionVariable();
2597         UrlRequest.Callback callback =
2598                 new UrlRequest.Callback() {
2599                     @Override
2600                     public void onRedirectReceived(
2601                             UrlRequest request, UrlResponseInfo info, String newLocationUrl) {
2602                         failedExpectation.set(true);
2603                         fail();
2604                     }
2605 
2606                     @Override
2607                     public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
2608                         failedExpectation.set(true);
2609                         fail();
2610                     }
2611 
2612                     @Override
2613                     public void onReadCompleted(
2614                             UrlRequest request, UrlResponseInfo info, ByteBuffer byteBuffer) {
2615                         failedExpectation.set(true);
2616                         fail();
2617                     }
2618 
2619                     @Override
2620                     public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
2621                         failedExpectation.set(true);
2622                         fail();
2623                     }
2624 
2625                     @Override
2626                     public void onFailed(
2627                             UrlRequest request, UrlResponseInfo info, CronetException error) {
2628                         assertThat(error).isInstanceOf(NetworkException.class);
2629                         assertThat(((NetworkException) error).getCronetInternalErrorCode())
2630                                 .isEqualTo(netError);
2631                         failedExpectation.set(
2632                                 ((NetworkException) error).getCronetInternalErrorCode()
2633                                         != netError);
2634                         done.open();
2635                     }
2636 
2637                     @Override
2638                     public void onCanceled(UrlRequest request, UrlResponseInfo info) {
2639                         failedExpectation.set(true);
2640                         fail();
2641                     }
2642                 };
2643 
2644         UrlRequest.Builder builder =
2645                 mTestRule
2646                         .getTestFramework()
2647                         .getEngine()
2648                         .newUrlRequestBuilder(
2649                                 MockUrlRequestJobFactory.getMockUrlWithFailure(
2650                                         FailurePhase.START, netError),
2651                                 callback,
2652                                 Executors.newSingleThreadExecutor());
2653         final UrlRequest urlRequest = builder.build();
2654         urlRequest.start();
2655         done.block();
2656         // Check that onFailed is called.
2657         assertThat(failedExpectation.get()).isFalse();
2658     }
2659 
checkSpecificErrorCode( int netError, int errorCode, String name, boolean immediatelyRetryable)2660     private void checkSpecificErrorCode(
2661             int netError, int errorCode, String name, boolean immediatelyRetryable)
2662             throws Exception {
2663         TestUrlRequestCallback callback =
2664                 startAndWaitForComplete(
2665                         MockUrlRequestJobFactory.getMockUrlWithFailure(
2666                                 FailurePhase.START, netError));
2667         assertThat(callback.getResponseInfo()).isNull();
2668         assertThat(callback.mError).isNotNull();
2669         assertThat(((NetworkException) callback.mError).getCronetInternalErrorCode())
2670                 .isEqualTo(netError);
2671         assertThat(((NetworkException) callback.mError).getErrorCode()).isEqualTo(errorCode);
2672         assertThat(((NetworkException) callback.mError).immediatelyRetryable())
2673                 .isEqualTo(immediatelyRetryable);
2674         assertThat(callback.mError)
2675                 .hasMessageThat()
2676                 .contains("Exception in CronetUrlRequest: net::ERR_" + name);
2677         assertThat(callback.mRedirectCount).isEqualTo(0);
2678         assertThat(callback.mOnErrorCalled).isTrue();
2679         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_FAILED);
2680     }
2681 
2682     // Returns the contents of byteBuffer, from its position() to its limit(),
2683     // as a String. Does not modify byteBuffer's position().
bufferContentsToString(ByteBuffer byteBuffer, int start, int end)2684     private String bufferContentsToString(ByteBuffer byteBuffer, int start, int end) {
2685         // Use a duplicate to avoid modifying byteBuffer.
2686         ByteBuffer duplicate = byteBuffer.duplicate();
2687         duplicate.position(start);
2688         duplicate.limit(end);
2689         byte[] contents = new byte[duplicate.remaining()];
2690         duplicate.get(contents);
2691         return new String(contents);
2692     }
2693 
assertResponseStepCanceled(TestUrlRequestCallback callback)2694     private void assertResponseStepCanceled(TestUrlRequestCallback callback) {
2695         if (callback.mResponseStep == ResponseStep.ON_FAILED && callback.mError != null) {
2696             throw new Error(
2697                     "Unexpected response state: " + ResponseStep.ON_FAILED, callback.mError);
2698         }
2699         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_CANCELED);
2700     }
2701 
2702     @Test
2703     @SmallTest
2704     @RequiresMinAndroidApi(Build.VERSION_CODES.N)
2705     // Used for Android's NetworkSecurityPolicy added in Nougat
testCleartextTrafficBlocked()2706     public void testCleartextTrafficBlocked() throws Exception {
2707         final int cleartextNotPermitted = -29;
2708         // This hostname needs to match the one in network_security_config.xml and the one used
2709         // by QuicTestServer.
2710         // https requests to it are tested in QuicTest, so this checks that we're only blocking
2711         // cleartext.
2712         final String url = "http://example.com/simple.txt";
2713         TestUrlRequestCallback callback = startAndWaitForComplete(url);
2714         assertThat(callback.getResponseInfo()).isNull();
2715         assertThat(callback.mError).isNotNull();
2716         // NetworkException#getCronetInternalErrorCode is exposed only by the native
2717         // implementation.
2718         if (mTestRule.implementationUnderTest() == CronetImplementation.STATICALLY_LINKED) {
2719             assertThat(((NetworkException) callback.mError).getCronetInternalErrorCode())
2720                     .isEqualTo(cleartextNotPermitted);
2721         }
2722     }
2723 
2724     @Test
2725     @SmallTest
2726     /**
2727      * Open many connections and cancel them right away. This test verifies all internal sockets and
2728      * other Closeables are properly closed. See crbug.com/726193.
2729      */
testGzipCancel()2730     public void testGzipCancel() throws Exception {
2731         String url = NativeTestServer.getFileURL("/gzipped.html");
2732         for (int i = 0; i < 100; i++) {
2733             TestUrlRequestCallback callback = new TestUrlRequestCallback();
2734             callback.setAutoAdvance(false);
2735             UrlRequest urlRequest =
2736                     mTestRule
2737                             .getTestFramework()
2738                             .getEngine()
2739                             .newUrlRequestBuilder(url, callback, callback.getExecutor())
2740                             .build();
2741             urlRequest.start();
2742             urlRequest.cancel();
2743             // If the test blocks until each UrlRequest finishes before starting the next UrlRequest
2744             // then it never catches the leak. If it starts all UrlRequests and then blocks until
2745             // all UrlRequests finish, it only catches the leak ~10% of the time. In its current
2746             // form it appears to catch the leak ~70% of the time.
2747             // Catching the leak may require a lot of busy threads so that the cancel() happens
2748             // before the UrlRequest has made much progress (and set mCurrentUrlConnection and
2749             // mResponseChannel).  This may be why blocking until each UrlRequest finishes doesn't
2750             // catch the leak.
2751             // The other quirk of this is that from teardown(), JavaCronetEngine.shutdown() is
2752             // called which calls ExecutorService.shutdown() which doesn't wait for the thread to
2753             // finish running tasks, and then teardown() calls GC looking for leaks. One possible
2754             // modification would be to expose the ExecutorService and then have tests call
2755             // awaitTermination() but this would complicate things, and adding a 1s sleep() to
2756             // allow the ExecutorService to terminate did not increase the chances of catching the
2757             // leak.
2758         }
2759     }
2760 
2761     @Test
2762     @SmallTest
2763     @RequiresMinApi(8) // JavaUrlRequest fixed in API level 8: crrev.com/499303
2764     /** Do a HEAD request and get back a 404. */
test404Head()2765     public void test404Head() throws Exception {
2766         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2767         UrlRequest.Builder builder =
2768                 mTestRule
2769                         .getTestFramework()
2770                         .getEngine()
2771                         .newUrlRequestBuilder(
2772                                 NativeTestServer.getFileURL("/notfound.html"),
2773                                 callback,
2774                                 callback.getExecutor());
2775         builder.setHttpMethod("HEAD").build().start();
2776         callback.blockForDone();
2777     }
2778 
2779     @Test
2780     @SmallTest
2781     @RequiresMinApi(9) // Tagging support added in API level 9: crrev.com/c/chromium/src/+/930086
2782     @RequiresMinAndroidApi(Build.VERSION_CODES.M) // crbug/1301957
testTagging()2783     public void testTagging() throws Exception {
2784         if (!CronetTestUtil.nativeCanGetTaggedBytes()) {
2785             Log.i(TAG, "Skipping test - GetTaggedBytes unsupported.");
2786             return;
2787         }
2788         String url = NativeTestServer.getEchoMethodURL();
2789 
2790         // Test untagged requests are given tag 0.
2791         int tag = 0;
2792         long priorBytes = CronetTestUtil.nativeGetTaggedBytes(tag);
2793         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2794         ExperimentalUrlRequest.Builder builder =
2795                 mTestRule
2796                         .getTestFramework()
2797                         .getEngine()
2798                         .newUrlRequestBuilder(url, callback, callback.getExecutor());
2799         builder.build().start();
2800         callback.blockForDone();
2801         assertThat(CronetTestUtil.nativeGetTaggedBytes(tag)).isGreaterThan(priorBytes);
2802 
2803         // Test explicit tagging.
2804         tag = 0x12345678;
2805         priorBytes = CronetTestUtil.nativeGetTaggedBytes(tag);
2806         callback = new TestUrlRequestCallback();
2807         builder =
2808                 mTestRule
2809                         .getTestFramework()
2810                         .getEngine()
2811                         .newUrlRequestBuilder(url, callback, callback.getExecutor());
2812         assertThat(builder).isEqualTo(builder.setTrafficStatsTag(tag));
2813         builder.build().start();
2814         callback.blockForDone();
2815         assertThat(CronetTestUtil.nativeGetTaggedBytes(tag)).isGreaterThan(priorBytes);
2816 
2817         // Test a different tag value to make sure reused connections are retagged.
2818         tag = 0x87654321;
2819         priorBytes = CronetTestUtil.nativeGetTaggedBytes(tag);
2820         callback = new TestUrlRequestCallback();
2821         builder =
2822                 mTestRule
2823                         .getTestFramework()
2824                         .getEngine()
2825                         .newUrlRequestBuilder(url, callback, callback.getExecutor());
2826         assertThat(builder).isEqualTo(builder.setTrafficStatsTag(tag));
2827         builder.build().start();
2828         callback.blockForDone();
2829         assertThat(CronetTestUtil.nativeGetTaggedBytes(tag)).isGreaterThan(priorBytes);
2830 
2831         // Test tagging with our UID.
2832         tag = 0;
2833         priorBytes = CronetTestUtil.nativeGetTaggedBytes(tag);
2834         callback = new TestUrlRequestCallback();
2835         builder =
2836                 mTestRule
2837                         .getTestFramework()
2838                         .getEngine()
2839                         .newUrlRequestBuilder(url, callback, callback.getExecutor());
2840         assertThat(builder).isEqualTo(builder.setTrafficStatsUid(Process.myUid()));
2841         builder.build().start();
2842         callback.blockForDone();
2843         assertThat(CronetTestUtil.nativeGetTaggedBytes(tag)).isGreaterThan(priorBytes);
2844     }
2845 
2846     @Test
2847     @SmallTest
2848     /**
2849      * Initiate many requests concurrently to make sure neither Cronet implementation crashes.
2850      * Regression test for https://crbug.com/844031.
2851      */
testManyRequests()2852     public void testManyRequests() throws Exception {
2853         String url = NativeTestServer.getMultiRedirectURL();
2854         final int numRequests = 2000;
2855         TestUrlRequestCallback[] callbacks = new TestUrlRequestCallback[numRequests];
2856         UrlRequest[] requests = new UrlRequest[numRequests];
2857         for (int i = 0; i < numRequests; i++) {
2858             // Share the first callback's executor to avoid creating too many single-threaded
2859             // executors and hence too many threads.
2860             if (i == 0) {
2861                 callbacks[i] = new TestUrlRequestCallback();
2862             } else {
2863                 callbacks[i] = new TestUrlRequestCallback(callbacks[0].getExecutor());
2864             }
2865             UrlRequest.Builder builder =
2866                     mTestRule
2867                             .getTestFramework()
2868                             .getEngine()
2869                             .newUrlRequestBuilder(url, callbacks[i], callbacks[i].getExecutor());
2870             requests[i] = builder.build();
2871         }
2872         for (UrlRequest request : requests) {
2873             request.start();
2874         }
2875         for (UrlRequest request : requests) {
2876             request.cancel();
2877         }
2878         for (TestUrlRequestCallback callback : callbacks) {
2879             callback.blockForDone();
2880         }
2881     }
2882 
2883     @Test
2884     @SmallTest
testSetIdempotency()2885     public void testSetIdempotency() throws Exception {
2886         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2887         ExperimentalUrlRequest.Builder builder =
2888                 mTestRule
2889                         .getTestFramework()
2890                         .getEngine()
2891                         .newUrlRequestBuilder(
2892                                 NativeTestServer.getEchoMethodURL(),
2893                                 callback,
2894                                 callback.getExecutor());
2895         assertThat(builder)
2896                 .isEqualTo(builder.setIdempotency(ExperimentalUrlRequest.Builder.IDEMPOTENT));
2897 
2898         TestUploadDataProvider dataProvider =
2899                 new TestUploadDataProvider(
2900                         TestUploadDataProvider.SuccessCallbackMode.SYNC, callback.getExecutor());
2901         dataProvider.addRead("test".getBytes());
2902         builder.setUploadDataProvider(dataProvider, callback.getExecutor());
2903         builder.addHeader("Content-Type", "useless/string");
2904         builder.build().start();
2905         callback.blockForDone();
2906         dataProvider.assertClosed();
2907 
2908         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
2909         assertThat(callback.mResponseAsString).isEqualTo("POST");
2910     }
2911 
2912     @Test
2913     @RequiresMinAndroidApi(Build.VERSION_CODES.M)
testBindToInvalidNetworkFails()2914     public void testBindToInvalidNetworkFails() {
2915         String url = NativeTestServer.getEchoMethodURL();
2916         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().getEngine();
2917         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2918         ExperimentalUrlRequest.Builder builder =
2919                 cronetEngine.newUrlRequestBuilder(url, callback, callback.getExecutor());
2920 
2921         if (mTestRule.implementationUnderTest() == CronetImplementation.AOSP_PLATFORM) {
2922             // android.net.http.UrlRequestBuilder#bindToNetwork requires an android.net.Network
2923             // object. So, in this case, it will be the wrapper layer that will fail to translate
2924             // that to a Network, not something in net's code. Hence, the failure will manifest
2925             // itself at bind time, not at request execution time.
2926             // Note: this will never happen in prod, as translation failure can only happen if we're
2927             // given a fake networkHandle.
2928             assertThrows(
2929                     IllegalArgumentException.class,
2930                     () -> builder.bindToNetwork(-150 /* invalid network handle */));
2931             return;
2932         }
2933 
2934         builder.bindToNetwork(-150 /* invalid network handle */);
2935         builder.build().start();
2936         callback.blockForDone();
2937         assertThat(callback.mError).isNotNull();
2938         if (mTestRule.implementationUnderTest() == CronetImplementation.FALLBACK) {
2939             assertThat(callback.mError).isInstanceOf(CronetExceptionImpl.class);
2940             assertThat(callback.mError).hasCauseThat().isInstanceOf(NetworkExceptionImpl.class);
2941         } else {
2942             assertThat(callback.mError).isInstanceOf(NetworkExceptionImpl.class);
2943         }
2944     }
2945 
2946     @Test
2947     @RequiresMinAndroidApi(Build.VERSION_CODES.M)
testBindToDefaultNetworkSucceeds()2948     public void testBindToDefaultNetworkSucceeds() {
2949         String url = NativeTestServer.getEchoMethodURL();
2950         ConnectivityManagerDelegate delegate =
2951                 new ConnectivityManagerDelegate(mTestRule.getTestFramework().getContext());
2952         Network defaultNetwork = delegate.getDefaultNetwork();
2953         assume().that(defaultNetwork).isNotNull();
2954 
2955         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().getEngine();
2956         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2957         ExperimentalUrlRequest.Builder builder =
2958                 cronetEngine.newUrlRequestBuilder(url, callback, callback.getExecutor());
2959         builder.bindToNetwork(defaultNetwork.getNetworkHandle());
2960         builder.build().start();
2961         callback.blockForDone();
2962         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
2963     }
2964 
2965     @NativeMethods("cronet_tests")
2966     interface Natives {
2967         // Return connection migration disable load flag value.
getConnectionMigrationDisableLoadFlag()2968         int getConnectionMigrationDisableLoadFlag();
2969     }
2970 }
2971