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.TruthJUnit.assume;
9 
10 import static org.junit.Assert.assertThrows;
11 
12 import static org.chromium.net.CronetEngine.Builder.HTTP_CACHE_IN_MEMORY;
13 import static org.chromium.net.CronetTestRule.getTestStorage;
14 import static org.chromium.net.truth.UrlResponseInfoSubject.assertThat;
15 
16 import android.net.Network;
17 import android.os.Build;
18 import android.os.Bundle;
19 import android.os.ConditionVariable;
20 import android.os.Handler;
21 import android.os.Looper;
22 import android.os.Process;
23 
24 import androidx.test.ext.junit.runners.AndroidJUnit4;
25 import androidx.test.filters.SmallTest;
26 import com.android.testutils.SkipPresubmit;
27 
28 import org.jni_zero.JNINamespace;
29 import org.jni_zero.NativeMethods;
30 import org.json.JSONObject;
31 import org.junit.After;
32 import org.junit.Before;
33 import org.junit.Rule;
34 import org.junit.Test;
35 import org.junit.runner.RunWith;
36 
37 import org.chromium.base.Log;
38 import org.chromium.base.PathUtils;
39 import org.chromium.base.test.util.DoNotBatch;
40 import org.chromium.net.CronetTestRule.CronetImplementation;
41 import org.chromium.net.CronetTestRule.DisableAutomaticNetLog;
42 import org.chromium.net.CronetTestRule.IgnoreFor;
43 import org.chromium.net.CronetTestRule.RequiresMinAndroidApi;
44 import org.chromium.net.CronetTestRule.RequiresMinApi;
45 import org.chromium.net.NetworkChangeNotifierAutoDetect.ConnectivityManagerDelegate;
46 import org.chromium.net.TestUrlRequestCallback.ResponseStep;
47 import org.chromium.net.httpflags.BaseFeature;
48 import org.chromium.net.httpflags.FlagValue;
49 import org.chromium.net.httpflags.Flags;
50 import org.chromium.net.impl.CronetExceptionImpl;
51 import org.chromium.net.impl.CronetLibraryLoader;
52 import org.chromium.net.impl.CronetManifest;
53 import org.chromium.net.impl.CronetManifestInterceptor;
54 import org.chromium.net.impl.CronetUrlRequestContext;
55 import org.chromium.net.impl.ImplVersion;
56 import org.chromium.net.impl.NativeCronetEngineBuilderImpl;
57 import org.chromium.net.impl.NetworkExceptionImpl;
58 
59 import java.io.BufferedReader;
60 import java.io.File;
61 import java.io.FileReader;
62 import java.net.URL;
63 import java.nio.ByteBuffer;
64 import java.util.Arrays;
65 import java.util.UUID;
66 import java.util.concurrent.Callable;
67 import java.util.concurrent.Executor;
68 import java.util.concurrent.FutureTask;
69 import java.util.concurrent.atomic.AtomicReference;
70 
71 /** Test CronetEngine. */
72 @DoNotBatch(reason = "crbug/1459563")
73 @RunWith(AndroidJUnit4.class)
74 @JNINamespace("cronet")
75 public class CronetUrlRequestContextTest {
76     @Rule public final CronetTestRule mTestRule = CronetTestRule.withManualEngineStartup();
77 
78     private static final String TAG = "CronetUrlReqCtxTest";
79     // URLs used for tests.
80     private static final String MOCK_CRONET_TEST_FAILED_URL = "http://mock.failed.request/-2";
81     private static final String MOCK_CRONET_TEST_SUCCESS_URL = "http://mock.http/success.txt";
82     private static final int MAX_FILE_SIZE = 1000000000;
83 
84     private String mUrl;
85     private String mUrl404;
86     private String mUrl500;
87 
88     @Before
setUp()89     public void setUp() throws Exception {
90         NativeTestServer.startNativeTestServer(mTestRule.getTestFramework().getContext());
91         mUrl = NativeTestServer.getSuccessURL();
92         mUrl404 = NativeTestServer.getNotFoundURL();
93         mUrl500 = NativeTestServer.getServerErrorURL();
94     }
95 
96     @After
tearDown()97     public void tearDown() throws Exception {
98         NativeTestServer.shutdownNativeTestServer();
99     }
100 
101     class RequestThread extends Thread {
102         public TestUrlRequestCallback mCallback;
103 
104         final String mUrl;
105         final ConditionVariable mRunBlocker;
106 
RequestThread(String url, ConditionVariable runBlocker)107         public RequestThread(String url, ConditionVariable runBlocker) {
108             mUrl = url;
109             mRunBlocker = runBlocker;
110         }
111 
112         @Override
run()113         public void run() {
114             mRunBlocker.block();
115             ExperimentalCronetEngine cronetEngine =
116                     mTestRule
117                             .getTestFramework()
118                             .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext())
119                             .build();
120             try {
121                 mCallback = new TestUrlRequestCallback();
122                 UrlRequest.Builder urlRequestBuilder =
123                         cronetEngine.newUrlRequestBuilder(mUrl, mCallback, mCallback.getExecutor());
124                 urlRequestBuilder.build().start();
125                 mCallback.blockForDone();
126             } finally {
127                 cronetEngine.shutdown();
128             }
129         }
130     }
131 
132     /** Callback that shutdowns the request context when request has succeeded or failed. */
133     static class ShutdownTestUrlRequestCallback extends TestUrlRequestCallback {
134         private final CronetEngine mCronetEngine;
135         private final ConditionVariable mCallbackCompletionBlock = new ConditionVariable();
136 
ShutdownTestUrlRequestCallback(CronetEngine cronetEngine)137         ShutdownTestUrlRequestCallback(CronetEngine cronetEngine) {
138             mCronetEngine = cronetEngine;
139         }
140 
141         @Override
onSucceeded(UrlRequest request, UrlResponseInfo info)142         public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
143             super.onSucceeded(request, info);
144             mCronetEngine.shutdown();
145             mCallbackCompletionBlock.open();
146         }
147 
148         @Override
onFailed(UrlRequest request, UrlResponseInfo info, CronetException error)149         public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) {
150             super.onFailed(request, info, error);
151             mCronetEngine.shutdown();
152             mCallbackCompletionBlock.open();
153         }
154 
155         // Wait for request completion callback.
blockForCallbackToComplete()156         void blockForCallbackToComplete() {
157             mCallbackCompletionBlock.block();
158         }
159     }
160 
setReadHttpFlagsInManifest(boolean value)161     private void setReadHttpFlagsInManifest(boolean value) {
162         Bundle metaData = new Bundle();
163         metaData.putBoolean(CronetManifest.READ_HTTP_FLAGS_META_DATA_KEY, value);
164         mTestRule.getTestFramework().interceptContext(new CronetManifestInterceptor(metaData));
165     }
166 
setLogFlag(String marker, String appId, String minVersion)167     private void setLogFlag(String marker, String appId, String minVersion) {
168         FlagValue.ConstrainedValue.Builder constrainedValueBuilder =
169                 FlagValue.ConstrainedValue.newBuilder()
170                         .setStringValue("Test log flag value " + marker);
171         if (appId != null) {
172             constrainedValueBuilder.setAppId(appId);
173         }
174         if (minVersion != null) {
175             constrainedValueBuilder.setMinVersion(minVersion);
176         }
177         mTestRule
178                 .getTestFramework()
179                 .setHttpFlags(
180                         Flags.newBuilder()
181                                 .putFlags(
182                                         CronetLibraryLoader.LOG_FLAG_NAME,
183                                         FlagValue.newBuilder()
184                                                 .addConstrainedValues(constrainedValueBuilder)
185                                                 .build())
186                                 .build());
187     }
188 
runOneRequest()189     private void runOneRequest() {
190         TestUrlRequestCallback callback = new TestUrlRequestCallback();
191         CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
192         UrlRequest.Builder urlRequestBuilder =
193                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
194         urlRequestBuilder.build().start();
195         callback.blockForDone();
196     }
197 
runRequestWhileExpectingLog(String marker, boolean shouldBeLogged)198     private void runRequestWhileExpectingLog(String marker, boolean shouldBeLogged)
199             throws Exception {
200         try (LogcatCapture logcatSink =
201                 new LogcatCapture(
202                         Arrays.asList(
203                                 Log.normalizeTag(CronetLibraryLoader.TAG + ":I"),
204                                 Log.normalizeTag(TAG + ":I"),
205                                 "chromium:I"))) {
206             // Use the engine at least once to ensure we do not race against Cronet initialization.
207             runOneRequest();
208 
209             String stopMarker = UUID.randomUUID().toString();
210             Log.i(TAG, "%s --- ENGINE STARTED ---", stopMarker);
211 
212             if (shouldBeLogged) {
213                 while (true) {
214                     String line = logcatSink.readLine();
215                     assertThat(line).doesNotContain(stopMarker);
216                     if (line.contains(marker)) break;
217                 }
218                 while (!logcatSink.readLine().contains(stopMarker)) {}
219             } else {
220                 while (true) {
221                     String line = logcatSink.readLine();
222                     assertThat(line).doesNotContain(marker);
223                     if (line.contains(stopMarker)) break;
224                 }
225             }
226         }
227     }
228 
229     @Test
230     @SmallTest
231     @IgnoreFor(
232             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
233             reason =
234                     "HTTP flags are only supported on native Cronet for now. "
235                             + "crbug.com/1495401: Emulator image does not have HttpFlags code yet")
testHttpFlagsAreLoaded()236     public void testHttpFlagsAreLoaded() throws Exception {
237         setReadHttpFlagsInManifest(true);
238         String marker = UUID.randomUUID().toString();
239         setLogFlag(marker, /* appId= */ null, /* minVersion= */ null);
240         runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ true);
241     }
242 
243     @Test
244     @SmallTest
245     @IgnoreFor(
246             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
247             reason =
248                     "HTTP flags are only supported on native Cronet for now. "
249                             + "crbug.com/1495401: Emulator image does not have HttpFlags code yet")
testHttpFlagsAreNotLoadedIfDisabledInManifest()250     public void testHttpFlagsAreNotLoadedIfDisabledInManifest() throws Exception {
251         setReadHttpFlagsInManifest(false);
252         String marker = UUID.randomUUID().toString();
253         setLogFlag(marker, /* appId= */ null, /* minVersion= */ null);
254         runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ false);
255     }
256 
257     @Test
258     @SmallTest
259     @IgnoreFor(
260             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
261             reason =
262                     "HTTP flags are only supported on native Cronet for now. "
263                             + "crbug.com/1495401: Emulator image does not have HttpFlags code yet")
testHttpFlagsNotAppliedIfAppIdDoesntMatch()264     public void testHttpFlagsNotAppliedIfAppIdDoesntMatch() throws Exception {
265         setReadHttpFlagsInManifest(true);
266         String marker = UUID.randomUUID().toString();
267         setLogFlag(marker, /* appId= */ "org.chromium.fake.app.id", /* minVersion= */ null);
268         runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ false);
269     }
270 
271     @Test
272     @SmallTest
273     @IgnoreFor(
274             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
275             reason =
276                     "HTTP flags are only supported on native Cronet for now. "
277                             + "crbug.com/1495401: Emulator image does not have HttpFlags code yet")
testHttpFlagsAppliedIfAppIdMatches()278     public void testHttpFlagsAppliedIfAppIdMatches() throws Exception {
279         setReadHttpFlagsInManifest(true);
280         String marker = UUID.randomUUID().toString();
281         setLogFlag(
282                 marker,
283                 /* appId= */ mTestRule.getTestFramework().getContext().getPackageName(),
284                 /* minVersion= */ ImplVersion.getCronetVersion());
285         runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ true);
286     }
287 
288     @Test
289     @SmallTest
290     @IgnoreFor(
291             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
292             reason =
293                     "HTTP flags are only supported on native Cronet for now. "
294                             + "crbug.com/1495401: Emulator image does not have HttpFlags code yet")
testHttpFlagsNotAppliedIfBelowMinVersion()295     public void testHttpFlagsNotAppliedIfBelowMinVersion() throws Exception {
296         setReadHttpFlagsInManifest(true);
297         String marker = UUID.randomUUID().toString();
298         setLogFlag(marker, /* appId= */ null, /* minVersion= */ "999999.0.0.0");
299         runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ false);
300     }
301 
302     @Test
303     @SmallTest
304     @IgnoreFor(
305             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
306             reason =
307                     "HTTP flags are only supported on native Cronet for now. "
308                             + "crbug.com/1495401: Emulator image does not have HttpFlags code yet")
testHttpFlagsAppliedIfAtMinVersion()309     public void testHttpFlagsAppliedIfAtMinVersion() throws Exception {
310         setReadHttpFlagsInManifest(true);
311         String marker = UUID.randomUUID().toString();
312         setLogFlag(marker, /* appId= */ null, /* minVersion= */ ImplVersion.getCronetVersion());
313         runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ true);
314     }
315 
316     @Test
317     @SmallTest
318     @IgnoreFor(
319             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
320             reason =
321                     "HTTP flags are only supported on native Cronet for now. "
322                             + "crbug.com/1495401: Emulator image does not have HttpFlags code yet")
testHttpFlagsAppliedIfAboveMinVersion()323     public void testHttpFlagsAppliedIfAboveMinVersion() throws Exception {
324         setReadHttpFlagsInManifest(true);
325         String marker = UUID.randomUUID().toString();
326         setLogFlag(marker, /* appId= */ null, /* minVersion= */ "100.0.0.0");
327         runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ true);
328     }
329 
setChromiumBaseFeatureLogFlag(boolean enable, String marker)330     private void setChromiumBaseFeatureLogFlag(boolean enable, String marker) {
331         var flags =
332                 Flags.newBuilder()
333                         .putFlags(
334                                 BaseFeature.FLAG_PREFIX + "CronetLogMe",
335                                 FlagValue.newBuilder()
336                                         .addConstrainedValues(
337                                                 FlagValue.ConstrainedValue.newBuilder()
338                                                         .setBoolValue(enable))
339                                         .build())
340                         .putFlags(
341                                 BaseFeature.FLAG_PREFIX
342                                         + "CronetLogMe"
343                                         + BaseFeature.PARAM_DELIMITER
344                                         + "message",
345                                 FlagValue.newBuilder()
346                                         .addConstrainedValues(
347                                                 FlagValue.ConstrainedValue.newBuilder()
348                                                         .setStringValue(marker))
349                                         .build())
350                         .build();
351         mTestRule.getTestFramework().setHttpFlags(flags);
352     }
353 
354     @Test
355     @SmallTest
356     @IgnoreFor(
357             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
358             reason =
359                     "HTTP flags are only supported on native Cronet for now. "
360                             + "crbug.com/1495401: Emulator image does not have HttpFlags code yet")
testBaseFeatureFlagsOverridesEnabled()361     public void testBaseFeatureFlagsOverridesEnabled() throws Exception {
362         setReadHttpFlagsInManifest(true);
363         String marker = UUID.randomUUID().toString();
364         setChromiumBaseFeatureLogFlag(true, marker);
365         runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ true);
366     }
367 
368     @Test
369     @SmallTest
370     @IgnoreFor(
371             implementations = {CronetImplementation.FALLBACK},
372             reason = "HTTP flags are only supported on native Cronet for now")
testBaseFeatureFlagsOverridesDisabled()373     public void testBaseFeatureFlagsOverridesDisabled() throws Exception {
374         setReadHttpFlagsInManifest(true);
375         String marker = UUID.randomUUID().toString();
376         setChromiumBaseFeatureLogFlag(false, marker);
377         runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ false);
378     }
379 
380     @Test
381     @SmallTest
382     @SuppressWarnings("deprecation")
testConfigUserAgent()383     public void testConfigUserAgent() throws Exception {
384         String userAgentName = "User-Agent";
385         String userAgentValue = "User-Agent-Value";
386 
387         mTestRule
388                 .getTestFramework()
389                 .applyEngineBuilderPatch((builder) -> builder.setUserAgent(userAgentValue));
390 
391         CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
392         NativeTestServer.shutdownNativeTestServer(); // startNativeTestServer returns false if it's
393         // already running
394         assertThat(
395                         NativeTestServer.startNativeTestServer(
396                                 mTestRule.getTestFramework().getContext()))
397                 .isTrue();
398         TestUrlRequestCallback callback = new TestUrlRequestCallback();
399         UrlRequest.Builder urlRequestBuilder =
400                 cronetEngine.newUrlRequestBuilder(
401                         NativeTestServer.getEchoHeaderURL(userAgentName),
402                         callback,
403                         callback.getExecutor());
404         urlRequestBuilder.build().start();
405         callback.blockForDone();
406         assertThat(callback.mResponseAsString).isEqualTo(userAgentValue);
407     }
408 
409     @Test
410     @SmallTest
411     @IgnoreFor(
412             implementations = {CronetImplementation.FALLBACK},
413             reason = "Fallback implementation does not check for outstanding requests")
testShutdown()414     public void testShutdown() throws Exception {
415         CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
416         ShutdownTestUrlRequestCallback callback = new ShutdownTestUrlRequestCallback(cronetEngine);
417         // Block callback when response starts to verify that shutdown fails
418         // if there are active requests.
419         callback.setAutoAdvance(false);
420         UrlRequest.Builder urlRequestBuilder =
421                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
422         UrlRequest urlRequest = urlRequestBuilder.build();
423         urlRequest.start();
424 
425         Exception e = assertThrows(Exception.class, cronetEngine::shutdown);
426         assertThat(e).hasMessageThat().matches("Cannot shutdown with (running|active) requests.");
427 
428         callback.waitForNextStep();
429         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED);
430 
431         e = assertThrows(Exception.class, cronetEngine::shutdown);
432         assertThat(e).hasMessageThat().matches("Cannot shutdown with (running|active) requests.");
433 
434         callback.startNextRead(urlRequest);
435 
436         callback.waitForNextStep();
437 
438         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_READ_COMPLETED);
439         e = assertThrows(Exception.class, cronetEngine::shutdown);
440         assertThat(e).hasMessageThat().matches("Cannot shutdown with (running|active) requests.");
441 
442         // May not have read all the data, in theory. Just enable auto-advance
443         // and finish the request.
444         callback.setAutoAdvance(true);
445         callback.startNextRead(urlRequest);
446         callback.blockForDone();
447         callback.blockForCallbackToComplete();
448         callback.shutdownExecutor();
449     }
450 
451     @Test
452     @SmallTest
453     @IgnoreFor(
454             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
455             reason = "Tests native implementation internals")
testShutdownDuringInit()456     public void testShutdownDuringInit() throws Exception {
457         final ConditionVariable block = new ConditionVariable(false);
458 
459         // Post a task to main thread to block until shutdown is called to test
460         // scenario when shutdown is called right after construction before
461         // context is fully initialized on the main thread.
462         Runnable blockingTask =
463                 new Runnable() {
464                     @Override
465                     public void run() {
466                         block.block();
467                     }
468                 };
469         // Ensure that test is not running on the main thread.
470         assertThat(Looper.getMainLooper()).isNotEqualTo(Looper.myLooper());
471         new Handler(Looper.getMainLooper()).post(blockingTask);
472 
473         // Create new request context, but its initialization on the main thread
474         // will be stuck behind blockingTask.
475         CronetUrlRequestContext cronetEngine =
476                 (CronetUrlRequestContext)
477                         mTestRule
478                                 .getTestFramework()
479                                 .createNewSecondaryBuilder(
480                                         mTestRule.getTestFramework().getContext())
481                                 .build();
482         // Unblock the main thread, so context gets initialized and shutdown on
483         // it.
484         block.open();
485         // Shutdown will wait for init to complete on main thread.
486         cronetEngine.shutdown();
487         // Verify that context is shutdown.
488         Exception e = assertThrows(Exception.class, cronetEngine::getUrlRequestContextAdapter);
489         assertThat(e).hasMessageThat().isEqualTo("Engine is shut down.");
490     }
491 
492     @Test
493     @SmallTest
494     @IgnoreFor(
495             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
496             reason = "Tests native implementation internals")
testInitAndShutdownOnMainThread()497     public void testInitAndShutdownOnMainThread() throws Exception {
498         final ConditionVariable block = new ConditionVariable(false);
499 
500         // Post a task to main thread to init and shutdown on the main thread.
501         Runnable blockingTask =
502                 () -> {
503                     // Create new request context, loading the library.
504                     final CronetUrlRequestContext cronetEngine =
505                             (CronetUrlRequestContext)
506                                     mTestRule
507                                             .getTestFramework()
508                                             .createNewSecondaryBuilder(
509                                                     mTestRule.getTestFramework().getContext())
510                                             .build();
511                     // Shutdown right after init.
512                     cronetEngine.shutdown();
513                     // Verify that context is shutdown.
514                     Exception e =
515                             assertThrows(
516                                     Exception.class, cronetEngine::getUrlRequestContextAdapter);
517                     assertThat(e).hasMessageThat().isEqualTo("Engine is shut down.");
518                     block.open();
519                 };
520         new Handler(Looper.getMainLooper()).post(blockingTask);
521         // Wait for shutdown to complete on main thread.
522         block.block();
523     }
524 
525     @Test
526     @SmallTest
527     @IgnoreFor(
528             implementations = {CronetImplementation.FALLBACK},
529             reason = "JavaCronetEngine doesn't support throwing on repeat shutdown()")
testMultipleShutdown()530     public void testMultipleShutdown() throws Exception {
531         CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
532         cronetEngine.shutdown();
533         Exception e = assertThrows(Exception.class, cronetEngine::shutdown);
534         assertThat(e).hasMessageThat().isEqualTo("Engine is shut down.");
535     }
536 
537     @Test
538     @SmallTest
testShutdownAfterError()539     public void testShutdownAfterError() throws Exception {
540         CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
541         ShutdownTestUrlRequestCallback callback = new ShutdownTestUrlRequestCallback(cronetEngine);
542         UrlRequest.Builder urlRequestBuilder =
543                 cronetEngine.newUrlRequestBuilder(
544                         MOCK_CRONET_TEST_FAILED_URL, callback, callback.getExecutor());
545         urlRequestBuilder.build().start();
546         callback.blockForDone();
547         assertThat(callback.mOnErrorCalled).isTrue();
548         callback.blockForCallbackToComplete();
549         callback.shutdownExecutor();
550     }
551 
552     @Test
553     @SmallTest
554     @IgnoreFor(
555             implementations = {CronetImplementation.FALLBACK},
556             reason = "JavaCronetEngine doesn't support throwing on shutdown()")
testShutdownAfterCancel()557     public void testShutdownAfterCancel() throws Exception {
558         CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
559         TestUrlRequestCallback callback = new TestUrlRequestCallback();
560         // Block callback when response starts to verify that shutdown fails
561         // if there are active requests.
562         callback.setAutoAdvance(false);
563         UrlRequest.Builder urlRequestBuilder =
564                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
565         UrlRequest urlRequest = urlRequestBuilder.build();
566         urlRequest.start();
567 
568         Exception e = assertThrows(Exception.class, cronetEngine::shutdown);
569         assertThat(e).hasMessageThat().matches("Cannot shutdown with (running|active) requests.");
570 
571         callback.waitForNextStep();
572         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED);
573         urlRequest.cancel();
574     }
575 
576     @Test
577     @SmallTest
578     @IgnoreFor(
579             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
580             reason = "Tests native implementation internals")
581     @RequiresMinAndroidApi(Build.VERSION_CODES.M) // Multi-network API is supported from Marshmallow
testNetworkBoundContextLifetime()582     public void testNetworkBoundContextLifetime() throws Exception {
583         // Multi-network API is available starting from Android Lollipop.
584         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
585         ConnectivityManagerDelegate delegate =
586                 new ConnectivityManagerDelegate(mTestRule.getTestFramework().getContext());
587         Network defaultNetwork = delegate.getDefaultNetwork();
588         assume().that(defaultNetwork).isNotNull();
589 
590         TestUrlRequestCallback callback = new TestUrlRequestCallback();
591         // Allows to check the underlying network-bound context state while the request is in
592         // progress.
593         callback.setAutoAdvance(false);
594 
595         ExperimentalUrlRequest.Builder urlRequestBuilder =
596                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
597         urlRequestBuilder.bindToNetwork(defaultNetwork.getNetworkHandle());
598         UrlRequest urlRequest = urlRequestBuilder.build();
599         assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isFalse();
600         urlRequest.start();
601         assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isTrue();
602 
603         // Resume callback execution.
604         callback.waitForNextStep();
605         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED);
606         callback.setAutoAdvance(true);
607         callback.startNextRead(urlRequest);
608         callback.blockForDone();
609         assertThat(callback.mError).isNull();
610 
611         // The default network should still be active, hence the underlying network-bound context
612         // should still be there.
613         assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isTrue();
614 
615         // Fake disconnect event for the default network, this should destroy the underlying
616         // network-bound context.
617         FutureTask<Void> task =
618                 new FutureTask<Void>(
619                         new Callable<Void>() {
620                             @Override
621                             public Void call() {
622                                 NetworkChangeNotifier.fakeNetworkDisconnected(
623                                         defaultNetwork.getNetworkHandle());
624                                 return null;
625                             }
626                         });
627         CronetLibraryLoader.postToInitThread(task);
628         task.get();
629         assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isFalse();
630     }
631 
632     @Test
633     @SmallTest
634     @IgnoreFor(
635             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
636             reason = "Tests native implementation internals")
637     @RequiresMinAndroidApi(Build.VERSION_CODES.M) // Multi-network API is supported from Marshmallow
testNetworkBoundRequestCancel()638     public void testNetworkBoundRequestCancel() throws Exception {
639         // Upon a network disconnection, NCN posts a tasks onto the network thread that calls
640         // CronetContext::NetworkTasks::OnNetworkDisconnected.
641         // Calling urlRequest.cancel() also, after some hoops, ends up in a posted tasks onto the
642         // network thread that calls CronetURLRequest::NetworkTasks::Destroy.
643         // Depending on their implementation this can lead to UAF, this test is here to prevent that
644         // from being introduced in the future.
645         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
646         TestUrlRequestCallback callback = new TestUrlRequestCallback();
647         callback.setAutoAdvance(false);
648         ExperimentalUrlRequest.Builder urlRequestBuilder =
649                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
650         ConnectivityManagerDelegate delegate =
651                 new ConnectivityManagerDelegate(mTestRule.getTestFramework().getContext());
652         Network defaultNetwork = delegate.getDefaultNetwork();
653         assume().that(defaultNetwork).isNotNull();
654 
655         urlRequestBuilder.bindToNetwork(defaultNetwork.getNetworkHandle());
656         UrlRequest urlRequest = urlRequestBuilder.build();
657 
658         assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isFalse();
659         urlRequest.start();
660         assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isTrue();
661 
662         callback.waitForNextStep();
663         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED);
664         assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isTrue();
665         // Cronet registers for NCN notifications on the init thread (see
666         // CronetLibraryLoader#ensureInitializedOnInitThread), hence we need to trigger fake
667         // notifications from there.
668         CronetLibraryLoader.postToInitThread(
669                 new Runnable() {
670                     @Override
671                     public void run() {
672                         NetworkChangeNotifier.fakeNetworkDisconnected(
673                                 defaultNetwork.getNetworkHandle());
674                         // Queue cancel after disconnect event.
675                         urlRequest.cancel();
676                     }
677                 });
678         // Wait until the cancel call propagates (this would block undefinitely without that since
679         // we previously set auto advance to false).
680         callback.blockForDone();
681         // mError should be null due to urlRequest.cancel().
682         assertThat(callback.mError).isNull();
683         // urlRequest.cancel(); should destroy the underlying network bound context.
684         assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isFalse();
685     }
686 
687     @Test
688     @RequiresMinAndroidApi(Build.VERSION_CODES.M)
testBindToInvalidNetworkFails()689     public void testBindToInvalidNetworkFails() {
690         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
691         TestUrlRequestCallback callback = new TestUrlRequestCallback();
692         if (mTestRule.implementationUnderTest() == CronetImplementation.AOSP_PLATFORM) {
693             // HttpEngine#bindToNetwork requires an android.net.Network object. So, in this case, it
694             // will be the wrapper layer that will fail to translate that to a Network, not
695             // something in net's code. Hence, the failure will manifest itself at bind time, not at
696             // request execution time.
697             // Note: this will never happen in prod, as translation failure can only happen if we're
698             // given a fake networkHandle.
699             assertThrows(
700                     IllegalArgumentException.class,
701                     () -> cronetEngine.bindToNetwork(-150 /* invalid network handle */));
702             return;
703         }
704 
705         cronetEngine.bindToNetwork(-150 /* invalid network handle */);
706         ExperimentalUrlRequest.Builder builder =
707                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
708         builder.build().start();
709         callback.blockForDone();
710 
711         assertThat(callback.mError).isNotNull();
712         if (mTestRule.implementationUnderTest() == CronetImplementation.FALLBACK) {
713             assertThat(callback.mError).isInstanceOf(CronetExceptionImpl.class);
714             assertThat(callback.mError).hasCauseThat().isInstanceOf(NetworkExceptionImpl.class);
715         } else {
716             assertThat(callback.mError).isInstanceOf(NetworkExceptionImpl.class);
717         }
718     }
719 
720     @Test
721     @RequiresMinAndroidApi(Build.VERSION_CODES.M)
testBindToDefaultNetworkSucceeds()722     public void testBindToDefaultNetworkSucceeds() {
723         ConnectivityManagerDelegate delegate =
724                 new ConnectivityManagerDelegate(mTestRule.getTestFramework().getContext());
725         Network defaultNetwork = delegate.getDefaultNetwork();
726         assume().that(defaultNetwork).isNotNull();
727 
728         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
729         cronetEngine.bindToNetwork(defaultNetwork.getNetworkHandle());
730         TestUrlRequestCallback callback = new TestUrlRequestCallback();
731         ExperimentalUrlRequest.Builder builder =
732                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
733         builder.build().start();
734         callback.blockForDone();
735 
736         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
737     }
738 
739     @Test
740     @SmallTest
741     @IgnoreFor(
742             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
743             reason = "NetLog is supported only by the native implementation")
744     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
testNetLog()745     public void testNetLog() throws Exception {
746         File directory = new File(PathUtils.getDataDirectory());
747         File file = File.createTempFile("cronet", "json", directory);
748         CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
749         // Start NetLog immediately after the request context is created to make
750         // sure that the call won't crash the app even when the native request
751         // context is not fully initialized. See crbug.com/470196.
752         cronetEngine.startNetLogToFile(file.getPath(), false);
753 
754         // Start a request.
755         TestUrlRequestCallback callback = new TestUrlRequestCallback();
756         UrlRequest.Builder urlRequestBuilder =
757                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
758         urlRequestBuilder.build().start();
759         callback.blockForDone();
760         cronetEngine.stopNetLog();
761         assertThat(file.exists()).isTrue();
762         assertThat(file.length()).isNotEqualTo(0);
763         assertThat(hasBytesInNetLog(file)).isFalse();
764         assertThat(file.delete()).isTrue();
765         assertThat(file.exists()).isFalse();
766     }
767 
768     @Test
769     @SmallTest
770     @IgnoreFor(
771             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
772             reason = "NetLog is supported only by the native implementation")
773     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
testBoundedFileNetLog()774     public void testBoundedFileNetLog() throws Exception {
775         File directory = new File(PathUtils.getDataDirectory());
776         File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis());
777         assertThat(netLogDir.exists()).isFalse();
778         assertThat(netLogDir.mkdir()).isTrue();
779         File logFile = new File(netLogDir, "netlog.json");
780         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
781         // Start NetLog immediately after the request context is created to make
782         // sure that the call won't crash the app even when the native request
783         // context is not fully initialized. See crbug.com/470196.
784         cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
785 
786         // Start a request.
787         TestUrlRequestCallback callback = new TestUrlRequestCallback();
788         UrlRequest.Builder urlRequestBuilder =
789                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
790         urlRequestBuilder.build().start();
791         callback.blockForDone();
792         cronetEngine.stopNetLog();
793         assertThat(logFile.exists()).isTrue();
794         assertThat(logFile.length()).isNotEqualTo(0);
795         assertThat(hasBytesInNetLog(logFile)).isFalse();
796         FileUtils.recursivelyDeleteFile(netLogDir);
797         assertThat(netLogDir.exists()).isFalse();
798     }
799 
800     @Test
801     @SmallTest
802     @IgnoreFor(
803             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
804             reason = "NetLog is supported only by the native implementation")
805     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
806     // Tests that if stopNetLog is not explicitly called, CronetEngine.shutdown()
807     // will take care of it. crbug.com/623701.
testNoStopNetLog()808     public void testNoStopNetLog() throws Exception {
809         File directory = new File(PathUtils.getDataDirectory());
810         File file = File.createTempFile("cronet", "json", directory);
811         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
812         cronetEngine.startNetLogToFile(file.getPath(), false);
813 
814         // Start a request.
815         TestUrlRequestCallback callback = new TestUrlRequestCallback();
816         UrlRequest.Builder urlRequestBuilder =
817                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
818         urlRequestBuilder.build().start();
819         callback.blockForDone();
820         // Shut down the engine without calling stopNetLog.
821         cronetEngine.shutdown();
822         assertThat(file.exists()).isTrue();
823         assertThat(file.length()).isNotEqualTo(0);
824         assertThat(hasBytesInNetLog(file)).isFalse();
825         assertThat(file.delete()).isTrue();
826         assertThat(file.exists()).isFalse();
827     }
828 
829     @Test
830     @SmallTest
831     @IgnoreFor(
832             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
833             reason = "NetLog is supported only by the native implementation")
834     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
835     // Tests that if stopNetLog is not explicitly called, CronetEngine.shutdown()
836     // will take care of it. crbug.com/623701.
testNoStopBoundedFileNetLog()837     public void testNoStopBoundedFileNetLog() throws Exception {
838         File directory = new File(PathUtils.getDataDirectory());
839         File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis());
840         assertThat(netLogDir.exists()).isFalse();
841         assertThat(netLogDir.mkdir()).isTrue();
842         File logFile = new File(netLogDir, "netlog.json");
843         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
844         cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
845 
846         // Start a request.
847         TestUrlRequestCallback callback = new TestUrlRequestCallback();
848         UrlRequest.Builder urlRequestBuilder =
849                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
850         urlRequestBuilder.build().start();
851         callback.blockForDone();
852         // Shut down the engine without calling stopNetLog.
853         cronetEngine.shutdown();
854         assertThat(logFile.exists()).isTrue();
855         assertThat(logFile.length()).isNotEqualTo(0);
856 
857         FileUtils.recursivelyDeleteFile(netLogDir);
858         assertThat(netLogDir.exists()).isFalse();
859     }
860 
861     @Test
862     @SmallTest
863     @SkipPresubmit(reason = "b/293141085 flaky test")
864     @IgnoreFor(
865             implementations = {CronetImplementation.AOSP_PLATFORM},
866             reason = "ActiveRequestCount is not available in AOSP")
testGetActiveRequestCount()867     public void testGetActiveRequestCount() throws Exception {
868         CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
869         TestUrlRequestCallback callback1 = new TestUrlRequestCallback();
870         TestUrlRequestCallback callback2 = new TestUrlRequestCallback();
871         callback1.setAutoAdvance(false);
872         callback2.setAutoAdvance(false);
873         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
874         UrlRequest request1 =
875                 cronetEngine.newUrlRequestBuilder(mUrl, callback1, callback1.getExecutor()).build();
876         UrlRequest request2 =
877                 cronetEngine.newUrlRequestBuilder(mUrl, callback2, callback2.getExecutor()).build();
878         request1.start();
879         request2.start();
880         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(2);
881         callback1.waitForNextStep();
882         callback1.setAutoAdvance(true);
883         callback1.startNextRead(request1);
884         callback1.blockForDone();
885         waitForActiveRequestCount(cronetEngine, 1);
886         callback2.waitForNextStep();
887         callback2.setAutoAdvance(true);
888         callback2.startNextRead(request2);
889         callback2.blockForDone();
890         waitForActiveRequestCount(cronetEngine, 0);
891     }
892 
893     @Test
894     @SmallTest
895     @IgnoreFor(
896             implementations = {CronetImplementation.AOSP_PLATFORM},
897             reason = "ActiveRequestCount is not available in AOSP")
testGetActiveRequestCountOnReachingSucceeded()898     public void testGetActiveRequestCountOnReachingSucceeded() throws Exception {
899         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
900         TestUrlRequestCallback callback = new TestUrlRequestCallback();
901         callback.setAutoAdvance(false);
902         callback.setBlockOnTerminalState(true);
903         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
904         UrlRequest request =
905                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build();
906         request.start();
907         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
908         callback.waitForNextStep();
909         callback.startNextRead(request);
910         callback.waitForNextStep();
911         callback.startNextRead(request);
912         callback.blockForDone();
913         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
914         callback.setBlockOnTerminalState(false);
915         waitForActiveRequestCount(cronetEngine, 0);
916     }
917 
918     @Test
919     @SmallTest
920     @IgnoreFor(
921             implementations = {CronetImplementation.AOSP_PLATFORM},
922             reason = "ActiveRequestCount is not available in AOSP")
testGetActiveRequestCountOnReachingCancel()923     public void testGetActiveRequestCountOnReachingCancel() throws Exception {
924         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
925         TestUrlRequestCallback callback = new TestUrlRequestCallback();
926         callback.setAutoAdvance(false);
927         callback.setBlockOnTerminalState(true);
928         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
929         UrlRequest request =
930                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build();
931         request.start();
932         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
933         request.cancel();
934         callback.blockForDone();
935         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
936         callback.setBlockOnTerminalState(false);
937         assertThat(callback.mOnCanceledCalled).isTrue();
938         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_CANCELED);
939         waitForActiveRequestCount(cronetEngine, 0);
940     }
941 
942     @Test
943     @SmallTest
944     @IgnoreFor(
945             implementations = {CronetImplementation.AOSP_PLATFORM},
946             reason = "ActiveRequestCount is not available in AOSP")
testGetActiveRequestCountOnReachingFail()947     public void testGetActiveRequestCountOnReachingFail() throws Exception {
948         final String badUrl = "www.unreachable-url.com";
949         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
950         TestUrlRequestCallback callback = new TestUrlRequestCallback();
951         callback.setAutoAdvance(false);
952         callback.setBlockOnTerminalState(true);
953         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
954         UrlRequest request =
955                 cronetEngine.newUrlRequestBuilder(badUrl, callback, callback.getExecutor()).build();
956         request.start();
957         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
958         callback.blockForDone();
959         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
960         callback.setBlockOnTerminalState(false);
961         assertThat(callback.mOnErrorCalled).isTrue();
962         assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_FAILED);
963         waitForActiveRequestCount(cronetEngine, 0);
964     }
965 
966     @Test
967     @SmallTest
968     @IgnoreFor(
969             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
970             reason =
971                     "crbug.com/1494901: Broken for JavaCronetEngine. "
972                             + "ActiveRequestCount is not available in AOSP")
testGetActiveRequestCountOnDoubleStart()973     public void testGetActiveRequestCountOnDoubleStart() throws Exception {
974         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
975         TestUrlRequestCallback callback = new TestUrlRequestCallback();
976         callback.setAutoAdvance(false);
977         UrlRequest request =
978                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build();
979         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
980         request.start();
981         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
982         assertThrows(Exception.class, request::start);
983         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
984         callback.setAutoAdvance(true);
985         callback.blockForDone();
986         waitForActiveRequestCount(cronetEngine, 0);
987     }
988 
989     @Test
990     @SmallTest
991     @IgnoreFor(
992             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
993             reason =
994                     "JavaCronetEngine currently never throws directly from start. "
995                             + "ActiveRequestCount is not available in AOSP")
testGetActiveRequestCountOnInvalidRequest()996     public void testGetActiveRequestCountOnInvalidRequest() throws Exception {
997         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
998         TestUrlRequestCallback callback = new TestUrlRequestCallback();
999         UrlRequest request =
1000                 cronetEngine
1001                         .newUrlRequestBuilder("", callback, callback.getExecutor())
1002                         .setHttpMethod("")
1003                         .build();
1004         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
1005         assertThrows(Exception.class, request::start);
1006         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
1007     }
1008 
1009     @Test
1010     @SmallTest
1011     @IgnoreFor(
1012             implementations = {CronetImplementation.AOSP_PLATFORM},
1013             reason = "ActiveRequestCount is not available in AOSP")
testGetActiveRequestCountWithCancel()1014     public void testGetActiveRequestCountWithCancel() throws Exception {
1015         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1016         TestUrlRequestCallback callback1 = new TestUrlRequestCallback();
1017         TestUrlRequestCallback callback2 = new TestUrlRequestCallback();
1018         callback1.setAutoAdvance(false);
1019         callback2.setAutoAdvance(false);
1020         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
1021         UrlRequest request1 =
1022                 cronetEngine.newUrlRequestBuilder(mUrl, callback1, callback1.getExecutor()).build();
1023         UrlRequest request2 =
1024                 cronetEngine.newUrlRequestBuilder(mUrl, callback2, callback2.getExecutor()).build();
1025         request1.start();
1026         request2.start();
1027         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(2);
1028         request1.cancel();
1029         callback1.blockForDone();
1030         assertThat(callback1.mOnCanceledCalled).isTrue();
1031         assertThat(callback1.mResponseStep).isEqualTo(ResponseStep.ON_CANCELED);
1032         waitForActiveRequestCount(cronetEngine, 1);
1033         callback2.waitForNextStep();
1034         callback2.setAutoAdvance(true);
1035         callback2.startNextRead(request2);
1036         callback2.blockForDone();
1037         waitForActiveRequestCount(cronetEngine, 0);
1038     }
1039 
1040     @Test
1041     @SmallTest
1042     @SkipPresubmit(reason = "b/293141085 flaky test")
1043     @IgnoreFor(
1044         implementations = {CronetImplementation.AOSP_PLATFORM},
1045         reason = "ActiveRequestCount is not available in AOSP")
testGetActiveRequestCountWithError()1046     public void testGetActiveRequestCountWithError() throws Exception {
1047         final String badUrl = "www.unreachable-url.com";
1048         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1049         TestUrlRequestCallback callback1 = new TestUrlRequestCallback();
1050         TestUrlRequestCallback callback2 = new TestUrlRequestCallback();
1051         callback1.setAutoAdvance(false);
1052         callback1.setBlockOnTerminalState(true);
1053         callback2.setAutoAdvance(false);
1054         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
1055         UrlRequest request1 =
1056                 cronetEngine
1057                         .newUrlRequestBuilder(badUrl, callback1, callback1.getExecutor())
1058                         .build();
1059         UrlRequest request2 =
1060                 cronetEngine.newUrlRequestBuilder(mUrl, callback2, callback2.getExecutor()).build();
1061         request1.start();
1062         request2.start();
1063         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(2);
1064         callback1.setBlockOnTerminalState(false);
1065         callback1.blockForDone();
1066         assertThat(callback1.mOnErrorCalled).isTrue();
1067         assertThat(callback1.mResponseStep).isEqualTo(ResponseStep.ON_FAILED);
1068         waitForActiveRequestCount(cronetEngine, 1);
1069         callback2.waitForNextStep();
1070         callback2.setAutoAdvance(true);
1071         callback2.startNextRead(request2);
1072         callback2.blockForDone();
1073         waitForActiveRequestCount(cronetEngine, 0);
1074     }
1075 
1076     @Test
1077     @SmallTest
1078     @IgnoreFor(
1079             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1080             reason = "Request finished listeners are only supported by native Cronet")
testGetActiveRequestCountOnRequestFinishedListener()1081     public void testGetActiveRequestCountOnRequestFinishedListener() throws Exception {
1082         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1083         TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
1084         requestFinishedListener.blockListener();
1085         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1086         UrlRequest request =
1087                 cronetEngine
1088                         .newUrlRequestBuilder(mUrl, callback, callback.getExecutor())
1089                         .setRequestFinishedListener(requestFinishedListener)
1090                         .build();
1091         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
1092         request.start();
1093         callback.blockForDone();
1094         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
1095         requestFinishedListener.blockUntilDone();
1096         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
1097         requestFinishedListener.unblockListener();
1098         waitForActiveRequestCount(cronetEngine, 0);
1099     }
1100 
1101     @Test
1102     @SmallTest
1103     @IgnoreFor(
1104             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1105             reason = "Request finished listeners are only supported by native Cronet")
testGetActiveRequestCountOnThrowingRequestFinishedListener()1106     public void testGetActiveRequestCountOnThrowingRequestFinishedListener() throws Exception {
1107         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1108         TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
1109         requestFinishedListener.makeListenerThrow();
1110         requestFinishedListener.blockListener();
1111         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1112         UrlRequest request =
1113                 cronetEngine
1114                         .newUrlRequestBuilder(mUrl, callback, callback.getExecutor())
1115                         .setRequestFinishedListener(requestFinishedListener)
1116                         .build();
1117         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
1118         request.start();
1119         callback.blockForDone();
1120         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
1121         requestFinishedListener.blockUntilDone();
1122         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
1123         requestFinishedListener.unblockListener();
1124         waitForActiveRequestCount(cronetEngine, 0);
1125     }
1126 
1127     @Test
1128     @SmallTest
1129     @IgnoreFor(
1130             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1131             reason = "Request finished listeners are only supported by native Cronet")
testGetActiveRequestCountOnThrowingEngineRequestFinishedListener()1132     public void testGetActiveRequestCountOnThrowingEngineRequestFinishedListener()
1133             throws Exception {
1134         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1135         TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
1136         requestFinishedListener.makeListenerThrow();
1137         requestFinishedListener.blockListener();
1138         cronetEngine.addRequestFinishedListener(requestFinishedListener);
1139         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1140         UrlRequest request =
1141                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build();
1142         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
1143         request.start();
1144         callback.blockForDone();
1145         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
1146         requestFinishedListener.blockUntilDone();
1147         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
1148         requestFinishedListener.unblockListener();
1149         waitForActiveRequestCount(cronetEngine, 0);
1150     }
1151 
1152     @Test
1153     @SmallTest
1154     @IgnoreFor(
1155             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1156             reason = "Request finished listeners are only supported by native Cronet")
testGetActiveRequestCountOnEngineRequestFinishedListener()1157     public void testGetActiveRequestCountOnEngineRequestFinishedListener() throws Exception {
1158         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1159         TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener();
1160         requestFinishedListener.blockListener();
1161         cronetEngine.addRequestFinishedListener(requestFinishedListener);
1162         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1163         UrlRequest request =
1164                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build();
1165         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0);
1166         request.start();
1167         callback.blockForDone();
1168         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
1169         requestFinishedListener.blockUntilDone();
1170         assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1);
1171         requestFinishedListener.unblockListener();
1172         waitForActiveRequestCount(cronetEngine, 0);
1173     }
1174 
1175     @Test
1176     @SmallTest
1177     @IgnoreFor(
1178             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1179             reason = "NetLog is supported only by the native implementation")
1180     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
1181     // Tests that NetLog contains events emitted by all live CronetEngines.
testNetLogContainEventsFromAllLiveEngines()1182     public void testNetLogContainEventsFromAllLiveEngines() throws Exception {
1183         File directory = new File(PathUtils.getDataDirectory());
1184         File file1 = File.createTempFile("cronet1", "json", directory);
1185         File file2 = File.createTempFile("cronet2", "json", directory);
1186         CronetEngine cronetEngine1 =
1187                 mTestRule
1188                         .getTestFramework()
1189                         .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext())
1190                         .build();
1191         CronetEngine cronetEngine2 =
1192                 mTestRule
1193                         .getTestFramework()
1194                         .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext())
1195                         .build();
1196 
1197         cronetEngine1.startNetLogToFile(file1.getPath(), false);
1198         cronetEngine2.startNetLogToFile(file2.getPath(), false);
1199 
1200         // Warm CronetEngine and make sure both CronetUrlRequestContexts are
1201         // initialized before testing the logs.
1202         makeRequestAndCheckStatus(cronetEngine1, mUrl, 200);
1203         makeRequestAndCheckStatus(cronetEngine2, mUrl, 200);
1204 
1205         // Use cronetEngine1 to make a request to mUrl404.
1206         makeRequestAndCheckStatus(cronetEngine1, mUrl404, 404);
1207 
1208         // Use cronetEngine2 to make a request to mUrl500.
1209         makeRequestAndCheckStatus(cronetEngine2, mUrl500, 500);
1210 
1211         cronetEngine1.stopNetLog();
1212         cronetEngine2.stopNetLog();
1213         assertThat(file1.exists()).isTrue();
1214         assertThat(file2.exists()).isTrue();
1215         // Make sure both files contain the two requests made separately using
1216         // different engines.
1217         assertThat(containsStringInNetLog(file1, mUrl404)).isTrue();
1218         assertThat(containsStringInNetLog(file1, mUrl500)).isTrue();
1219         assertThat(containsStringInNetLog(file2, mUrl404)).isTrue();
1220         assertThat(containsStringInNetLog(file2, mUrl500)).isTrue();
1221         assertThat(file1.delete()).isTrue();
1222         assertThat(file2.delete()).isTrue();
1223     }
1224 
1225     @Test
1226     @SmallTest
1227     @IgnoreFor(
1228             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1229             reason = "NetLog is supported only by the native implementation")
1230     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
1231     // Tests that NetLog contains events emitted by all live CronetEngines.
testBoundedFileNetLogContainEventsFromAllLiveEngines()1232     public void testBoundedFileNetLogContainEventsFromAllLiveEngines() throws Exception {
1233         File directory = new File(PathUtils.getDataDirectory());
1234         File netLogDir1 = new File(directory, "NetLog1" + System.currentTimeMillis());
1235         assertThat(netLogDir1.exists()).isFalse();
1236         assertThat(netLogDir1.mkdir()).isTrue();
1237         File netLogDir2 = new File(directory, "NetLog2" + System.currentTimeMillis());
1238         assertThat(netLogDir2.exists()).isFalse();
1239         assertThat(netLogDir2.mkdir()).isTrue();
1240         File logFile1 = new File(netLogDir1, "netlog.json");
1241         File logFile2 = new File(netLogDir2, "netlog.json");
1242 
1243         CronetEngine cronetEngine1 =
1244                 mTestRule
1245                         .getTestFramework()
1246                         .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext())
1247                         .build();
1248         CronetEngine cronetEngine2 =
1249                 mTestRule
1250                         .getTestFramework()
1251                         .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext())
1252                         .build();
1253 
1254         cronetEngine1.startNetLogToDisk(netLogDir1.getPath(), false, MAX_FILE_SIZE);
1255         cronetEngine2.startNetLogToDisk(netLogDir2.getPath(), false, MAX_FILE_SIZE);
1256 
1257         // Warm CronetEngine and make sure both CronetUrlRequestContexts are
1258         // initialized before testing the logs.
1259         makeRequestAndCheckStatus(cronetEngine1, mUrl, 200);
1260         makeRequestAndCheckStatus(cronetEngine2, mUrl, 200);
1261 
1262         // Use cronetEngine1 to make a request to mUrl404.
1263         makeRequestAndCheckStatus(cronetEngine1, mUrl404, 404);
1264 
1265         // Use cronetEngine2 to make a request to mUrl500.
1266         makeRequestAndCheckStatus(cronetEngine2, mUrl500, 500);
1267 
1268         cronetEngine1.stopNetLog();
1269         cronetEngine2.stopNetLog();
1270 
1271         assertThat(logFile1.exists()).isTrue();
1272         assertThat(logFile2.exists()).isTrue();
1273         assertThat(logFile1.length()).isNotEqualTo(0);
1274         assertThat(logFile2.length()).isNotEqualTo(0);
1275 
1276         // Make sure both files contain the two requests made separately using
1277         // different engines.
1278         assertThat(containsStringInNetLog(logFile1, mUrl404)).isTrue();
1279         assertThat(containsStringInNetLog(logFile1, mUrl500)).isTrue();
1280         assertThat(containsStringInNetLog(logFile2, mUrl404)).isTrue();
1281         assertThat(containsStringInNetLog(logFile2, mUrl500)).isTrue();
1282 
1283         FileUtils.recursivelyDeleteFile(netLogDir1);
1284         assertThat(netLogDir1.exists()).isFalse();
1285         FileUtils.recursivelyDeleteFile(netLogDir2);
1286         assertThat(netLogDir2.exists()).isFalse();
1287     }
1288 
createCronetEngineWithCache(int cacheType)1289     private CronetEngine createCronetEngineWithCache(int cacheType) {
1290         CronetEngine.Builder builder =
1291                 mTestRule
1292                         .getTestFramework()
1293                         .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext());
1294         if (cacheType == CronetEngine.Builder.HTTP_CACHE_DISK
1295                 || cacheType == CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP) {
1296             builder.setStoragePath(getTestStorage(mTestRule.getTestFramework().getContext()));
1297         }
1298         builder.enableHttpCache(cacheType, 100 * 1024);
1299         // Don't check the return value here, because startNativeTestServer() returns false when the
1300         // NativeTestServer is already running and this method needs to be called twice without
1301         // shutting down the NativeTestServer in between.
1302         NativeTestServer.startNativeTestServer(mTestRule.getTestFramework().getContext());
1303         return builder.build();
1304     }
1305 
1306     @Test
1307     @SmallTest
1308     @SkipPresubmit(reason = "b/293141085 flaky test")
1309     @IgnoreFor(
1310             implementations = {CronetImplementation.FALLBACK},
1311             reason = "Fallback implementation does not have a network thread.")
1312     // Tests that if CronetEngine is shut down on the network thread, an appropriate exception
1313     // is thrown.
testShutDownEngineOnNetworkThread()1314     public void testShutDownEngineOnNetworkThread() throws Exception {
1315         final CronetEngine cronetEngine =
1316                 createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK);
1317         String url = NativeTestServer.getFileURL("/cacheable.txt");
1318         // Make a request to a cacheable resource.
1319         checkRequestCaching(cronetEngine, url, false);
1320 
1321         final AtomicReference<Throwable> thrown = new AtomicReference<>();
1322         // Shut down the server.
1323         NativeTestServer.shutdownNativeTestServer();
1324         class CancelUrlRequestCallback extends TestUrlRequestCallback {
1325             @Override
1326             public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
1327                 super.onResponseStarted(request, info);
1328                 request.cancel();
1329                 // Shut down CronetEngine immediately after request is destroyed.
1330                 try {
1331                     cronetEngine.shutdown();
1332                 } catch (Exception e) {
1333                     thrown.set(e);
1334                 }
1335             }
1336 
1337             @Override
1338             public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
1339                 // onSucceeded will not happen, because the request is canceled
1340                 // after sending first read and the executor is single threaded.
1341                 throw new AssertionError("Unexpected");
1342             }
1343 
1344             @Override
1345             public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) {
1346                 throw new AssertionError("Unexpected");
1347             }
1348         }
1349         Executor directExecutor =
1350                 new Executor() {
1351                     @Override
1352                     public void execute(Runnable command) {
1353                         command.run();
1354                     }
1355                 };
1356         CancelUrlRequestCallback callback = new CancelUrlRequestCallback();
1357         callback.setAllowDirectExecutor(true);
1358         UrlRequest.Builder urlRequestBuilder =
1359                 cronetEngine.newUrlRequestBuilder(url, callback, directExecutor);
1360         urlRequestBuilder.allowDirectExecutor();
1361         urlRequestBuilder.build().start();
1362         callback.blockForDone();
1363         assertThat(thrown.get()).isInstanceOf(RuntimeException.class);
1364         cronetEngine.shutdown();
1365     }
1366 
1367     @Test
1368     @SmallTest
1369     @SkipPresubmit(reason = "b/293141085 flaky test")
1370     @IgnoreFor(
1371             implementations = {CronetImplementation.FALLBACK},
1372             reason = "Fallback implementation has no support for caches")
1373     // Tests that if CronetEngine is shut down when reading from disk cache,
1374     // there isn't a crash. See crbug.com/486120.
testShutDownEngineWhenReadingFromDiskCache()1375     public void testShutDownEngineWhenReadingFromDiskCache() throws Exception {
1376         final CronetEngine cronetEngine =
1377                 createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK);
1378         String url = NativeTestServer.getFileURL("/cacheable.txt");
1379         // Make a request to a cacheable resource.
1380         checkRequestCaching(cronetEngine, url, false);
1381 
1382         // Shut down the server.
1383         NativeTestServer.shutdownNativeTestServer();
1384         class CancelUrlRequestCallback extends TestUrlRequestCallback {
1385             @Override
1386             public void onResponseStarted(UrlRequest request, UrlResponseInfo info) {
1387                 super.onResponseStarted(request, info);
1388                 request.cancel();
1389                 // Shut down CronetEngine immediately after request is destroyed.
1390                 cronetEngine.shutdown();
1391             }
1392 
1393             @Override
1394             public void onSucceeded(UrlRequest request, UrlResponseInfo info) {
1395                 // onSucceeded will not happen, because the request is canceled
1396                 // after sending first read and the executor is single threaded.
1397                 throw new RuntimeException("Unexpected");
1398             }
1399 
1400             @Override
1401             public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) {
1402                 throw new RuntimeException("Unexpected");
1403             }
1404         }
1405         CancelUrlRequestCallback callback = new CancelUrlRequestCallback();
1406         UrlRequest.Builder urlRequestBuilder =
1407                 cronetEngine.newUrlRequestBuilder(url, callback, callback.getExecutor());
1408         urlRequestBuilder.build().start();
1409         callback.blockForDone();
1410         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1411         assertThat(callback.getResponseInfoWithChecks()).wasCached();
1412         assertThat(callback.mOnCanceledCalled).isTrue();
1413     }
1414 
1415     @Test
1416     @SmallTest
1417     @IgnoreFor(
1418             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1419             reason = "NetLog is supported only by the native implementation")
1420     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
testNetLogAfterShutdown()1421     public void testNetLogAfterShutdown() throws Exception {
1422         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1423         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1424         UrlRequest.Builder urlRequestBuilder =
1425                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1426         urlRequestBuilder.build().start();
1427         callback.blockForDone();
1428         cronetEngine.shutdown();
1429 
1430         File directory = new File(PathUtils.getDataDirectory());
1431         File file = File.createTempFile("cronet", "json", directory);
1432 
1433         Exception e =
1434                 assertThrows(
1435                         Exception.class,
1436                         () -> cronetEngine.startNetLogToFile(file.getPath(), false));
1437         assertThat(e).hasMessageThat().isEqualTo("Engine is shut down.");
1438         assertThat(hasBytesInNetLog(file)).isFalse();
1439         assertThat(file.delete()).isTrue();
1440         assertThat(file.exists()).isFalse();
1441     }
1442 
1443     @Test
1444     @SmallTest
1445     @IgnoreFor(
1446             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1447             reason = "NetLog is supported only by the native implementation")
1448     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
testBoundedFileNetLogAfterShutdown()1449     public void testBoundedFileNetLogAfterShutdown() throws Exception {
1450         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1451         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1452         UrlRequest.Builder urlRequestBuilder =
1453                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1454         urlRequestBuilder.build().start();
1455         callback.blockForDone();
1456         cronetEngine.shutdown();
1457 
1458         File directory = new File(PathUtils.getDataDirectory());
1459         File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis());
1460         assertThat(netLogDir.exists()).isFalse();
1461         assertThat(netLogDir.mkdir()).isTrue();
1462         File logFile = new File(netLogDir, "netlog.json");
1463         Exception e =
1464                 assertThrows(
1465                         Exception.class,
1466                         () ->
1467                                 cronetEngine.startNetLogToDisk(
1468                                         netLogDir.getPath(), false, MAX_FILE_SIZE));
1469         assertThat(e).hasMessageThat().isEqualTo("Engine is shut down.");
1470         assertThat(logFile.exists()).isFalse();
1471         FileUtils.recursivelyDeleteFile(netLogDir);
1472         assertThat(netLogDir.exists()).isFalse();
1473     }
1474 
1475     @Test
1476     @SmallTest
1477     @IgnoreFor(
1478             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1479             reason = "NetLog is supported only by the native implementation")
1480     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
testNetLogStartMultipleTimes()1481     public void testNetLogStartMultipleTimes() throws Exception {
1482         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1483         File directory = new File(PathUtils.getDataDirectory());
1484         File file = File.createTempFile("cronet", "json", directory);
1485         // Start NetLog multiple times.
1486         cronetEngine.startNetLogToFile(file.getPath(), false);
1487         cronetEngine.startNetLogToFile(file.getPath(), false);
1488         cronetEngine.startNetLogToFile(file.getPath(), false);
1489         cronetEngine.startNetLogToFile(file.getPath(), false);
1490         // Start a request.
1491         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1492         UrlRequest.Builder urlRequestBuilder =
1493                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1494         urlRequestBuilder.build().start();
1495         callback.blockForDone();
1496         cronetEngine.stopNetLog();
1497         assertThat(file.exists()).isTrue();
1498         assertThat(file.length()).isNotEqualTo(0);
1499         assertThat(hasBytesInNetLog(file)).isFalse();
1500         assertThat(file.delete()).isTrue();
1501         assertThat(file.exists()).isFalse();
1502     }
1503 
1504     @Test
1505     @SmallTest
1506     @IgnoreFor(
1507             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1508             reason = "NetLog is supported only by the native implementation")
1509     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
testBoundedFileNetLogStartMultipleTimes()1510     public void testBoundedFileNetLogStartMultipleTimes() throws Exception {
1511         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1512         File directory = new File(PathUtils.getDataDirectory());
1513         File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis());
1514         assertThat(netLogDir.exists()).isFalse();
1515         assertThat(netLogDir.mkdir()).isTrue();
1516         File logFile = new File(netLogDir, "netlog.json");
1517         // Start NetLog multiple times. This should be equivalent to starting NetLog
1518         // once. Each subsequent start (without calling stopNetLog) should be a no-op.
1519         cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
1520         cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
1521         cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
1522         cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
1523         // Start a request.
1524         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1525         UrlRequest.Builder urlRequestBuilder =
1526                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1527         urlRequestBuilder.build().start();
1528         callback.blockForDone();
1529         cronetEngine.stopNetLog();
1530         assertThat(logFile.exists()).isTrue();
1531         assertThat(logFile.length()).isNotEqualTo(0);
1532         assertThat(hasBytesInNetLog(logFile)).isFalse();
1533         FileUtils.recursivelyDeleteFile(netLogDir);
1534         assertThat(netLogDir.exists()).isFalse();
1535     }
1536 
1537     @Test
1538     @SmallTest
1539     @IgnoreFor(
1540             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1541             reason = "NetLog is supported only by the native implementation")
1542     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
testNetLogStopMultipleTimes()1543     public void testNetLogStopMultipleTimes() throws Exception {
1544         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1545         File directory = new File(PathUtils.getDataDirectory());
1546         File file = File.createTempFile("cronet", "json", directory);
1547         cronetEngine.startNetLogToFile(file.getPath(), false);
1548         // Start a request.
1549         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1550         UrlRequest.Builder urlRequestBuilder =
1551                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1552         urlRequestBuilder.build().start();
1553         callback.blockForDone();
1554         // Stop NetLog multiple times.
1555         cronetEngine.stopNetLog();
1556         cronetEngine.stopNetLog();
1557         cronetEngine.stopNetLog();
1558         cronetEngine.stopNetLog();
1559         cronetEngine.stopNetLog();
1560         assertThat(file.exists()).isTrue();
1561         assertThat(file.length()).isNotEqualTo(0);
1562         assertThat(hasBytesInNetLog(file)).isFalse();
1563         assertThat(file.delete()).isTrue();
1564         assertThat(file.exists()).isFalse();
1565     }
1566 
1567     @Test
1568     @SmallTest
1569     @IgnoreFor(
1570             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1571             reason = "NetLog is supported only by the native implementation")
1572     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
testBoundedFileNetLogStopMultipleTimes()1573     public void testBoundedFileNetLogStopMultipleTimes() throws Exception {
1574         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1575         File directory = new File(PathUtils.getDataDirectory());
1576         File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis());
1577         assertThat(netLogDir.exists()).isFalse();
1578         assertThat(netLogDir.mkdir()).isTrue();
1579         File logFile = new File(netLogDir, "netlog.json");
1580         cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE);
1581         // Start a request.
1582         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1583         UrlRequest.Builder urlRequestBuilder =
1584                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1585         urlRequestBuilder.build().start();
1586         callback.blockForDone();
1587         // Stop NetLog multiple times. This should be equivalent to stopping NetLog once.
1588         // Each subsequent stop (without calling startNetLogToDisk first) should be a no-op.
1589         cronetEngine.stopNetLog();
1590         cronetEngine.stopNetLog();
1591         cronetEngine.stopNetLog();
1592         cronetEngine.stopNetLog();
1593         cronetEngine.stopNetLog();
1594         assertThat(logFile.exists()).isTrue();
1595         assertThat(logFile.length()).isNotEqualTo(0);
1596         assertThat(hasBytesInNetLog(logFile)).isFalse();
1597         FileUtils.recursivelyDeleteFile(netLogDir);
1598         assertThat(netLogDir.exists()).isFalse();
1599     }
1600 
1601     @Test
1602     @SmallTest
1603     @IgnoreFor(
1604             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1605             reason = "NetLog is supported only by the native implementation")
1606     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
testNetLogWithBytes()1607     public void testNetLogWithBytes() throws Exception {
1608         File directory = new File(PathUtils.getDataDirectory());
1609         File file = File.createTempFile("cronet", "json", directory);
1610         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1611         // Start NetLog with logAll as true.
1612         cronetEngine.startNetLogToFile(file.getPath(), true);
1613         // Start a request.
1614         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1615         UrlRequest.Builder urlRequestBuilder =
1616                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1617         urlRequestBuilder.build().start();
1618         callback.blockForDone();
1619         cronetEngine.stopNetLog();
1620         assertThat(file.exists()).isTrue();
1621         assertThat(file.length()).isNotEqualTo(0);
1622         assertThat(hasBytesInNetLog(file)).isTrue();
1623         assertThat(file.delete()).isTrue();
1624         assertThat(file.exists()).isFalse();
1625     }
1626 
1627     @Test
1628     @SmallTest
1629     @IgnoreFor(
1630             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1631             reason = "NetLog is supported only by the native implementation")
1632     @DisableAutomaticNetLog(reason = "Test is targeting NetLog")
testBoundedFileNetLogWithBytes()1633     public void testBoundedFileNetLogWithBytes() throws Exception {
1634         File directory = new File(PathUtils.getDataDirectory());
1635         File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis());
1636         assertThat(netLogDir.exists()).isFalse();
1637         assertThat(netLogDir.mkdir()).isTrue();
1638         File logFile = new File(netLogDir, "netlog.json");
1639         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1640         // Start NetLog with logAll as true.
1641         cronetEngine.startNetLogToDisk(netLogDir.getPath(), true, MAX_FILE_SIZE);
1642         // Start a request.
1643         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1644         UrlRequest.Builder urlRequestBuilder =
1645                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1646         urlRequestBuilder.build().start();
1647         callback.blockForDone();
1648         cronetEngine.stopNetLog();
1649 
1650         assertThat(logFile.exists()).isTrue();
1651         assertThat(logFile.length()).isNotEqualTo(0);
1652         assertThat(hasBytesInNetLog(logFile)).isTrue();
1653         FileUtils.recursivelyDeleteFile(netLogDir);
1654         assertThat(netLogDir.exists()).isFalse();
1655     }
1656 
hasBytesInNetLog(File logFile)1657     private boolean hasBytesInNetLog(File logFile) throws Exception {
1658         return containsStringInNetLog(logFile, "\"bytes\"");
1659     }
1660 
containsStringInNetLog(File logFile, String content)1661     private boolean containsStringInNetLog(File logFile, String content) throws Exception {
1662         BufferedReader logReader = new BufferedReader(new FileReader(logFile));
1663         try {
1664             String logLine;
1665             while ((logLine = logReader.readLine()) != null) {
1666                 if (logLine.contains(content)) {
1667                     return true;
1668                 }
1669             }
1670             return false;
1671         } finally {
1672             logReader.close();
1673         }
1674     }
1675 
1676     /**
1677      * Helper method to make a request to {@code url}, wait for it to complete, and check that the
1678      * status code is the same as {@code expectedStatusCode}.
1679      */
makeRequestAndCheckStatus( CronetEngine engine, String url, int expectedStatusCode)1680     private void makeRequestAndCheckStatus(
1681             CronetEngine engine, String url, int expectedStatusCode) {
1682         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1683         UrlRequest request =
1684                 engine.newUrlRequestBuilder(url, callback, callback.getExecutor()).build();
1685         request.start();
1686         callback.blockForDone();
1687         assertThat(callback.getResponseInfoWithChecks())
1688                 .hasHttpStatusCodeThat()
1689                 .isEqualTo(expectedStatusCode);
1690     }
1691 
checkRequestCaching(CronetEngine engine, String url, boolean expectCached)1692     private void checkRequestCaching(CronetEngine engine, String url, boolean expectCached) {
1693         checkRequestCaching(engine, url, expectCached, false);
1694     }
1695 
checkRequestCaching( CronetEngine engine, String url, boolean expectCached, boolean disableCache)1696     private void checkRequestCaching(
1697             CronetEngine engine, String url, boolean expectCached, boolean disableCache) {
1698         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1699         UrlRequest.Builder urlRequestBuilder =
1700                 engine.newUrlRequestBuilder(url, callback, callback.getExecutor());
1701         if (disableCache) {
1702             urlRequestBuilder.disableCache();
1703         }
1704         urlRequestBuilder.build().start();
1705         callback.blockForDone();
1706         assertThat(callback.getResponseInfoWithChecks().wasCached()).isEqualTo(expectCached);
1707         assertThat(callback.mResponseAsString).isEqualTo("this is a cacheable file\n");
1708     }
1709 
1710     @Test
1711     @SmallTest
1712     @IgnoreFor(
1713             implementations = {CronetImplementation.FALLBACK},
1714             reason = "No caches support for fallback implementation")
testEnableHttpCacheDisabled()1715     public void testEnableHttpCacheDisabled() throws Exception {
1716         CronetEngine cronetEngine =
1717                 createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISABLED);
1718         String url = NativeTestServer.getFileURL("/cacheable.txt");
1719         checkRequestCaching(cronetEngine, url, false);
1720         checkRequestCaching(cronetEngine, url, false);
1721         checkRequestCaching(cronetEngine, url, false);
1722         cronetEngine.shutdown();
1723     }
1724 
1725     @Test
1726     @SmallTest
1727     @IgnoreFor(
1728             implementations = {CronetImplementation.FALLBACK},
1729             reason = "No caches support for fallback implementation")
testEnableHttpCacheInMemory()1730     public void testEnableHttpCacheInMemory() throws Exception {
1731         CronetEngine cronetEngine =
1732                 createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY);
1733         String url = NativeTestServer.getFileURL("/cacheable.txt");
1734         checkRequestCaching(cronetEngine, url, false);
1735         checkRequestCaching(cronetEngine, url, true);
1736         NativeTestServer.shutdownNativeTestServer();
1737         checkRequestCaching(cronetEngine, url, true);
1738         cronetEngine.shutdown();
1739     }
1740 
1741     @Test
1742     @SmallTest
1743     @IgnoreFor(
1744             implementations = {CronetImplementation.FALLBACK},
1745             reason = "No caches support for fallback implementation")
testEnableHttpCacheDisk()1746     public void testEnableHttpCacheDisk() throws Exception {
1747         CronetEngine cronetEngine =
1748                 createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK);
1749         String url = NativeTestServer.getFileURL("/cacheable.txt");
1750         checkRequestCaching(cronetEngine, url, false);
1751         checkRequestCaching(cronetEngine, url, true);
1752         NativeTestServer.shutdownNativeTestServer();
1753         checkRequestCaching(cronetEngine, url, true);
1754         cronetEngine.shutdown();
1755     }
1756 
1757     @Test
1758     @SmallTest
1759     @SkipPresubmit(reason = "b/293141085 flaky test")
1760     @IgnoreFor(
1761             implementations = {CronetImplementation.FALLBACK},
1762             reason = "No caches support for fallback implementation")
testNoConcurrentDiskUsage()1763     public void testNoConcurrentDiskUsage() throws Exception {
1764         CronetEngine cronetEngine =
1765                 createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK);
1766 
1767         IllegalStateException e =
1768                 assertThrows(
1769                         IllegalStateException.class,
1770                         () -> createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK));
1771         assertThat(e).hasMessageThat().isEqualTo("Disk cache storage path already in use");
1772 
1773         String url = NativeTestServer.getFileURL("/cacheable.txt");
1774         checkRequestCaching(cronetEngine, url, false);
1775         checkRequestCaching(cronetEngine, url, true);
1776         NativeTestServer.shutdownNativeTestServer();
1777         checkRequestCaching(cronetEngine, url, true);
1778         cronetEngine.shutdown();
1779     }
1780 
1781     @Test
1782     @SmallTest
1783     @SkipPresubmit(reason = "b/293141085 flaky test")
1784     @IgnoreFor(
1785             implementations = {CronetImplementation.FALLBACK},
1786             reason = "No caches support for fallback implementation")
testEnableHttpCacheDiskNoHttp()1787     public void testEnableHttpCacheDiskNoHttp() throws Exception {
1788         CronetEngine cronetEngine =
1789                 createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP);
1790         String url = NativeTestServer.getFileURL("/cacheable.txt");
1791         checkRequestCaching(cronetEngine, url, false);
1792         checkRequestCaching(cronetEngine, url, false);
1793         checkRequestCaching(cronetEngine, url, false);
1794 
1795         // Make a new CronetEngine and try again to make sure the response didn't get cached on the
1796         // first request. See https://crbug.com/743232.
1797         cronetEngine.shutdown();
1798         cronetEngine = createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP);
1799         checkRequestCaching(cronetEngine, url, false);
1800         checkRequestCaching(cronetEngine, url, false);
1801         checkRequestCaching(cronetEngine, url, false);
1802         cronetEngine.shutdown();
1803     }
1804 
1805     @Test
1806     @SmallTest
1807     @IgnoreFor(
1808             implementations = {CronetImplementation.FALLBACK},
1809             reason = "No caches support for fallback implementation")
testDisableCache()1810     public void testDisableCache() throws Exception {
1811         CronetEngine cronetEngine =
1812                 createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK);
1813         String url = NativeTestServer.getFileURL("/cacheable.txt");
1814 
1815         // When cache is disabled, making a request does not write to the cache.
1816         checkRequestCaching(
1817                 cronetEngine, url, false, true
1818                 /** disable cache */
1819                 );
1820         checkRequestCaching(cronetEngine, url, false);
1821 
1822         // When cache is enabled, the second request is cached.
1823         checkRequestCaching(
1824                 cronetEngine, url, false, true
1825                 /** disable cache */
1826                 );
1827         checkRequestCaching(cronetEngine, url, true);
1828 
1829         // Shut down the server, next request should have a cached response.
1830         NativeTestServer.shutdownNativeTestServer();
1831         checkRequestCaching(cronetEngine, url, true);
1832 
1833         // Cache is disabled after server is shut down, request should fail.
1834         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1835         UrlRequest.Builder urlRequestBuilder =
1836                 cronetEngine.newUrlRequestBuilder(url, callback, callback.getExecutor());
1837         urlRequestBuilder.disableCache();
1838         urlRequestBuilder.build().start();
1839         callback.blockForDone();
1840         assertThat(callback.mError)
1841                 .hasMessageThat()
1842                 .contains("Exception in CronetUrlRequest: net::ERR_CONNECTION_REFUSED");
1843         cronetEngine.shutdown();
1844     }
1845 
1846     @Test
1847     @SmallTest
1848     @IgnoreFor(
1849             implementations = {CronetImplementation.FALLBACK},
1850             reason = "No caches support for fallback implementation")
testEnableHttpCacheDiskNewEngine()1851     public void testEnableHttpCacheDiskNewEngine() throws Exception {
1852         CronetEngine cronetEngine =
1853                 createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK);
1854         String url = NativeTestServer.getFileURL("/cacheable.txt");
1855         checkRequestCaching(cronetEngine, url, false);
1856         checkRequestCaching(cronetEngine, url, true);
1857         NativeTestServer.shutdownNativeTestServer();
1858         checkRequestCaching(cronetEngine, url, true);
1859 
1860         // Shutdown original context and create another that uses the same cache.
1861         cronetEngine.shutdown();
1862         cronetEngine =
1863                 mTestRule
1864                         .getTestFramework()
1865                         .enableDiskCache(
1866                                 mTestRule
1867                                         .getTestFramework()
1868                                         .createNewSecondaryBuilder(
1869                                                 mTestRule.getTestFramework().getContext()))
1870                         .build();
1871         checkRequestCaching(cronetEngine, url, true);
1872         cronetEngine.shutdown();
1873     }
1874 
1875     @Test
1876     @SmallTest
testInitEngineAndStartRequest()1877     public void testInitEngineAndStartRequest() {
1878         // Immediately make a request after initializing the engine.
1879         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1880         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1881         UrlRequest.Builder urlRequestBuilder =
1882                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1883         urlRequestBuilder.build().start();
1884         callback.blockForDone();
1885         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
1886     }
1887 
1888     @Test
1889     @SmallTest
testInitEngineStartTwoRequests()1890     public void testInitEngineStartTwoRequests() throws Exception {
1891         // Make two requests after initializing the context.
1892         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1893         int[] statusCodes = {0, 0};
1894         String[] urls = {mUrl, mUrl404};
1895         for (int i = 0; i < 2; i++) {
1896             TestUrlRequestCallback callback = new TestUrlRequestCallback();
1897             UrlRequest.Builder urlRequestBuilder =
1898                     cronetEngine.newUrlRequestBuilder(urls[i], callback, callback.getExecutor());
1899             urlRequestBuilder.build().start();
1900             callback.blockForDone();
1901             statusCodes[i] = callback.getResponseInfoWithChecks().getHttpStatusCode();
1902         }
1903         assertThat(statusCodes).asList().containsExactly(200, 404).inOrder();
1904     }
1905 
1906     @Test
1907     @SmallTest
testInitTwoEnginesSimultaneously()1908     public void testInitTwoEnginesSimultaneously() throws Exception {
1909         // Threads will block on runBlocker to ensure simultaneous execution.
1910         ConditionVariable runBlocker = new ConditionVariable(false);
1911         RequestThread thread1 = new RequestThread(mUrl, runBlocker);
1912         RequestThread thread2 = new RequestThread(mUrl404, runBlocker);
1913 
1914         thread1.start();
1915         thread2.start();
1916         runBlocker.open();
1917         thread1.join();
1918         thread2.join();
1919         assertThat(thread1.mCallback.getResponseInfoWithChecks())
1920                 .hasHttpStatusCodeThat()
1921                 .isEqualTo(200);
1922         assertThat(thread2.mCallback.getResponseInfoWithChecks())
1923                 .hasHttpStatusCodeThat()
1924                 .isEqualTo(404);
1925     }
1926 
1927     @Test
1928     @SmallTest
testInitTwoEnginesInSequence()1929     public void testInitTwoEnginesInSequence() throws Exception {
1930         ConditionVariable runBlocker = new ConditionVariable(true);
1931         RequestThread thread1 = new RequestThread(mUrl, runBlocker);
1932         RequestThread thread2 = new RequestThread(mUrl404, runBlocker);
1933 
1934         thread1.start();
1935         thread1.join();
1936         thread2.start();
1937         thread2.join();
1938         assertThat(thread1.mCallback.getResponseInfoWithChecks())
1939                 .hasHttpStatusCodeThat()
1940                 .isEqualTo(200);
1941         assertThat(thread2.mCallback.getResponseInfoWithChecks())
1942                 .hasHttpStatusCodeThat()
1943                 .isEqualTo(404);
1944     }
1945 
1946     @Test
1947     @SmallTest
testInitDifferentEngines()1948     public void testInitDifferentEngines() throws Exception {
1949         // Test that concurrently instantiating Cronet context's upon various
1950         // different versions of the same Android Context does not cause crashes
1951         // like crbug.com/453845
1952         CronetEngine firstEngine =
1953                 mTestRule
1954                         .getTestFramework()
1955                         .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext())
1956                         .build();
1957         CronetEngine secondEngine =
1958                 mTestRule
1959                         .getTestFramework()
1960                         .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext())
1961                         .build();
1962         CronetEngine thirdEngine =
1963                 mTestRule
1964                         .getTestFramework()
1965                         .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext())
1966                         .build();
1967         firstEngine.shutdown();
1968         secondEngine.shutdown();
1969         thirdEngine.shutdown();
1970     }
1971 
1972     @Test
1973     @SmallTest
1974     @IgnoreFor(
1975             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
1976             reason = "Global metrics delta is supported only by the native implementation")
testGetGlobalMetricsDeltas()1977     public void testGetGlobalMetricsDeltas() throws Exception {
1978         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
1979 
1980         byte[] delta1 = cronetEngine.getGlobalMetricsDeltas();
1981 
1982         TestUrlRequestCallback callback = new TestUrlRequestCallback();
1983         UrlRequest.Builder builder =
1984                 cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor());
1985         builder.build().start();
1986         callback.blockForDone();
1987         // Fetch deltas on a different thread the second time to make sure this is permitted.
1988         // See crbug.com/719448
1989         FutureTask<byte[]> task =
1990                 new FutureTask<byte[]>(
1991                         new Callable<byte[]>() {
1992                             @Override
1993                             public byte[] call() {
1994                                 return cronetEngine.getGlobalMetricsDeltas();
1995                             }
1996                         });
1997         new Thread(task).start();
1998         byte[] delta2 = task.get();
1999         assertThat(delta2).isNotEmpty();
2000         assertThat(delta2).isNotEqualTo(delta1);
2001     }
2002 
2003     @Test
2004     @SmallTest
2005     @IgnoreFor(
2006             implementations = {CronetImplementation.FALLBACK},
2007             reason = "Deliberate manual creation of native engines")
testCronetEngineBuilderConfig()2008     public void testCronetEngineBuilderConfig() throws Exception {
2009         // This is to prompt load of native library.
2010         mTestRule.getTestFramework().startEngine();
2011         // Verify CronetEngine.Builder config is passed down accurately to native code.
2012         ExperimentalCronetEngine.Builder builder =
2013                 new ExperimentalCronetEngine.Builder(mTestRule.getTestFramework().getContext());
2014         builder.enableHttp2(false);
2015         builder.enableQuic(true);
2016         builder.addQuicHint("example.com", 12, 34);
2017         builder.enableHttpCache(HTTP_CACHE_IN_MEMORY, 54321);
2018         builder.setUserAgent("efgh");
2019         builder.setExperimentalOptions("");
2020         builder.setStoragePath(getTestStorage(mTestRule.getTestFramework().getContext()));
2021         builder.enablePublicKeyPinningBypassForLocalTrustAnchors(false);
2022         CronetUrlRequestContextTestJni.get()
2023                 .verifyUrlRequestContextConfig(
2024                         CronetUrlRequestContext.createNativeUrlRequestContextConfig(
2025                                 CronetTestUtil.getCronetEngineBuilderImpl(builder)),
2026                         getTestStorage(mTestRule.getTestFramework().getContext()));
2027     }
2028 
2029     @Test
2030     @SmallTest
2031     @IgnoreFor(
2032             implementations = {CronetImplementation.FALLBACK},
2033             reason = "Deliberate manual creation of native engines")
testCronetEngineQuicOffConfig()2034     public void testCronetEngineQuicOffConfig() throws Exception {
2035         // This is to prompt load of native library.
2036         mTestRule.getTestFramework().startEngine();
2037         // Verify CronetEngine.Builder config is passed down accurately to native code.
2038         ExperimentalCronetEngine.Builder builder =
2039                 new ExperimentalCronetEngine.Builder(mTestRule.getTestFramework().getContext());
2040         builder.enableHttp2(false);
2041         // QUIC is on by default. Disabling it here to make sure the built config can correctly
2042         // reflect the change.
2043         builder.enableQuic(false);
2044         builder.enableHttpCache(HTTP_CACHE_IN_MEMORY, 54321);
2045         builder.setExperimentalOptions("");
2046         builder.setUserAgent("efgh");
2047         builder.setStoragePath(getTestStorage(mTestRule.getTestFramework().getContext()));
2048         builder.enablePublicKeyPinningBypassForLocalTrustAnchors(false);
2049         CronetUrlRequestContextTestJni.get()
2050                 .verifyUrlRequestContextQuicOffConfig(
2051                         CronetUrlRequestContext.createNativeUrlRequestContextConfig(
2052                                 CronetTestUtil.getCronetEngineBuilderImpl(builder)),
2053                         getTestStorage(mTestRule.getTestFramework().getContext()));
2054     }
2055 
2056     private static class TestBadLibraryLoader extends CronetEngine.Builder.LibraryLoader {
2057         private boolean mWasCalled;
2058 
2059         @Override
loadLibrary(String libName)2060         public void loadLibrary(String libName) {
2061             // Report that this method was called, but don't load the library
2062             mWasCalled = true;
2063         }
2064 
wasCalled()2065         boolean wasCalled() {
2066             return mWasCalled;
2067         }
2068     }
2069 
2070     @Test
2071     @SmallTest
2072     @IgnoreFor(
2073             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
2074             reason = "LibraryLoader is supported only by the native implementation")
testSetLibraryLoaderIsEnforcedByDefaultEmbeddedProvider()2075     public void testSetLibraryLoaderIsEnforcedByDefaultEmbeddedProvider() throws Exception {
2076         CronetEngine.Builder builder =
2077                 new CronetEngine.Builder(mTestRule.getTestFramework().getContext());
2078         TestBadLibraryLoader loader = new TestBadLibraryLoader();
2079         builder.setLibraryLoader(loader);
2080 
2081         assertThrows(
2082                 "Native library should not be loaded", UnsatisfiedLinkError.class, builder::build);
2083         assertThat(loader.wasCalled()).isTrue();
2084 
2085         // The init thread is started *before* the library is loaded, so the init thread is running
2086         // despite the library loading failure. Init thread initialization can race against test
2087         // cleanup (e.g. Context access). We work around the issue by ensuring test cleanup will
2088         // call shutdown() on a real engine, which will block until the init thread initialization
2089         // is done.
2090         mTestRule.getTestFramework().startEngine();
2091     }
2092 
2093     @Test
2094     @SmallTest
2095     @IgnoreFor(
2096             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
2097             reason = "LibraryLoader is supported only by the native implementation")
testSetLibraryLoaderIsIgnoredInNativeCronetEngineBuilderImpl()2098     public void testSetLibraryLoaderIsIgnoredInNativeCronetEngineBuilderImpl() throws Exception {
2099         CronetEngine.Builder builder =
2100                 new CronetEngine.Builder(
2101                         new NativeCronetEngineBuilderImpl(
2102                                 mTestRule.getTestFramework().getContext()));
2103         TestBadLibraryLoader loader = new TestBadLibraryLoader();
2104         builder.setLibraryLoader(loader);
2105         CronetEngine engine = builder.build();
2106         assertThat(engine).isNotNull();
2107         assertThat(loader.wasCalled()).isFalse();
2108         engine.shutdown();
2109     }
2110 
2111     // Creates a CronetEngine on another thread and then one on the main thread.  This shouldn't
2112     // crash.
2113     @Test
2114     @SmallTest
testThreadedStartup()2115     public void testThreadedStartup() throws Exception {
2116         final ConditionVariable otherThreadDone = new ConditionVariable();
2117         final ConditionVariable uiThreadDone = new ConditionVariable();
2118         new Handler(Looper.getMainLooper())
2119                 .post(
2120                         new Runnable() {
2121                             @Override
2122                             public void run() {
2123                                 final ExperimentalCronetEngine.Builder builder =
2124                                         mTestRule
2125                                                 .getTestFramework()
2126                                                 .createNewSecondaryBuilder(
2127                                                         mTestRule.getTestFramework().getContext());
2128                                 new Thread() {
2129                                     @Override
2130                                     public void run() {
2131                                         CronetEngine cronetEngine = builder.build();
2132                                         otherThreadDone.open();
2133                                         cronetEngine.shutdown();
2134                                     }
2135                                 }.start();
2136                                 otherThreadDone.block();
2137                                 builder.build().shutdown();
2138                                 uiThreadDone.open();
2139                             }
2140                         });
2141         assertThat(uiThreadDone.block(1000)).isTrue();
2142     }
2143 
2144     @Test
2145     @SmallTest
2146     @IgnoreFor(
2147             implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM},
2148             reason = "JSON experimental options are supported only by the native implementation")
testHostResolverRules()2149     public void testHostResolverRules() throws Exception {
2150         String resolverTestHostname = "some-weird-hostname";
2151         URL testUrl = new URL(mUrl);
2152         JSONObject hostResolverRules =
2153                 new JSONObject()
2154                         .put(
2155                                 "host_resolver_rules",
2156                                 "MAP " + resolverTestHostname + " " + testUrl.getHost());
2157         mTestRule
2158                 .getTestFramework()
2159                 .applyEngineBuilderPatch(
2160                         (builder) -> {
2161                             JSONObject experimentalOptions =
2162                                     new JSONObject().put("HostResolverRules", hostResolverRules);
2163                             builder.setExperimentalOptions(experimentalOptions.toString());
2164                         });
2165 
2166         ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine();
2167 
2168         TestUrlRequestCallback callback = new TestUrlRequestCallback();
2169         URL requestUrl =
2170                 new URL("http", resolverTestHostname, testUrl.getPort(), testUrl.getFile());
2171         UrlRequest.Builder urlRequestBuilder =
2172                 cronetEngine.newUrlRequestBuilder(
2173                         requestUrl.toString(), callback, callback.getExecutor());
2174         urlRequestBuilder.build().start();
2175         callback.blockForDone();
2176         assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200);
2177     }
2178 
2179     /** Runs {@code r} on {@code engine}'s network thread. */
postToNetworkThread(final CronetEngine engine, final Runnable r)2180     private static void postToNetworkThread(final CronetEngine engine, final Runnable r) {
2181         // Works by requesting an invalid URL which results in onFailed() being called, which is
2182         // done through a direct executor which causes onFailed to be run on the network thread.
2183         Executor directExecutor =
2184                 new Executor() {
2185                     @Override
2186                     public void execute(Runnable runable) {
2187                         runable.run();
2188                     }
2189                 };
2190         UrlRequest.Callback callback =
2191                 new UrlRequest.Callback() {
2192                     @Override
2193                     public void onRedirectReceived(
2194                             UrlRequest request,
2195                             UrlResponseInfo responseInfo,
2196                             String newLocationUrl) {}
2197 
2198                     @Override
2199                     public void onResponseStarted(
2200                             UrlRequest request, UrlResponseInfo responseInfo) {}
2201 
2202                     @Override
2203                     public void onReadCompleted(
2204                             UrlRequest request,
2205                             UrlResponseInfo responseInfo,
2206                             ByteBuffer byteBuffer) {}
2207 
2208                     @Override
2209                     public void onSucceeded(UrlRequest request, UrlResponseInfo responseInfo) {}
2210 
2211                     @Override
2212                     public void onFailed(
2213                             UrlRequest request,
2214                             UrlResponseInfo responseInfo,
2215                             CronetException error) {
2216                         r.run();
2217                     }
2218                 };
2219         engine.newUrlRequestBuilder("", callback, directExecutor).build().start();
2220     }
2221 
2222     /** @returns the thread priority of {@code engine}'s network thread. */
2223     private static class ApiHelper {
doesContextExistForNetwork(CronetEngine engine, Network network)2224         public static boolean doesContextExistForNetwork(CronetEngine engine, Network network)
2225                 throws Exception {
2226             FutureTask<Boolean> task =
2227                     new FutureTask<Boolean>(
2228                             new Callable<Boolean>() {
2229                                 @Override
2230                                 public Boolean call() {
2231                                     return CronetTestUtil.doesURLRequestContextExistForTesting(
2232                                             engine, network);
2233                                 }
2234                             });
2235             postToNetworkThread(engine, task);
2236             return task.get();
2237         }
2238     }
2239 
2240     /** @returns the thread priority of {@code engine}'s network thread. */
getThreadPriority(CronetEngine engine)2241     private int getThreadPriority(CronetEngine engine) throws Exception {
2242         FutureTask<Integer> task =
2243                 new FutureTask<Integer>(
2244                         new Callable<Integer>() {
2245                             @Override
2246                             public Integer call() {
2247                                 return Process.getThreadPriority(Process.myTid());
2248                             }
2249                         });
2250         postToNetworkThread(engine, task);
2251         return task.get();
2252     }
2253 
2254     /**
2255      * Cronet does not currently provide an API to wait for the active request count to change. We
2256      * can't just wait for the terminal callback to fire because Cronet updates the count some time
2257      * *after* we return from the callback. We hack around this by polling the active request count
2258      * in a loop.
2259      */
waitForActiveRequestCount(CronetEngine engine, int expectedCount)2260     private static void waitForActiveRequestCount(CronetEngine engine, int expectedCount)
2261             throws Exception {
2262         while (engine.getActiveRequestCount() != expectedCount) Thread.sleep(100);
2263     }
2264 
2265     @Test
2266     @SmallTest
2267     @RequiresMinApi(6) // setThreadPriority added in API 6: crrev.com/472449
2268     @IgnoreFor(
2269             implementations = {CronetImplementation.AOSP_PLATFORM},
2270             reason = "ThreadPriority is not available in AOSP")
testCronetEngineThreadPriority()2271     public void testCronetEngineThreadPriority() throws Exception {
2272         ExperimentalCronetEngine.Builder builder =
2273                 mTestRule
2274                         .getTestFramework()
2275                         .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext());
2276         // Try out of bounds thread priorities.
2277         IllegalArgumentException e =
2278                 assertThrows(IllegalArgumentException.class, () -> builder.setThreadPriority(-21));
2279         assertThat(e).hasMessageThat().isEqualTo("Thread priority invalid");
2280 
2281         e = assertThrows(IllegalArgumentException.class, () -> builder.setThreadPriority(20));
2282         assertThat(e).hasMessageThat().isEqualTo("Thread priority invalid");
2283 
2284         // Test that valid thread priority range (-20..19) is working.
2285         for (int threadPriority = -20; threadPriority < 20; threadPriority++) {
2286             builder.setThreadPriority(threadPriority);
2287             CronetEngine engine = builder.build();
2288             try {
2289                 assertThat(getThreadPriority(engine)).isEqualTo(threadPriority);
2290             } finally {
2291                 engine.shutdown();
2292             }
2293         }
2294     }
2295 
2296     @NativeMethods("cronet_tests")
2297     interface Natives {
2298         // Verifies that CronetEngine.Builder config from testCronetEngineBuilderConfig() is
2299         // properly translated to a native UrlRequestContextConfig.
verifyUrlRequestContextConfig(long config, String storagePath)2300         void verifyUrlRequestContextConfig(long config, String storagePath);
2301 
2302         // Verifies that CronetEngine.Builder config from testCronetEngineQuicOffConfig() is
2303         // properly translated to a native UrlRequestContextConfig and QUIC is turned off.
verifyUrlRequestContextQuicOffConfig(long config, String storagePath)2304         void verifyUrlRequestContextQuicOffConfig(long config, String storagePath);
2305     }
2306 }
2307