• 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 org.junit.Assert.assertEquals;
8 import static org.junit.Assert.assertFalse;
9 import static org.junit.Assert.assertNotNull;
10 import static org.junit.Assert.assertNull;
11 import static org.junit.Assert.assertTrue;
12 import static org.junit.Assert.fail;
13 
14 import static android.net.http.HttpEngine.Builder.HTTP_CACHE_IN_MEMORY;
15 import static org.chromium.net.CronetTestRule.assertContains;
16 import static org.chromium.net.CronetTestRule.getContext;
17 import static org.chromium.net.CronetTestRule.getTestStorage;
18 
19 import android.net.http.HttpEngine;
20 import android.net.http.HttpException;
21 import android.net.http.ExperimentalHttpEngine;
22 import android.net.http.ExperimentalUrlRequest;
23 import android.net.http.UrlRequest;
24 import android.net.http.UrlResponseInfo;
25 import android.content.Context;
26 import android.content.ContextWrapper;
27 import android.net.Network;
28 import android.os.Build;
29 import android.os.ConditionVariable;
30 import android.os.Handler;
31 import android.os.Looper;
32 import android.os.Process;
33 
34 import androidx.test.ext.junit.runners.AndroidJUnit4;
35 import androidx.test.filters.SmallTest;
36 
37 import org.json.JSONObject;
38 import org.junit.After;
39 import org.junit.Before;
40 import org.junit.Rule;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 
44 import org.chromium.base.FileUtils;
45 import org.chromium.base.PathUtils;
46 import org.chromium.base.annotations.JNINamespace;
47 import org.chromium.base.annotations.NativeMethods;
48 import org.chromium.net.CronetTestRule.CronetTestFramework;
49 import org.chromium.net.CronetTestRule.OnlyRunNativeCronet;
50 import org.chromium.net.CronetTestRule.RequiresMinAndroidApi;
51 import org.chromium.net.CronetTestRule.RequiresMinApi;
52 import org.chromium.net.NetworkChangeNotifierAutoDetect.ConnectivityManagerDelegate;
53 import org.chromium.net.TestUrlRequestCallback.ResponseStep;
54 import org.chromium.net.impl.CronetLibraryLoader;
55 import org.chromium.net.impl.CronetUrlRequestContext;
56 
57 import java.io.BufferedReader;
58 import java.io.File;
59 import java.io.FileReader;
60 import java.net.URL;
61 import java.nio.ByteBuffer;
62 import java.util.Arrays;
63 import java.util.concurrent.Callable;
64 import java.util.concurrent.Executor;
65 import java.util.concurrent.FutureTask;
66 import java.util.concurrent.atomic.AtomicReference;
67 
68 /**
69  * Test CronetEngine.
70  */
71 @RunWith(AndroidJUnit4.class)
72 @JNINamespace("cronet")
73 public class CronetUrlRequestContextTest {
74     @Rule
75     public final CronetTestRule mTestRule = new CronetTestRule();
76 
77     private static final String TAG = CronetUrlRequestContextTest.class.getSimpleName();
78 
79     // URLs used for tests.
80     private static final String MOCK_CRONET_TEST_FAILED_URL =
81             "http://mock.failed.request/-2";
82     private static final String MOCK_CRONET_TEST_SUCCESS_URL =
83             "http://mock.http/success.txt";
84     private static final int MAX_FILE_SIZE = 1000000000;
85     private static final int NUM_EVENT_FILES = 10;
86     private String mUrl;
87     private String mUrl404;
88     private String mUrl500;
89 
90     @Before
setUp()91     public void setUp() throws Exception {
92         assertTrue(NativeTestServer.startNativeTestServer(getContext()));
93         mUrl = NativeTestServer.getSuccessURL();
94         mUrl404 = NativeTestServer.getNotFoundURL();
95         mUrl500 = NativeTestServer.getServerErrorURL();
96     }
97 
98     @After
tearDown()99     public void tearDown() throws Exception {
100         NativeTestServer.shutdownNativeTestServer();
101     }
102 
103     static class RequestThread extends Thread {
104         public TestUrlRequestCallback mCallback;
105 
106         final String mUrl;
107         final ConditionVariable mRunBlocker;
108 
RequestThread(String url, ConditionVariable runBlocker)109         public RequestThread(String url, ConditionVariable runBlocker) {
110             mUrl = url;
111             mRunBlocker = runBlocker;
112         }
113 
114         @Override
run()115         public void run() {
116             mRunBlocker.block();
117             HttpEngine cronetEngine = new HttpEngine.Builder(getContext()).build();
118             mCallback = new TestUrlRequestCallback();
119             UrlRequest.Builder urlRequestBuilder =
120                     cronetEngine.newUrlRequestBuilder(mUrl, mCallback.getExecutor(), mCallback);
121             urlRequestBuilder.build().start();
122             mCallback.blockForDone();
123         }
124     }
125 
126     /**
127      * Callback that shutdowns the request context when request has succeeded
128      * or failed.
129      */
130     static class ShutdownTestUrlRequestCallback extends TestUrlRequestCallback {
131         private final HttpEngine mCronetEngine;
132         private final ConditionVariable mCallbackCompletionBlock = new ConditionVariable();
133 
ShutdownTestUrlRequestCallback(HttpEngine cronetEngine)134         ShutdownTestUrlRequestCallback(HttpEngine cronetEngine) {
135             mCronetEngine = cronetEngine;
136         }
137 
138         @Override
onSucceeded(UrlRequest request, UrlResponseInfo info)139         public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
140             super.onSucceeded(request, info);
141             mCronetEngine.shutdown();
142             mCallbackCompletionBlock.open();
143         }
144 
145         @Override
onFailed(UrlRequest request, UrlResponseInfo info, HttpException error)146         public void onFailed(UrlRequest request, UrlResponseInfo info, HttpException error) {
147             super.onFailed(request, info, error);
148             mCronetEngine.shutdown();
149             mCallbackCompletionBlock.open();
150         }
151 
152         // Wait for request completion callback.
blockForCallbackToComplete()153         void blockForCallbackToComplete() {
154             mCallbackCompletionBlock.block();
155         }
156     }
157 
158     @Test
159     @SmallTest
160     @SuppressWarnings("deprecation")
testConfigUserAgent()161     public void testConfigUserAgent() throws Exception {
162         String userAgentName = "User-Agent";
163         String userAgentValue = "User-Agent-Value";
164         ExperimentalHttpEngine.Builder cronetEngineBuilder =
165                 new ExperimentalHttpEngine.Builder(getContext());
166         cronetEngineBuilder.setUserAgent(userAgentValue);
167         final HttpEngine cronetEngine = cronetEngineBuilder.build();
168         NativeTestServer.shutdownNativeTestServer(); // startNativeTestServer returns false if it's
169         // already running
170         assertTrue(NativeTestServer.startNativeTestServer(getContext()));
171         TestUrlRequestCallback callback = new TestUrlRequestCallback();
172         UrlRequest.Builder urlRequestBuilder = cronetEngine.newUrlRequestBuilder(
173                 NativeTestServer.getEchoHeaderURL(userAgentName), callback.getExecutor(), callback);
174         urlRequestBuilder.build().start();
175         callback.blockForDone();
176         assertEquals(userAgentValue, callback.mResponseAsString);
177     }
178 
179     @Test
180     @SmallTest
181     // TODO: Remove the annotation after fixing http://crbug.com/637979 & http://crbug.com/637972
182     @OnlyRunNativeCronet
testShutdown()183     public void testShutdown() throws Exception {
184         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
185         ShutdownTestUrlRequestCallback callback =
186                 new ShutdownTestUrlRequestCallback(testFramework.mCronetEngine);
187         // Block callback when response starts to verify that shutdown fails
188         // if there are active requests.
189         callback.setAutoAdvance(false);
190         UrlRequest.Builder urlRequestBuilder = testFramework.mCronetEngine.newUrlRequestBuilder(
191                 mUrl, callback.getExecutor(), callback);
192         UrlRequest urlRequest = urlRequestBuilder.build();
193         urlRequest.start();
194         try {
195             testFramework.mCronetEngine.shutdown();
196             fail("Should throw an exception");
197         } catch (Exception e) {
198             assertEquals("Cannot shutdown with active requests.", e.getMessage());
199         }
200 
201         callback.waitForNextStep();
202         assertEquals(ResponseStep.ON_RESPONSE_STARTED, callback.mResponseStep);
203         try {
204             testFramework.mCronetEngine.shutdown();
205             fail("Should throw an exception");
206         } catch (Exception e) {
207             assertEquals("Cannot shutdown with active requests.", e.getMessage());
208         }
209         callback.startNextRead(urlRequest);
210 
211         callback.waitForNextStep();
212         assertEquals(ResponseStep.ON_READ_COMPLETED, callback.mResponseStep);
213         try {
214             testFramework.mCronetEngine.shutdown();
215             fail("Should throw an exception");
216         } catch (Exception e) {
217             assertEquals("Cannot shutdown with active requests.", e.getMessage());
218         }
219 
220         // May not have read all the data, in theory. Just enable auto-advance
221         // and finish the request.
222         callback.setAutoAdvance(true);
223         callback.startNextRead(urlRequest);
224         callback.blockForDone();
225         callback.blockForCallbackToComplete();
226         callback.shutdownExecutor();
227     }
228 
229     @Test
230     @SmallTest
231     @OnlyRunNativeCronet
testShutdownDuringInit()232     public void testShutdownDuringInit() throws Exception {
233         final ConditionVariable block = new ConditionVariable(false);
234 
235         // Post a task to main thread to block until shutdown is called to test
236         // scenario when shutdown is called right after construction before
237         // context is fully initialized on the main thread.
238         Runnable blockingTask = new Runnable() {
239             @Override
240             public void run() {
241                 try {
242                     block.block();
243                 } catch (Exception e) {
244                     fail("Caught " + e.getMessage());
245                 }
246             }
247         };
248         // Ensure that test is not running on the main thread.
249         assertTrue(Looper.getMainLooper() != Looper.myLooper());
250         new Handler(Looper.getMainLooper()).post(blockingTask);
251 
252         // Create new request context, but its initialization on the main thread
253         // will be stuck behind blockingTask.
254         final CronetUrlRequestContext cronetEngine =
255                 (CronetUrlRequestContext) new HttpEngine.Builder(getContext()).build();
256         // Unblock the main thread, so context gets initialized and shutdown on
257         // it.
258         block.open();
259         // Shutdown will wait for init to complete on main thread.
260         cronetEngine.shutdown();
261         // Verify that context is shutdown.
262         try {
263             cronetEngine.getUrlRequestContextAdapter();
264             fail("Should throw an exception.");
265         } catch (Exception e) {
266             assertEquals("Engine is shut down.", e.getMessage());
267         }
268     }
269 
270     @Test
271     @SmallTest
272     @OnlyRunNativeCronet
testInitAndShutdownOnMainThread()273     public void testInitAndShutdownOnMainThread() throws Exception {
274         final ConditionVariable block = new ConditionVariable(false);
275 
276         // Post a task to main thread to init and shutdown on the main thread.
277         Runnable blockingTask = new Runnable() {
278             @Override
279             public void run() {
280                 // Create new request context, loading the library.
281                 final CronetUrlRequestContext cronetEngine =
282                         (CronetUrlRequestContext) new HttpEngine.Builder(getContext()).build();
283                 // Shutdown right after init.
284                 cronetEngine.shutdown();
285                 // Verify that context is shutdown.
286                 try {
287                     cronetEngine.getUrlRequestContextAdapter();
288                     fail("Should throw an exception.");
289                 } catch (Exception e) {
290                     assertEquals("Engine is shut down.", e.getMessage());
291                 }
292                 block.open();
293             }
294         };
295         new Handler(Looper.getMainLooper()).post(blockingTask);
296         // Wait for shutdown to complete on main thread.
297         block.block();
298     }
299 
300     @Test
301     @SmallTest
302     @OnlyRunNativeCronet // JavaCronetEngine doesn't support throwing on repeat shutdown()
testMultipleShutdown()303     public void testMultipleShutdown() throws Exception {
304         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
305         try {
306             testFramework.mCronetEngine.shutdown();
307             testFramework.mCronetEngine.shutdown();
308             fail("Should throw an exception");
309         } catch (Exception e) {
310             assertEquals("Engine is shut down.", e.getMessage());
311         }
312     }
313 
314     @Test
315     @SmallTest
316     // TODO: Remove the annotation after fixing http://crbug.com/637972
317     @OnlyRunNativeCronet
testShutdownAfterError()318     public void testShutdownAfterError() throws Exception {
319         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
320         ShutdownTestUrlRequestCallback callback =
321                 new ShutdownTestUrlRequestCallback(testFramework.mCronetEngine);
322         UrlRequest.Builder urlRequestBuilder = testFramework.mCronetEngine.newUrlRequestBuilder(
323                 MOCK_CRONET_TEST_FAILED_URL, callback.getExecutor(), callback);
324         urlRequestBuilder.build().start();
325         callback.blockForDone();
326         assertTrue(callback.mOnErrorCalled);
327         callback.blockForCallbackToComplete();
328         callback.shutdownExecutor();
329     }
330 
331     @Test
332     @SmallTest
333     @OnlyRunNativeCronet // JavaCronetEngine doesn't support throwing on shutdown()
testShutdownAfterCancel()334     public void testShutdownAfterCancel() throws Exception {
335         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
336         TestUrlRequestCallback callback = new TestUrlRequestCallback();
337         // Block callback when response starts to verify that shutdown fails
338         // if there are active requests.
339         callback.setAutoAdvance(false);
340         UrlRequest.Builder urlRequestBuilder = testFramework.mCronetEngine.newUrlRequestBuilder(
341                 mUrl, callback.getExecutor(), callback);
342         UrlRequest urlRequest = urlRequestBuilder.build();
343         urlRequest.start();
344         try {
345             testFramework.mCronetEngine.shutdown();
346             fail("Should throw an exception");
347         } catch (Exception e) {
348             assertEquals("Cannot shutdown with active requests.", e.getMessage());
349         }
350         callback.waitForNextStep();
351         assertEquals(ResponseStep.ON_RESPONSE_STARTED, callback.mResponseStep);
352         urlRequest.cancel();
353         testFramework.mCronetEngine.shutdown();
354     }
355 
356     @Test
357     @SmallTest
358     @OnlyRunNativeCronet // Only chromium based Cronet supports the multi-network API
359     @RequiresMinAndroidApi(Build.VERSION_CODES.M) // Multi-network API is supported from Marshmallow
testNetworkBoundContextLifetime()360     public void testNetworkBoundContextLifetime() throws Exception {
361         // Multi-network API is available starting from Android Lollipop.
362         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
363         ConnectivityManagerDelegate delegate = new ConnectivityManagerDelegate(getContext());
364         Network defaultNetwork = delegate.getDefaultNetwork();
365         if (defaultNetwork == null) {
366             testFramework.mCronetEngine.shutdown();
367             return;
368         }
369 
370         TestUrlRequestCallback callback = new TestUrlRequestCallback();
371         // Allows to check the underlying network-bound context state while the request is in
372         // progress.
373         callback.setAutoAdvance(false);
374 
375         UrlRequest.Builder urlRequestBuilder =
376                 testFramework.mCronetEngine.newUrlRequestBuilder(
377                         mUrl, callback.getExecutor(), callback);
378         urlRequestBuilder.bindToNetwork(defaultNetwork);
379         UrlRequest urlRequest = urlRequestBuilder.build();
380 
381         assertFalse(
382                 ApiHelper.doesContextExistForNetwork(testFramework.mCronetEngine, defaultNetwork));
383         urlRequest.start();
384         assertTrue(
385                 ApiHelper.doesContextExistForNetwork(testFramework.mCronetEngine, defaultNetwork));
386 
387         // Resume callback execution.
388         callback.waitForNextStep();
389         assertEquals(ResponseStep.ON_RESPONSE_STARTED, callback.mResponseStep);
390         callback.setAutoAdvance(true);
391         callback.startNextRead(urlRequest);
392         callback.blockForDone();
393         assertNull(callback.mError);
394 
395         // The default network should still be active, hence the underlying network-bound context
396         // should still be there.
397         assertTrue(
398                 ApiHelper.doesContextExistForNetwork(testFramework.mCronetEngine, defaultNetwork));
399 
400         // Fake disconnect event for the default network, this should destroy the underlying
401         // network-bound context.
402         FutureTask<Void> task = new FutureTask<Void>(new Callable<Void>() {
403             @Override
404             public Void call() {
405                 NetworkChangeNotifier.fakeNetworkDisconnected(defaultNetwork.getNetworkHandle());
406                 return null;
407             }
408         });
409         CronetLibraryLoader.postToInitThread(task);
410         task.get();
411         assertFalse(
412                 ApiHelper.doesContextExistForNetwork(testFramework.mCronetEngine, defaultNetwork));
413         testFramework.mCronetEngine.shutdown();
414     }
415 
416     @Test
417     @SmallTest
418     @OnlyRunNativeCronet // Only chromium based Cronet supports the multi-network API
419     @RequiresMinAndroidApi(Build.VERSION_CODES.M) // Multi-network API is supported from Marshmallow
testNetworkBoundRequestCancel()420     public void testNetworkBoundRequestCancel() throws Exception {
421         // Upon a network disconnection, NCN posts a tasks onto the network thread that calls
422         // CronetContext::NetworkTasks::OnNetworkDisconnected.
423         // Calling urlRequest.cancel() also, after some hoops, ends up in a posted tasks onto the
424         // network thread that calls CronetURLRequest::NetworkTasks::Destroy.
425         // Depending on their implementation this can lead to UAF, this test is here to prevent that
426         // from being introduced in the future.
427         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
428         TestUrlRequestCallback callback = new TestUrlRequestCallback();
429         callback.setAutoAdvance(false);
430         UrlRequest.Builder urlRequestBuilder =
431                 testFramework.mCronetEngine.newUrlRequestBuilder(
432                         mUrl, callback, callback.getExecutor());
433         ConnectivityManagerDelegate delegate = new ConnectivityManagerDelegate(getContext());
434         Network defaultNetwork = delegate.getDefaultNetwork();
435         if (defaultNetwork == null) {
436             testFramework.mCronetEngine.shutdown();
437             return;
438         }
439 
440         urlRequestBuilder.bindToNetwork(defaultNetwork);
441         UrlRequest urlRequest = urlRequestBuilder.build();
442 
443         assertFalse(
444                 ApiHelper.doesContextExistForNetwork(testFramework.mCronetEngine, defaultNetwork));
445         urlRequest.start();
446         assertTrue(
447                 ApiHelper.doesContextExistForNetwork(testFramework.mCronetEngine, defaultNetwork));
448 
449         callback.waitForNextStep();
450         assertEquals(ResponseStep.ON_RESPONSE_STARTED, callback.mResponseStep);
451         assertTrue(
452                 ApiHelper.doesContextExistForNetwork(testFramework.mCronetEngine, defaultNetwork));
453         // Cronet registers for NCN notifications on the init thread (see
454         // CronetLibraryLoader#ensureInitializedOnInitThread), hence we need to trigger fake
455         // notifications from there.
456         CronetLibraryLoader.postToInitThread(new Runnable() {
457             @Override
458             public void run() {
459                 NetworkChangeNotifier.fakeNetworkDisconnected(defaultNetwork.getNetworkHandle());
460                 // Queue cancel after disconnect event.
461                 urlRequest.cancel();
462             }
463         });
464         // Wait until the cancel call propagates (this would block undefinitely without that since
465         // we previously set auto advance to false).
466         callback.blockForDone();
467         // mError should be null due to urlRequest.cancel().
468         assertNull(callback.mError);
469         // urlRequest.cancel(); should destroy the underlying network bound context.
470         assertFalse(
471                 ApiHelper.doesContextExistForNetwork(testFramework.mCronetEngine, defaultNetwork));
472         testFramework.mCronetEngine.shutdown();
473     }
474 
475     @Test
476     @SmallTest
477     @OnlyRunNativeCronet // No netlogs for pure java impl
testNetLog()478     public void testNetLog() throws Exception {
479         Context context = getContext();
480         File directory = new File(PathUtils.getDataDirectory());
481         File file = File.createTempFile("cronet", "json", directory);
482         HttpEngine cronetEngine = new HttpEngine.Builder(context).build();
483         // Start NetLog immediately after the request context is created to make
484         // sure that the call won't crash the app even when the native request
485         // context is not fully initialized. See crbug.com/470196.
486         cronetEngine.startNetLogToFile(file.getPath(), false);
487 
488         // Start a request.
489         TestUrlRequestCallback callback = new TestUrlRequestCallback();
490         UrlRequest.Builder urlRequestBuilder =
491                 cronetEngine.newUrlRequestBuilder(mUrl, callback.getExecutor(), callback);
492         urlRequestBuilder.build().start();
493         callback.blockForDone();
494         cronetEngine.stopNetLog();
495         assertTrue(file.exists());
496         assertTrue(file.length() != 0);
497         assertFalse(hasBytesInNetLog(file));
498         assertTrue(file.delete());
499         assertTrue(!file.exists());
500     }
501 
502     @Test
503     @SmallTest
504     @OnlyRunNativeCronet // No netlogs for pure java impl
testBoundedFileNetLog()505     public void testBoundedFileNetLog() throws Exception {
506         Context context = getContext();
507         File directory = new File(PathUtils.getDataDirectory());
508         File netLogDir = new File(directory, "NetLog");
509         assertFalse(netLogDir.exists());
510         assertTrue(netLogDir.mkdir());
511         File logFile = new File(netLogDir, "netlog.json");
512         ExperimentalHttpEngine cronetEngine =
513                 new ExperimentalHttpEngine.Builder(context).build();
514         // Start NetLog immediately after the request context is created to make
515         // sure that the call won't crash the app even when the native request
516         // context is not fully initialized. See crbug.com/470196.
517         cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
518 
519         // Start a request.
520         TestUrlRequestCallback callback = new TestUrlRequestCallback();
521         UrlRequest.Builder urlRequestBuilder =
522                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
523         urlRequestBuilder.build().start();
524         callback.blockForDone();
525         cronetEngine.stopNetLog();
526         assertTrue(logFile.exists());
527         assertTrue(logFile.length() != 0);
528         assertFalse(hasBytesInNetLog(logFile));
529         FileUtils.recursivelyDeleteFile(netLogDir, FileUtils.DELETE_ALL);
530         assertFalse(netLogDir.exists());
531     }
532 
533     @Test
534     @SmallTest
535     @OnlyRunNativeCronet // No netlogs for pure java impl
536     // Tests that if stopNetLog is not explicity called, CronetEngine.shutdown()
537     // will take care of it. crbug.com/623701.
testNoStopNetLog()538     public void testNoStopNetLog() throws Exception {
539         Context context = getContext();
540         File directory = new File(PathUtils.getDataDirectory());
541         File file = File.createTempFile("cronet", "json", directory);
542         HttpEngine cronetEngine = new HttpEngine.Builder(context).build();
543         cronetEngine.startNetLogToFile(file.getPath(), false);
544 
545         // Start a request.
546         TestUrlRequestCallback callback = new TestUrlRequestCallback();
547         UrlRequest.Builder urlRequestBuilder =
548                 cronetEngine.newUrlRequestBuilder(mUrl, callback.getExecutor(), callback);
549         urlRequestBuilder.build().start();
550         callback.blockForDone();
551         // Shut down the engine without calling stopNetLog.
552         cronetEngine.shutdown();
553         assertTrue(file.exists());
554         assertTrue(file.length() != 0);
555         assertFalse(hasBytesInNetLog(file));
556         assertTrue(file.delete());
557         assertTrue(!file.exists());
558     }
559 
560     @Test
561     @SmallTest
562     @OnlyRunNativeCronet // No netlogs for pure java impl
563     // Tests that if stopNetLog is not explicity called, CronetEngine.shutdown()
564     // will take care of it. crbug.com/623701.
testNoStopBoundedFileNetLog()565     public void testNoStopBoundedFileNetLog() throws Exception {
566         Context context = getContext();
567         File directory = new File(PathUtils.getDataDirectory());
568         File netLogDir = new File(directory, "NetLog");
569         assertFalse(netLogDir.exists());
570         assertTrue(netLogDir.mkdir());
571         File logFile = new File(netLogDir, "netlog.json");
572         ExperimentalHttpEngine cronetEngine =
573                 new ExperimentalHttpEngine.Builder(context).build();
574         cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
575 
576         // Start a request.
577         TestUrlRequestCallback callback = new TestUrlRequestCallback();
578         UrlRequest.Builder urlRequestBuilder =
579                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
580         urlRequestBuilder.build().start();
581         callback.blockForDone();
582         // Shut down the engine without calling stopNetLog.
583         cronetEngine.shutdown();
584         assertTrue(logFile.exists());
585         assertTrue(logFile.length() != 0);
586 
587         FileUtils.recursivelyDeleteFile(netLogDir, FileUtils.DELETE_ALL);
588         assertFalse(netLogDir.exists());
589     }
590 
591     @Test
592     @SmallTest
testGetActiveRequestCount()593     public void testGetActiveRequestCount() throws Exception {
594         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
595         HttpEngine cronetEngine = testFramework.mCronetEngine;
596         TestUrlRequestCallback callback1 = new TestUrlRequestCallback();
597         TestUrlRequestCallback callback2 = new TestUrlRequestCallback();
598         callback1.setAutoAdvance(false);
599         callback2.setAutoAdvance(false);
600         assertEquals(0, cronetEngine.getActiveRequestCount());
601         UrlRequest request1 =
602                 cronetEngine.newUrlRequestBuilder(mUrl, callback1, callback1.getExecutor()).build();
603         UrlRequest request2 =
604                 cronetEngine.newUrlRequestBuilder(mUrl, callback2, callback2.getExecutor()).build();
605         request1.start();
606         request2.start();
607         assertEquals(2, cronetEngine.getActiveRequestCount());
608         callback1.waitForNextStep();
609         callback1.setAutoAdvance(true);
610         callback1.startNextRead(request1);
611         callback1.blockForDone();
612         assertEquals(1, cronetEngine.getActiveRequestCount());
613         callback2.waitForNextStep();
614         callback2.setAutoAdvance(true);
615         callback2.startNextRead(request2);
616         callback2.blockForDone();
617         assertEquals(0, cronetEngine.getActiveRequestCount());
618     }
619 
620     @Test
621     @SmallTest
testGetActiveRequestCountOnReachingSucceeded()622     public void testGetActiveRequestCountOnReachingSucceeded() throws Exception {
623         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
624         HttpEngine cronetEngine = testFramework.mCronetEngine;
625         TestUrlRequestCallback callback = new TestUrlRequestCallback();
626         callback.setAutoAdvance(false);
627         callback.setBlockOnTerminalState(true);
628         assertEquals(0, cronetEngine.getActiveRequestCount());
629         UrlRequest request =
630                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build();
631         request.start();
632         assertEquals(1, cronetEngine.getActiveRequestCount());
633         callback.waitForNextStep();
634         callback.startNextRead(request);
635         callback.waitForNextStep();
636         callback.startNextRead(request);
637         callback.waitForTerminalToStart();
638         assertEquals(0, cronetEngine.getActiveRequestCount());
639         callback.setBlockOnTerminalState(false);
640     }
641 
642     @Test
643     @SmallTest
testGetActiveRequestCountOnReachingCancel()644     public void testGetActiveRequestCountOnReachingCancel() throws Exception {
645         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
646         HttpEngine cronetEngine = testFramework.mCronetEngine;
647         TestUrlRequestCallback callback = new TestUrlRequestCallback();
648         callback.setAutoAdvance(false);
649         callback.setBlockOnTerminalState(true);
650         assertEquals(0, cronetEngine.getActiveRequestCount());
651         UrlRequest request =
652                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build();
653         request.start();
654         assertEquals(1, cronetEngine.getActiveRequestCount());
655         request.cancel();
656         callback.waitForTerminalToStart();
657         assertEquals(0, cronetEngine.getActiveRequestCount());
658         callback.setBlockOnTerminalState(false);
659         assertTrue(callback.mOnCanceledCalled);
660         assertEquals(ResponseStep.ON_CANCELED, callback.mResponseStep);
661     }
662 
663     @Test
664     @SmallTest
testGetActiveRequestCountOnReachingFail()665     public void testGetActiveRequestCountOnReachingFail() throws Exception {
666         final String badUrl = "www.unreachable-url.com";
667         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
668         HttpEngine cronetEngine = testFramework.mCronetEngine;
669         TestUrlRequestCallback callback = new TestUrlRequestCallback();
670         callback.setAutoAdvance(false);
671         callback.setBlockOnTerminalState(true);
672         assertEquals(0, cronetEngine.getActiveRequestCount());
673         UrlRequest request =
674                 cronetEngine.newUrlRequestBuilder(badUrl, callback, callback.getExecutor()).build();
675         request.start();
676         assertEquals(1, cronetEngine.getActiveRequestCount());
677         callback.waitForTerminalToStart();
678         assertEquals(0, cronetEngine.getActiveRequestCount());
679         callback.setBlockOnTerminalState(false);
680         assertTrue(callback.mOnErrorCalled);
681         assertEquals(ResponseStep.ON_FAILED, callback.mResponseStep);
682     }
683 
684     @Test
685     @SmallTest
testGetActiveRequestCountWithCancel()686     public void testGetActiveRequestCountWithCancel() throws Exception {
687         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
688         HttpEngine cronetEngine = testFramework.mCronetEngine;
689         TestUrlRequestCallback callback1 = new TestUrlRequestCallback();
690         TestUrlRequestCallback callback2 = new TestUrlRequestCallback();
691         callback1.setAutoAdvance(false);
692         callback2.setAutoAdvance(false);
693         assertEquals(0, cronetEngine.getActiveRequestCount());
694         UrlRequest request1 =
695                 cronetEngine.newUrlRequestBuilder(mUrl, callback1, callback1.getExecutor()).build();
696         UrlRequest request2 =
697                 cronetEngine.newUrlRequestBuilder(mUrl, callback2, callback2.getExecutor()).build();
698         request1.start();
699         request2.start();
700         assertEquals(2, cronetEngine.getActiveRequestCount());
701         request1.cancel();
702         callback1.blockForDone();
703         assertTrue(callback1.mOnCanceledCalled);
704         assertEquals(ResponseStep.ON_CANCELED, callback1.mResponseStep);
705         assertEquals(1, cronetEngine.getActiveRequestCount());
706         callback2.waitForNextStep();
707         callback2.setAutoAdvance(true);
708         callback2.startNextRead(request2);
709         callback2.blockForDone();
710         assertEquals(0, cronetEngine.getActiveRequestCount());
711     }
712 
713     @Test
714     @SmallTest
testGetActiveRequestCountWithError()715     public void testGetActiveRequestCountWithError() throws Exception {
716         final String badUrl = "www.unreachable-url.com";
717         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
718         HttpEngine cronetEngine = testFramework.mCronetEngine;
719         TestUrlRequestCallback callback1 = new TestUrlRequestCallback();
720         TestUrlRequestCallback callback2 = new TestUrlRequestCallback();
721         callback1.setAutoAdvance(false);
722         callback2.setAutoAdvance(false);
723         assertEquals(0, cronetEngine.getActiveRequestCount());
724         UrlRequest request1 =
725                 cronetEngine.newUrlRequestBuilder(badUrl, callback1, callback1.getExecutor())
726                         .build();
727         UrlRequest request2 =
728                 cronetEngine.newUrlRequestBuilder(mUrl, callback2, callback2.getExecutor()).build();
729         request1.start();
730         request2.start();
731         assertEquals(2, cronetEngine.getActiveRequestCount());
732         callback1.blockForDone();
733         assertTrue(callback1.mOnErrorCalled);
734         assertEquals(ResponseStep.ON_FAILED, callback1.mResponseStep);
735         assertEquals(1, cronetEngine.getActiveRequestCount());
736         callback2.waitForNextStep();
737         callback2.setAutoAdvance(true);
738         callback2.startNextRead(request2);
739         callback2.blockForDone();
740         assertEquals(0, cronetEngine.getActiveRequestCount());
741     }
742 
743     @Test
744     @SmallTest
745     @OnlyRunNativeCronet
746     // Tests that NetLog contains events emitted by all live CronetEngines.
testNetLogContainEventsFromAllLiveEngines()747     public void testNetLogContainEventsFromAllLiveEngines() throws Exception {
748         Context context = getContext();
749         File directory = new File(PathUtils.getDataDirectory());
750         File file1 = File.createTempFile("cronet1", "json", directory);
751         File file2 = File.createTempFile("cronet2", "json", directory);
752         HttpEngine cronetEngine1 = new HttpEngine.Builder(context).build();
753         HttpEngine cronetEngine2 = new HttpEngine.Builder(context).build();
754 
755         cronetEngine1.startNetLogToFile(file1.getPath(), false);
756         cronetEngine2.startNetLogToFile(file2.getPath(), false);
757 
758         // Warm CronetEngine and make sure both CronetUrlRequestContexts are
759         // initialized before testing the logs.
760         makeRequestAndCheckStatus(cronetEngine1, mUrl, 200);
761         makeRequestAndCheckStatus(cronetEngine2, mUrl, 200);
762 
763         // Use cronetEngine1 to make a request to mUrl404.
764         makeRequestAndCheckStatus(cronetEngine1, mUrl404, 404);
765 
766         // Use cronetEngine2 to make a request to mUrl500.
767         makeRequestAndCheckStatus(cronetEngine2, mUrl500, 500);
768 
769         cronetEngine1.stopNetLog();
770         cronetEngine2.stopNetLog();
771         assertTrue(file1.exists());
772         assertTrue(file2.exists());
773         // Make sure both files contain the two requests made separately using
774         // different engines.
775         assertTrue(containsStringInNetLog(file1, mUrl404));
776         assertTrue(containsStringInNetLog(file1, mUrl500));
777         assertTrue(containsStringInNetLog(file2, mUrl404));
778         assertTrue(containsStringInNetLog(file2, mUrl500));
779         assertTrue(file1.delete());
780         assertTrue(file2.delete());
781     }
782 
783     @Test
784     @SmallTest
785     @OnlyRunNativeCronet
786     // Tests that NetLog contains events emitted by all live CronetEngines.
testBoundedFileNetLogContainEventsFromAllLiveEngines()787     public void testBoundedFileNetLogContainEventsFromAllLiveEngines() throws Exception {
788         Context context = getContext();
789         File directory = new File(PathUtils.getDataDirectory());
790         File netLogDir1 = new File(directory, "NetLog1");
791         assertFalse(netLogDir1.exists());
792         assertTrue(netLogDir1.mkdir());
793         File netLogDir2 = new File(directory, "NetLog2");
794         assertFalse(netLogDir2.exists());
795         assertTrue(netLogDir2.mkdir());
796         File logFile1 = new File(netLogDir1, "netlog.json");
797         File logFile2 = new File(netLogDir2, "netlog.json");
798 
799         ExperimentalHttpEngine cronetEngine1 =
800                 new ExperimentalHttpEngine.Builder(context).build();
801         ExperimentalHttpEngine cronetEngine2 =
802                 new ExperimentalHttpEngine.Builder(context).build();
803 
804         cronetEngine1.startNetLogToDisk(netLogDir1.getPath(), false, MAX_FILE_SIZE);
805         cronetEngine2.startNetLogToDisk(netLogDir2.getPath(), false, MAX_FILE_SIZE);
806 
807         // Warm CronetEngine and make sure both CronetUrlRequestContexts are
808         // initialized before testing the logs.
809         makeRequestAndCheckStatus(cronetEngine1, mUrl, 200);
810         makeRequestAndCheckStatus(cronetEngine2, mUrl, 200);
811 
812         // Use cronetEngine1 to make a request to mUrl404.
813         makeRequestAndCheckStatus(cronetEngine1, mUrl404, 404);
814 
815         // Use cronetEngine2 to make a request to mUrl500.
816         makeRequestAndCheckStatus(cronetEngine2, mUrl500, 500);
817 
818         cronetEngine1.stopNetLog();
819         cronetEngine2.stopNetLog();
820 
821         assertTrue(logFile1.exists());
822         assertTrue(logFile2.exists());
823         assertTrue(logFile1.length() != 0);
824         assertTrue(logFile2.length() != 0);
825 
826         // Make sure both files contain the two requests made separately using
827         // different engines.
828         assertTrue(containsStringInNetLog(logFile1, mUrl404));
829         assertTrue(containsStringInNetLog(logFile1, mUrl500));
830         assertTrue(containsStringInNetLog(logFile2, mUrl404));
831         assertTrue(containsStringInNetLog(logFile2, mUrl500));
832 
833         FileUtils.recursivelyDeleteFile(netLogDir1, FileUtils.DELETE_ALL);
834         assertFalse(netLogDir1.exists());
835         FileUtils.recursivelyDeleteFile(netLogDir2, FileUtils.DELETE_ALL);
836         assertFalse(netLogDir2.exists());
837     }
838 
createCronetEngineWithCache(int cacheType)839     private HttpEngine createCronetEngineWithCache(int cacheType) {
840         HttpEngine.Builder builder = new HttpEngine.Builder(getContext());
841         if (cacheType == HttpEngine.Builder.HTTP_CACHE_DISK
842                 || cacheType == HttpEngine.Builder.HTTP_CACHE_DISK_NO_HTTP) {
843             builder.setStoragePath(getTestStorage(getContext()));
844         }
845         builder.setEnableHttpCache(cacheType, 100 * 1024);
846         // Don't check the return value here, because startNativeTestServer() returns false when the
847         // NativeTestServer is already running and this method needs to be called twice without
848         // shutting down the NativeTestServer in between.
849         NativeTestServer.startNativeTestServer(getContext());
850         return builder.build();
851     }
852 
853     @Test
854     @SmallTest
855     @OnlyRunNativeCronet
856     // Tests that if CronetEngine is shut down on the network thread, an appropriate exception
857     // is thrown.
testShutDownEngineOnNetworkThread()858     public void testShutDownEngineOnNetworkThread() throws Exception {
859         final HttpEngine cronetEngine =
860                 createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_DISK);
861         String url = NativeTestServer.getFileURL("/cacheable.txt");
862         // Make a request to a cacheable resource.
863         checkRequestCaching(cronetEngine, url, false);
864 
865         final AtomicReference<Throwable> thrown = new AtomicReference<>();
866         // Shut down the server.
867         NativeTestServer.shutdownNativeTestServer();
868         class CancelUrlRequestCallback extends TestUrlRequestCallback {
869             @Override
870             public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
871                 super.onResponseStarted(request, info);
872                 request.cancel();
873                 // Shut down CronetEngine immediately after request is destroyed.
874                 try {
875                     cronetEngine.shutdown();
876                 } catch (Exception e) {
877                     thrown.set(e);
878                 }
879             }
880 
881             @Override
882             public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
883                 // onSucceeded will not happen, because the request is canceled
884                 // after sending first read and the executor is single threaded.
885                 throw new RuntimeException("Unexpected");
886             }
887 
888             @Override
889             public void onFailed(UrlRequest request, UrlResponseInfo info, HttpException error) {
890                 throw new RuntimeException("Unexpected");
891             }
892         }
893         Executor directExecutor = new Executor() {
894             @Override
895             public void execute(Runnable command) {
896                 command.run();
897             }
898         };
899         CancelUrlRequestCallback callback = new CancelUrlRequestCallback();
900         callback.setAllowDirectExecutor(true);
901         UrlRequest.Builder urlRequestBuilder =
902                 cronetEngine.newUrlRequestBuilder(url, callback, directExecutor);
903         urlRequestBuilder.setDirectExecutorAllowed(true);
904         urlRequestBuilder.build().start();
905         callback.blockForDone();
906         assertTrue(thrown.get() instanceof RuntimeException);
907         cronetEngine.shutdown();
908     }
909 
910     @Test
911     @SmallTest
912     @OnlyRunNativeCronet
913     // Tests that if CronetEngine is shut down when reading from disk cache,
914     // there isn't a crash. See crbug.com/486120.
testShutDownEngineWhenReadingFromDiskCache()915     public void testShutDownEngineWhenReadingFromDiskCache() throws Exception {
916         final HttpEngine cronetEngine =
917                 createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_DISK);
918         String url = NativeTestServer.getFileURL("/cacheable.txt");
919         // Make a request to a cacheable resource.
920         checkRequestCaching(cronetEngine, url, false);
921 
922         // Shut down the server.
923         NativeTestServer.shutdownNativeTestServer();
924         class CancelUrlRequestCallback extends TestUrlRequestCallback {
925             @Override
926             public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
927                 super.onResponseStarted(request, info);
928                 request.cancel();
929                 // Shut down CronetEngine immediately after request is destroyed.
930                 cronetEngine.shutdown();
931             }
932 
933             @Override
934             public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
935                 // onSucceeded will not happen, because the request is canceled
936                 // after sending first read and the executor is single threaded.
937                 throw new RuntimeException("Unexpected");
938             }
939 
940             @Override
941             public void onFailed(UrlRequest request, UrlResponseInfo info, HttpException error) {
942                 throw new RuntimeException("Unexpected");
943             }
944         }
945         CancelUrlRequestCallback callback = new CancelUrlRequestCallback();
946         UrlRequest.Builder urlRequestBuilder =
947                 cronetEngine.newUrlRequestBuilder(url, callback.getExecutor(), callback);
948         urlRequestBuilder.build().start();
949         callback.blockForDone();
950         assertEquals(200, callback.mResponseInfo.getHttpStatusCode());
951         assertTrue(callback.mResponseInfo.wasCached());
952         assertTrue(callback.mOnCanceledCalled);
953     }
954 
955     @Test
956     @SmallTest
957     @OnlyRunNativeCronet
testNetLogAfterShutdown()958     public void testNetLogAfterShutdown() throws Exception {
959         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
960         TestUrlRequestCallback callback = new TestUrlRequestCallback();
961         UrlRequest.Builder urlRequestBuilder = testFramework.mCronetEngine.newUrlRequestBuilder(
962                 mUrl, callback.getExecutor(), callback);
963         urlRequestBuilder.build().start();
964         callback.blockForDone();
965         testFramework.mCronetEngine.shutdown();
966 
967         File directory = new File(PathUtils.getDataDirectory());
968         File file = File.createTempFile("cronet", "json", directory);
969         try {
970             testFramework.mCronetEngine.startNetLogToFile(file.getPath(), false);
971             fail("Should throw an exception.");
972         } catch (Exception e) {
973             assertEquals("Engine is shut down.", e.getMessage());
974         }
975         assertFalse(hasBytesInNetLog(file));
976         assertTrue(file.delete());
977         assertTrue(!file.exists());
978     }
979 
980     @Test
981     @SmallTest
982     @OnlyRunNativeCronet
testBoundedFileNetLogAfterShutdown()983     public void testBoundedFileNetLogAfterShutdown() throws Exception {
984         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
985         TestUrlRequestCallback callback = new TestUrlRequestCallback();
986         UrlRequest.Builder urlRequestBuilder = testFramework.mCronetEngine.newUrlRequestBuilder(
987                 mUrl, callback, callback.getExecutor());
988         urlRequestBuilder.build().start();
989         callback.blockForDone();
990         testFramework.mCronetEngine.shutdown();
991 
992         File directory = new File(PathUtils.getDataDirectory());
993         File netLogDir = new File(directory, "NetLog");
994         assertFalse(netLogDir.exists());
995         assertTrue(netLogDir.mkdir());
996         File logFile = new File(netLogDir, "netlog.json");
997         try {
998             testFramework.mCronetEngine.startNetLogToDisk(
999                     netLogDir.getPath(), false, MAX_FILE_SIZE);
1000             fail("Should throw an exception.");
1001         } catch (Exception e) {
1002             assertEquals("Engine is shut down.", e.getMessage());
1003         }
1004         assertFalse(logFile.exists());
1005         FileUtils.recursivelyDeleteFile(netLogDir, FileUtils.DELETE_ALL);
1006         assertFalse(netLogDir.exists());
1007     }
1008 
1009     @Test
1010     @SmallTest
1011     @OnlyRunNativeCronet
testNetLogStartMultipleTimes()1012     public void testNetLogStartMultipleTimes() throws Exception {
1013         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
1014         File directory = new File(PathUtils.getDataDirectory());
1015         File file = File.createTempFile("cronet", "json", directory);
1016         // Start NetLog multiple times.
1017         testFramework.mCronetEngine.startNetLogToFile(file.getPath(), false);
1018         testFramework.mCronetEngine.startNetLogToFile(file.getPath(), false);
1019         testFramework.mCronetEngine.startNetLogToFile(file.getPath(), false);
1020         testFramework.mCronetEngine.startNetLogToFile(file.getPath(), false);
1021         // Start a request.
1022         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1023         UrlRequest.Builder urlRequestBuilder = testFramework.mCronetEngine.newUrlRequestBuilder(
1024                 mUrl, callback.getExecutor(), callback);
1025         urlRequestBuilder.build().start();
1026         callback.blockForDone();
1027         testFramework.mCronetEngine.stopNetLog();
1028         assertTrue(file.exists());
1029         assertTrue(file.length() != 0);
1030         assertFalse(hasBytesInNetLog(file));
1031         assertTrue(file.delete());
1032         assertTrue(!file.exists());
1033     }
1034 
1035     @Test
1036     @SmallTest
1037     @OnlyRunNativeCronet
testBoundedFileNetLogStartMultipleTimes()1038     public void testBoundedFileNetLogStartMultipleTimes() throws Exception {
1039         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
1040         File directory = new File(PathUtils.getDataDirectory());
1041         File netLogDir = new File(directory, "NetLog");
1042         assertFalse(netLogDir.exists());
1043         assertTrue(netLogDir.mkdir());
1044         File logFile = new File(netLogDir, "netlog.json");
1045         // Start NetLog multiple times. This should be equivalent to starting NetLog
1046         // once. Each subsequent start (without calling stopNetLog) should be a no-op.
1047         testFramework.mCronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
1048         testFramework.mCronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
1049         testFramework.mCronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
1050         testFramework.mCronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
1051         // Start a request.
1052         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1053         UrlRequest.Builder urlRequestBuilder = testFramework.mCronetEngine.newUrlRequestBuilder(
1054                 mUrl, callback, callback.getExecutor());
1055         urlRequestBuilder.build().start();
1056         callback.blockForDone();
1057         testFramework.mCronetEngine.stopNetLog();
1058         assertTrue(logFile.exists());
1059         assertTrue(logFile.length() != 0);
1060         assertFalse(hasBytesInNetLog(logFile));
1061         FileUtils.recursivelyDeleteFile(netLogDir, FileUtils.DELETE_ALL);
1062         assertFalse(netLogDir.exists());
1063     }
1064 
1065     @Test
1066     @SmallTest
1067     @OnlyRunNativeCronet
testNetLogStopMultipleTimes()1068     public void testNetLogStopMultipleTimes() throws Exception {
1069         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
1070         File directory = new File(PathUtils.getDataDirectory());
1071         File file = File.createTempFile("cronet", "json", directory);
1072         testFramework.mCronetEngine.startNetLogToFile(file.getPath(), false);
1073         // Start a request.
1074         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1075         UrlRequest.Builder urlRequestBuilder = testFramework.mCronetEngine.newUrlRequestBuilder(
1076                 mUrl, callback.getExecutor(), callback);
1077         urlRequestBuilder.build().start();
1078         callback.blockForDone();
1079         // Stop NetLog multiple times.
1080         testFramework.mCronetEngine.stopNetLog();
1081         testFramework.mCronetEngine.stopNetLog();
1082         testFramework.mCronetEngine.stopNetLog();
1083         testFramework.mCronetEngine.stopNetLog();
1084         testFramework.mCronetEngine.stopNetLog();
1085         assertTrue(file.exists());
1086         assertTrue(file.length() != 0);
1087         assertFalse(hasBytesInNetLog(file));
1088         assertTrue(file.delete());
1089         assertTrue(!file.exists());
1090     }
1091 
1092     @Test
1093     @SmallTest
1094     @OnlyRunNativeCronet
testBoundedFileNetLogStopMultipleTimes()1095     public void testBoundedFileNetLogStopMultipleTimes() throws Exception {
1096         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
1097         File directory = new File(PathUtils.getDataDirectory());
1098         File netLogDir = new File(directory, "NetLog");
1099         assertFalse(netLogDir.exists());
1100         assertTrue(netLogDir.mkdir());
1101         File logFile = new File(netLogDir, "netlog.json");
1102         testFramework.mCronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
1103         // Start a request.
1104         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1105         UrlRequest.Builder urlRequestBuilder = testFramework.mCronetEngine.newUrlRequestBuilder(
1106                 mUrl, callback, callback.getExecutor());
1107         urlRequestBuilder.build().start();
1108         callback.blockForDone();
1109         // Stop NetLog multiple times. This should be equivalent to stopping NetLog once.
1110         // Each subsequent stop (without calling startNetLogToDisk first) should be a no-op.
1111         testFramework.mCronetEngine.stopNetLog();
1112         testFramework.mCronetEngine.stopNetLog();
1113         testFramework.mCronetEngine.stopNetLog();
1114         testFramework.mCronetEngine.stopNetLog();
1115         testFramework.mCronetEngine.stopNetLog();
1116         assertTrue(logFile.exists());
1117         assertTrue(logFile.length() != 0);
1118         assertFalse(hasBytesInNetLog(logFile));
1119         FileUtils.recursivelyDeleteFile(netLogDir, FileUtils.DELETE_ALL);
1120         assertFalse(netLogDir.exists());
1121     }
1122 
1123     @Test
1124     @SmallTest
1125     @OnlyRunNativeCronet
testNetLogWithBytes()1126     public void testNetLogWithBytes() throws Exception {
1127         Context context = getContext();
1128         File directory = new File(PathUtils.getDataDirectory());
1129         File file = File.createTempFile("cronet", "json", directory);
1130         HttpEngine cronetEngine = new HttpEngine.Builder(context).build();
1131         // Start NetLog with logAll as true.
1132         cronetEngine.startNetLogToFile(file.getPath(), true);
1133         // Start a request.
1134         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1135         UrlRequest.Builder urlRequestBuilder =
1136                 cronetEngine.newUrlRequestBuilder(mUrl, callback.getExecutor(), callback);
1137         urlRequestBuilder.build().start();
1138         callback.blockForDone();
1139         cronetEngine.stopNetLog();
1140         assertTrue(file.exists());
1141         assertTrue(file.length() != 0);
1142         assertTrue(hasBytesInNetLog(file));
1143         assertTrue(file.delete());
1144         assertTrue(!file.exists());
1145     }
1146 
1147     @Test
1148     @SmallTest
1149     @OnlyRunNativeCronet
testBoundedFileNetLogWithBytes()1150     public void testBoundedFileNetLogWithBytes() throws Exception {
1151         Context context = getContext();
1152         File directory = new File(PathUtils.getDataDirectory());
1153         File netLogDir = new File(directory, "NetLog");
1154         assertFalse(netLogDir.exists());
1155         assertTrue(netLogDir.mkdir());
1156         File logFile = new File(netLogDir, "netlog.json");
1157         ExperimentalHttpEngine cronetEngine =
1158                 new ExperimentalHttpEngine.Builder(context).build();
1159         // Start NetLog with logAll as true.
1160         cronetEngine.startNetLogToDisk(netLogDir.getPath(), true, MAX_FILE_SIZE);
1161         // Start a request.
1162         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1163         UrlRequest.Builder urlRequestBuilder =
1164                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1165         urlRequestBuilder.build().start();
1166         callback.blockForDone();
1167         cronetEngine.stopNetLog();
1168 
1169         assertTrue(logFile.exists());
1170         assertTrue(logFile.length() != 0);
1171         assertTrue(hasBytesInNetLog(logFile));
1172         FileUtils.recursivelyDeleteFile(netLogDir, FileUtils.DELETE_ALL);
1173         assertFalse(netLogDir.exists());
1174     }
1175 
hasBytesInNetLog(File logFile)1176     private boolean hasBytesInNetLog(File logFile) throws Exception {
1177         return containsStringInNetLog(logFile, "\"bytes\"");
1178     }
1179 
containsStringInNetLog(File logFile, String content)1180     private boolean containsStringInNetLog(File logFile, String content) throws Exception {
1181         BufferedReader logReader = new BufferedReader(new FileReader(logFile));
1182         try {
1183             String logLine;
1184             while ((logLine = logReader.readLine()) != null) {
1185                 if (logLine.contains(content)) {
1186                     return true;
1187                 }
1188             }
1189             return false;
1190         } finally {
1191             logReader.close();
1192         }
1193     }
1194 
1195     /**
1196      * Helper method to make a request to {@code url}, wait for it to
1197      * complete, and check that the status code is the same as {@code expectedStatusCode}.
1198      */
makeRequestAndCheckStatus( HttpEngine engine, String url, int expectedStatusCode)1199     private void makeRequestAndCheckStatus(
1200             HttpEngine engine, String url, int expectedStatusCode) {
1201         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1202         UrlRequest request =
1203                 engine.newUrlRequestBuilder(url, callback.getExecutor(), callback).build();
1204         request.start();
1205         callback.blockForDone();
1206         assertEquals(expectedStatusCode, callback.mResponseInfo.getHttpStatusCode());
1207     }
1208 
checkRequestCaching(HttpEngine engine, String url, boolean expectCached)1209     private void checkRequestCaching(HttpEngine engine, String url, boolean expectCached) {
1210         checkRequestCaching(engine, url, expectCached, false);
1211     }
1212 
checkRequestCaching( HttpEngine engine, String url, boolean expectCached, boolean disableCache)1213     private void checkRequestCaching(
1214             HttpEngine engine, String url, boolean expectCached, boolean disableCache) {
1215         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1216         UrlRequest.Builder urlRequestBuilder =
1217                 engine.newUrlRequestBuilder(url, callback.getExecutor(), callback);
1218         if (disableCache) {
1219             urlRequestBuilder.setCacheDisabled(true);
1220         }
1221         urlRequestBuilder.build().start();
1222         callback.blockForDone();
1223         assertEquals(expectCached, callback.mResponseInfo.wasCached());
1224         assertEquals("this is a cacheable file\n", callback.mResponseAsString);
1225     }
1226 
1227     @Test
1228     @SmallTest
1229     @OnlyRunNativeCronet
testEnableHttpCacheDisabled()1230     public void testEnableHttpCacheDisabled() throws Exception {
1231         HttpEngine cronetEngine =
1232                 createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_DISABLED);
1233         String url = NativeTestServer.getFileURL("/cacheable.txt");
1234         checkRequestCaching(cronetEngine, url, false);
1235         checkRequestCaching(cronetEngine, url, false);
1236         checkRequestCaching(cronetEngine, url, false);
1237         cronetEngine.shutdown();
1238     }
1239 
1240     @Test
1241     @SmallTest
testEnableHttpCacheInMemory()1242     public void testEnableHttpCacheInMemory() throws Exception {
1243         HttpEngine cronetEngine =
1244                 createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_IN_MEMORY);
1245         String url = NativeTestServer.getFileURL("/cacheable.txt");
1246         checkRequestCaching(cronetEngine, url, false);
1247         checkRequestCaching(cronetEngine, url, true);
1248         NativeTestServer.shutdownNativeTestServer();
1249         checkRequestCaching(cronetEngine, url, true);
1250         cronetEngine.shutdown();
1251     }
1252 
1253     @Test
1254     @SmallTest
testEnableHttpCacheDisk()1255     public void testEnableHttpCacheDisk() throws Exception {
1256         HttpEngine cronetEngine =
1257                 createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_DISK);
1258         String url = NativeTestServer.getFileURL("/cacheable.txt");
1259         checkRequestCaching(cronetEngine, url, false);
1260         checkRequestCaching(cronetEngine, url, true);
1261         NativeTestServer.shutdownNativeTestServer();
1262         checkRequestCaching(cronetEngine, url, true);
1263         cronetEngine.shutdown();
1264     }
1265 
1266     @Test
1267     @SmallTest
1268     @OnlyRunNativeCronet
testNoConcurrentDiskUsage()1269     public void testNoConcurrentDiskUsage() throws Exception {
1270         HttpEngine cronetEngine =
1271                 createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_DISK);
1272         try {
1273             createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_DISK);
1274             fail();
1275         } catch (IllegalStateException e) {
1276             assertEquals("Disk cache storage path already in use", e.getMessage());
1277         }
1278         String url = NativeTestServer.getFileURL("/cacheable.txt");
1279         checkRequestCaching(cronetEngine, url, false);
1280         checkRequestCaching(cronetEngine, url, true);
1281         NativeTestServer.shutdownNativeTestServer();
1282         checkRequestCaching(cronetEngine, url, true);
1283         cronetEngine.shutdown();
1284     }
1285 
1286     @Test
1287     @SmallTest
1288     @OnlyRunNativeCronet
testEnableHttpCacheDiskNoHttp()1289     public void testEnableHttpCacheDiskNoHttp() throws Exception {
1290         HttpEngine cronetEngine =
1291                 createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_DISK_NO_HTTP);
1292         String url = NativeTestServer.getFileURL("/cacheable.txt");
1293         checkRequestCaching(cronetEngine, url, false);
1294         checkRequestCaching(cronetEngine, url, false);
1295         checkRequestCaching(cronetEngine, url, false);
1296 
1297         // Make a new CronetEngine and try again to make sure the response didn't get cached on the
1298         // first request. See https://crbug.com/743232.
1299         cronetEngine.shutdown();
1300         cronetEngine = createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_DISK_NO_HTTP);
1301         checkRequestCaching(cronetEngine, url, false);
1302         checkRequestCaching(cronetEngine, url, false);
1303         checkRequestCaching(cronetEngine, url, false);
1304         cronetEngine.shutdown();
1305     }
1306 
1307     @Test
1308     @SmallTest
testDisableCache()1309     public void testDisableCache() throws Exception {
1310         HttpEngine cronetEngine =
1311                 createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_DISK);
1312         String url = NativeTestServer.getFileURL("/cacheable.txt");
1313 
1314         // When cache is disabled, making a request does not write to the cache.
1315         checkRequestCaching(cronetEngine, url, false, true /** disable cache */);
1316         checkRequestCaching(cronetEngine, url, false);
1317 
1318         // When cache is enabled, the second request is cached.
1319         checkRequestCaching(cronetEngine, url, false, true /** disable cache */);
1320         checkRequestCaching(cronetEngine, url, true);
1321 
1322         // Shut down the server, next request should have a cached response.
1323         NativeTestServer.shutdownNativeTestServer();
1324         checkRequestCaching(cronetEngine, url, true);
1325 
1326         // Cache is disabled after server is shut down, request should fail.
1327         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1328         UrlRequest.Builder urlRequestBuilder =
1329                 cronetEngine.newUrlRequestBuilder(url, callback.getExecutor(), callback);
1330         urlRequestBuilder.setCacheDisabled(true);
1331         urlRequestBuilder.build().start();
1332         callback.blockForDone();
1333         assertNotNull(callback.mError);
1334         assertContains("Exception in CronetUrlRequest: net::ERR_CONNECTION_REFUSED",
1335                 callback.mError.getMessage());
1336         cronetEngine.shutdown();
1337     }
1338 
1339     @Test
1340     @SmallTest
testEnableHttpCacheDiskNewEngine()1341     public void testEnableHttpCacheDiskNewEngine() throws Exception {
1342         HttpEngine cronetEngine =
1343                 createCronetEngineWithCache(HttpEngine.Builder.HTTP_CACHE_DISK);
1344         String url = NativeTestServer.getFileURL("/cacheable.txt");
1345         checkRequestCaching(cronetEngine, url, false);
1346         checkRequestCaching(cronetEngine, url, true);
1347         NativeTestServer.shutdownNativeTestServer();
1348         checkRequestCaching(cronetEngine, url, true);
1349 
1350         // Shutdown original context and create another that uses the same cache.
1351         cronetEngine.shutdown();
1352         cronetEngine =
1353                 mTestRule.enableDiskCache(new HttpEngine.Builder(getContext())).build();
1354         checkRequestCaching(cronetEngine, url, true);
1355         cronetEngine.shutdown();
1356     }
1357 
1358     @Test
1359     @SmallTest
testInitEngineAndStartRequest()1360     public void testInitEngineAndStartRequest() {
1361         // Immediately make a request after initializing the engine.
1362         HttpEngine cronetEngine = new HttpEngine.Builder(getContext()).build();
1363         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1364         UrlRequest.Builder urlRequestBuilder =
1365                 cronetEngine.newUrlRequestBuilder(mUrl, callback.getExecutor(), callback);
1366         urlRequestBuilder.build().start();
1367         callback.blockForDone();
1368         assertEquals(200, callback.mResponseInfo.getHttpStatusCode());
1369         cronetEngine.shutdown();
1370     }
1371 
1372     @Test
1373     @SmallTest
testInitEngineStartTwoRequests()1374     public void testInitEngineStartTwoRequests() throws Exception {
1375         // Make two requests after initializing the context.
1376         HttpEngine cronetEngine = new HttpEngine.Builder(getContext()).build();
1377         int[] statusCodes = {0, 0};
1378         String[] urls = {mUrl, mUrl404};
1379         for (int i = 0; i < 2; i++) {
1380             TestUrlRequestCallback callback = new TestUrlRequestCallback();
1381             UrlRequest.Builder urlRequestBuilder =
1382                     cronetEngine.newUrlRequestBuilder(urls[i], callback.getExecutor(), callback);
1383             urlRequestBuilder.build().start();
1384             callback.blockForDone();
1385             statusCodes[i] = callback.mResponseInfo.getHttpStatusCode();
1386         }
1387         assertEquals(200, statusCodes[0]);
1388         assertEquals(404, statusCodes[1]);
1389         cronetEngine.shutdown();
1390     }
1391 
1392     @Test
1393     @SmallTest
testInitTwoEnginesSimultaneously()1394     public void testInitTwoEnginesSimultaneously() throws Exception {
1395         // Threads will block on runBlocker to ensure simultaneous execution.
1396         ConditionVariable runBlocker = new ConditionVariable(false);
1397         RequestThread thread1 = new RequestThread(mUrl, runBlocker);
1398         RequestThread thread2 = new RequestThread(mUrl404, runBlocker);
1399 
1400         thread1.start();
1401         thread2.start();
1402         runBlocker.open();
1403         thread1.join();
1404         thread2.join();
1405         assertEquals(200, thread1.mCallback.mResponseInfo.getHttpStatusCode());
1406         assertEquals(404, thread2.mCallback.mResponseInfo.getHttpStatusCode());
1407     }
1408 
1409     @Test
1410     @SmallTest
testInitTwoEnginesInSequence()1411     public void testInitTwoEnginesInSequence() throws Exception {
1412         ConditionVariable runBlocker = new ConditionVariable(true);
1413         RequestThread thread1 = new RequestThread(mUrl, runBlocker);
1414         RequestThread thread2 = new RequestThread(mUrl404, runBlocker);
1415 
1416         thread1.start();
1417         thread1.join();
1418         thread2.start();
1419         thread2.join();
1420         assertEquals(200, thread1.mCallback.mResponseInfo.getHttpStatusCode());
1421         assertEquals(404, thread2.mCallback.mResponseInfo.getHttpStatusCode());
1422     }
1423 
1424     @Test
1425     @SmallTest
testInitDifferentEngines()1426     public void testInitDifferentEngines() throws Exception {
1427         // Test that concurrently instantiating Cronet context's upon various
1428         // different versions of the same Android Context does not cause crashes
1429         // like crbug.com/453845
1430         HttpEngine firstEngine = new HttpEngine.Builder(getContext()).build();
1431         HttpEngine secondEngine = new HttpEngine.Builder(getContext()).build();
1432         HttpEngine thirdEngine =
1433                 new HttpEngine.Builder(new ContextWrapper(getContext())).build();
1434         firstEngine.shutdown();
1435         secondEngine.shutdown();
1436         thirdEngine.shutdown();
1437     }
1438 
1439     @Test
1440     @SmallTest
1441     @OnlyRunNativeCronet // Java engine doesn't produce metrics
testGetGlobalMetricsDeltas()1442     public void testGetGlobalMetricsDeltas() throws Exception {
1443         final CronetTestFramework testFramework = mTestRule.startCronetTestFramework();
1444 
1445         byte[] delta1 = testFramework.mCronetEngine.getGlobalMetricsDeltas();
1446 
1447         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1448         UrlRequest.Builder builder = testFramework.mCronetEngine.newUrlRequestBuilder(
1449                 mUrl, callback.getExecutor(), callback);
1450         builder.build().start();
1451         callback.blockForDone();
1452         // Fetch deltas on a different thread the second time to make sure this is permitted.
1453         // See crbug.com/719448
1454         FutureTask<byte[]> task = new FutureTask<byte[]>(new Callable<byte[]>() {
1455             @Override
1456             public byte[] call() {
1457                 return testFramework.mCronetEngine.getGlobalMetricsDeltas();
1458             }
1459         });
1460         new Thread(task).start();
1461         byte[] delta2 = task.get();
1462         assertTrue(delta2.length != 0);
1463         assertFalse(Arrays.equals(delta1, delta2));
1464     }
1465 
1466     @Test
1467     @SmallTest
testCronetEngineBuilderConfig()1468     public void testCronetEngineBuilderConfig() throws Exception {
1469         // This is to prompt load of native library.
1470         mTestRule.startCronetTestFramework();
1471         // Verify CronetEngine.Builder config is passed down accurately to native code.
1472         ExperimentalHttpEngine.Builder builder =
1473                 new ExperimentalHttpEngine.Builder(getContext());
1474         builder.setEnableHttp2(false);
1475         builder.setEnableQuic(true);
1476         builder.addQuicHint("example.com", 12, 34);
1477         builder.setEnableHttpCache(HTTP_CACHE_IN_MEMORY, 54321);
1478         builder.setUserAgent("efgh");
1479         builder.setExperimentalOptions("");
1480         builder.setStoragePath(getTestStorage(getContext()));
1481         builder.setEnablePublicKeyPinningBypassForLocalTrustAnchors(false);
1482         CronetUrlRequestContextTestJni.get().verifyUrlRequestContextConfig(
1483                 CronetUrlRequestContext.createNativeUrlRequestContextConfig(
1484                         CronetTestUtil.getCronetEngineBuilderImpl(builder)),
1485                 getTestStorage(getContext()));
1486     }
1487 
1488     @Test
1489     @SmallTest
testCronetEngineQuicOffConfig()1490     public void testCronetEngineQuicOffConfig() throws Exception {
1491         // This is to prompt load of native library.
1492         mTestRule.startCronetTestFramework();
1493         // Verify CronetEngine.Builder config is passed down accurately to native code.
1494         ExperimentalHttpEngine.Builder builder =
1495                 new ExperimentalHttpEngine.Builder(getContext());
1496         builder.setEnableHttp2(false);
1497         // QUIC is on by default. Disabling it here to make sure the built config can correctly
1498         // reflect the change.
1499         builder.setEnableQuic(false);
1500         builder.setEnableHttpCache(HTTP_CACHE_IN_MEMORY, 54321);
1501         builder.setExperimentalOptions("");
1502         builder.setUserAgent("efgh");
1503         builder.setStoragePath(getTestStorage(getContext()));
1504         builder.setEnablePublicKeyPinningBypassForLocalTrustAnchors(false);
1505         CronetUrlRequestContextTestJni.get().verifyUrlRequestContextQuicOffConfig(
1506                 CronetUrlRequestContext.createNativeUrlRequestContextConfig(
1507                         CronetTestUtil.getCronetEngineBuilderImpl(builder)),
1508                 getTestStorage(getContext()));
1509     }
1510 
1511     // Creates a CronetEngine on another thread and then one on the main thread.  This shouldn't
1512     // crash.
1513     @Test
1514     @SmallTest
testThreadedStartup()1515     public void testThreadedStartup() throws Exception {
1516         final ConditionVariable otherThreadDone = new ConditionVariable();
1517         final ConditionVariable uiThreadDone = new ConditionVariable();
1518         new Handler(Looper.getMainLooper()).post(new Runnable() {
1519             @Override
1520             public void run() {
1521                 final ExperimentalHttpEngine.Builder builder =
1522                         new ExperimentalHttpEngine.Builder(getContext());
1523                 new Thread() {
1524                     @Override
1525                     public void run() {
1526                         HttpEngine cronetEngine = builder.build();
1527                         otherThreadDone.open();
1528                         cronetEngine.shutdown();
1529                     }
1530                 }
1531                         .start();
1532                 otherThreadDone.block();
1533                 builder.build().shutdown();
1534                 uiThreadDone.open();
1535             }
1536         });
1537         assertTrue(uiThreadDone.block(1000));
1538     }
1539 
1540     @Test
1541     @SmallTest
testHostResolverRules()1542     public void testHostResolverRules() throws Exception {
1543         String resolverTestHostname = "some-weird-hostname";
1544         URL testUrl = new URL(mUrl);
1545         ExperimentalHttpEngine.Builder cronetEngineBuilder =
1546                 new ExperimentalHttpEngine.Builder(getContext());
1547         JSONObject hostResolverRules = new JSONObject().put(
1548                 "host_resolver_rules", "MAP " + resolverTestHostname + " " + testUrl.getHost());
1549         JSONObject experimentalOptions =
1550                 new JSONObject().put("HostResolverRules", hostResolverRules);
1551         cronetEngineBuilder.setExperimentalOptions(experimentalOptions.toString());
1552 
1553         final HttpEngine cronetEngine = cronetEngineBuilder.build();
1554         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1555         URL requestUrl =
1556                 new URL("http", resolverTestHostname, testUrl.getPort(), testUrl.getFile());
1557         UrlRequest.Builder urlRequestBuilder = cronetEngine.newUrlRequestBuilder(
1558                 requestUrl.toString(), callback.getExecutor(), callback);
1559         urlRequestBuilder.build().start();
1560         callback.blockForDone();
1561         assertEquals(200, callback.mResponseInfo.getHttpStatusCode());
1562     }
1563 
1564     /**
1565      * Runs {@code r} on {@code engine}'s network thread.
1566      */
postToNetworkThread(final HttpEngine engine, final Runnable r)1567     private static void postToNetworkThread(final HttpEngine engine, final Runnable r) {
1568         // Works by requesting an invalid URL which results in onFailed() being called, which is
1569         // done through a direct executor which causes onFailed to be run on the network thread.
1570         Executor directExecutor = new Executor() {
1571             @Override
1572             public void execute(Runnable runable) {
1573                 runable.run();
1574             }
1575         };
1576         UrlRequest.Callback callback = new UrlRequest.Callback() {
1577             @Override
1578             public void onRedirectReceived(
1579                     UrlRequest request, UrlResponseInfo responseInfo, String newLocationUrl) {}
1580             @Override
1581             public void onResponseStarted(UrlRequest request, UrlResponseInfo responseInfo) {}
1582             @Override
1583             public void onReadCompleted(
1584                     UrlRequest request, UrlResponseInfo responseInfo, ByteBuffer byteBuffer) {}
1585             @Override
1586             public void onSucceeded(UrlRequest request, UrlResponseInfo responseInfo) {}
1587 
1588             @Override
1589             public void onFailed(
1590                     UrlRequest request, UrlResponseInfo responseInfo, HttpException error) {
1591                 r.run();
1592             }
1593             @Override
1594             public void onCanceled(UrlRequest request, UrlResponseInfo responseInfo) {}
1595         };
1596         engine.newUrlRequestBuilder("", directExecutor, callback).build().start();
1597     }
1598 
1599     /**
1600      * @returns the thread priority of {@code engine}'s network thread.
1601      */
1602     private static class ApiHelper {
doesContextExistForNetwork(HttpEngine engine, Network network)1603         public static boolean doesContextExistForNetwork(HttpEngine engine, Network network)
1604                 throws Exception {
1605             FutureTask<Boolean> task = new FutureTask<Boolean>(new Callable<Boolean>() {
1606                 @Override
1607                 public Boolean call() {
1608                     return CronetTestUtil.doesURLRequestContextExistForTesting(engine, network);
1609                 }
1610             });
1611             postToNetworkThread(engine, task);
1612             return task.get();
1613         }
1614     }
1615 
1616     /**
1617      * @returns the thread priority of {@code engine}'s network thread.
1618      */
getThreadPriority(HttpEngine engine)1619     private int getThreadPriority(HttpEngine engine) throws Exception {
1620         FutureTask<Integer> task = new FutureTask<Integer>(new Callable<Integer>() {
1621             @Override
1622             public Integer call() {
1623                 return Process.getThreadPriority(Process.myTid());
1624             }
1625         });
1626         postToNetworkThread(engine, task);
1627         return task.get();
1628     }
1629 
1630     @Test
1631     @SmallTest
1632     @RequiresMinApi(6) // setThreadPriority added in API 6: crrev.com/472449
testCronetEngineThreadPriority()1633     public void testCronetEngineThreadPriority() throws Exception {
1634         ExperimentalHttpEngine.Builder builder =
1635                 new ExperimentalHttpEngine.Builder(getContext());
1636         // Try out of bounds thread priorities.
1637         try {
1638             builder.setThreadPriority(-21);
1639             fail();
1640         } catch (IllegalArgumentException e) {
1641             assertEquals("Thread priority invalid", e.getMessage());
1642         }
1643         try {
1644             builder.setThreadPriority(20);
1645             fail();
1646         } catch (IllegalArgumentException e) {
1647             assertEquals("Thread priority invalid", e.getMessage());
1648         }
1649         // Test that valid thread priority range (-20..19) is working.
1650         for (int threadPriority = -20; threadPriority < 20; threadPriority++) {
1651             builder.setThreadPriority(threadPriority);
1652             HttpEngine engine = builder.build();
1653             assertEquals(threadPriority, getThreadPriority(engine));
1654             engine.shutdown();
1655         }
1656     }
1657 
1658     @NativeMethods("cronet_tests")
1659     interface Natives {
1660         // Verifies that CronetEngine.Builder config from testCronetEngineBuilderConfig() is
1661         // properly translated to a native UrlRequestContextConfig.
verifyUrlRequestContextConfig(long config, String storagePath)1662         void verifyUrlRequestContextConfig(long config, String storagePath);
1663 
1664         // Verifies that CronetEngine.Builder config from testCronetEngineQuicOffConfig() is
1665         // properly translated to a native UrlRequestContextConfig and QUIC is turned off.
verifyUrlRequestContextQuicOffConfig(long config, String storagePath)1666         void verifyUrlRequestContextQuicOffConfig(long config, String storagePath);
1667     }
1668 }
1669