| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| package org.chromium.net; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| import static com.google.common.truth.TruthJUnit.assume; |
| |
| import static org.junit.Assert.assertThrows; |
| |
| import static org.chromium.net.CronetEngine.Builder.HTTP_CACHE_IN_MEMORY; |
| import static org.chromium.net.CronetTestRule.getTestStorage; |
| import static org.chromium.net.truth.UrlResponseInfoSubject.assertThat; |
| |
| import android.net.Network; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.ConditionVariable; |
| import android.os.Handler; |
| import android.os.Looper; |
| import android.os.Process; |
| |
| import androidx.test.ext.junit.runners.AndroidJUnit4; |
| import androidx.test.filters.SmallTest; |
| import com.android.testutils.SkipPresubmit; |
| |
| import org.jni_zero.JNINamespace; |
| import org.jni_zero.NativeMethods; |
| import org.json.JSONObject; |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import org.chromium.base.Log; |
| import org.chromium.base.PathUtils; |
| import org.chromium.base.test.util.DoNotBatch; |
| import org.chromium.net.CronetTestRule.CronetImplementation; |
| import org.chromium.net.CronetTestRule.DisableAutomaticNetLog; |
| import org.chromium.net.CronetTestRule.IgnoreFor; |
| import org.chromium.net.CronetTestRule.RequiresMinAndroidApi; |
| import org.chromium.net.CronetTestRule.RequiresMinApi; |
| import org.chromium.net.NetworkChangeNotifierAutoDetect.ConnectivityManagerDelegate; |
| import org.chromium.net.TestUrlRequestCallback.ResponseStep; |
| import org.chromium.net.httpflags.BaseFeature; |
| import org.chromium.net.httpflags.FlagValue; |
| import org.chromium.net.httpflags.Flags; |
| import org.chromium.net.impl.CronetExceptionImpl; |
| import org.chromium.net.impl.CronetLibraryLoader; |
| import org.chromium.net.impl.CronetManifest; |
| import org.chromium.net.impl.CronetManifestInterceptor; |
| import org.chromium.net.impl.CronetUrlRequestContext; |
| import org.chromium.net.impl.ImplVersion; |
| import org.chromium.net.impl.NativeCronetEngineBuilderImpl; |
| import org.chromium.net.impl.NetworkExceptionImpl; |
| |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileReader; |
| import java.net.URL; |
| import java.nio.ByteBuffer; |
| import java.util.Arrays; |
| import java.util.UUID; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.Executor; |
| import java.util.concurrent.FutureTask; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| /** Test CronetEngine. */ |
| @DoNotBatch(reason = "crbug/1459563") |
| @RunWith(AndroidJUnit4.class) |
| @JNINamespace("cronet") |
| public class CronetUrlRequestContextTest { |
| @Rule public final CronetTestRule mTestRule = CronetTestRule.withManualEngineStartup(); |
| |
| private static final String TAG = "CronetUrlReqCtxTest"; |
| // URLs used for tests. |
| private static final String MOCK_CRONET_TEST_FAILED_URL = "http://mock.failed.request/-2"; |
| private static final String MOCK_CRONET_TEST_SUCCESS_URL = "http://mock.http/success.txt"; |
| private static final int MAX_FILE_SIZE = 1000000000; |
| |
| private String mUrl; |
| private String mUrl404; |
| private String mUrl500; |
| |
| @Before |
| public void setUp() throws Exception { |
| NativeTestServer.startNativeTestServer(mTestRule.getTestFramework().getContext()); |
| mUrl = NativeTestServer.getSuccessURL(); |
| mUrl404 = NativeTestServer.getNotFoundURL(); |
| mUrl500 = NativeTestServer.getServerErrorURL(); |
| } |
| |
| @After |
| public void tearDown() throws Exception { |
| NativeTestServer.shutdownNativeTestServer(); |
| } |
| |
| class RequestThread extends Thread { |
| public TestUrlRequestCallback mCallback; |
| |
| final String mUrl; |
| final ConditionVariable mRunBlocker; |
| |
| public RequestThread(String url, ConditionVariable runBlocker) { |
| mUrl = url; |
| mRunBlocker = runBlocker; |
| } |
| |
| @Override |
| public void run() { |
| mRunBlocker.block(); |
| ExperimentalCronetEngine cronetEngine = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()) |
| .build(); |
| try { |
| mCallback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, mCallback, mCallback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| mCallback.blockForDone(); |
| } finally { |
| cronetEngine.shutdown(); |
| } |
| } |
| } |
| |
| /** Callback that shutdowns the request context when request has succeeded or failed. */ |
| static class ShutdownTestUrlRequestCallback extends TestUrlRequestCallback { |
| private final CronetEngine mCronetEngine; |
| private final ConditionVariable mCallbackCompletionBlock = new ConditionVariable(); |
| |
| ShutdownTestUrlRequestCallback(CronetEngine cronetEngine) { |
| mCronetEngine = cronetEngine; |
| } |
| |
| @Override |
| public void onSucceeded(UrlRequest request, UrlResponseInfo info) { |
| super.onSucceeded(request, info); |
| mCronetEngine.shutdown(); |
| mCallbackCompletionBlock.open(); |
| } |
| |
| @Override |
| public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) { |
| super.onFailed(request, info, error); |
| mCronetEngine.shutdown(); |
| mCallbackCompletionBlock.open(); |
| } |
| |
| // Wait for request completion callback. |
| void blockForCallbackToComplete() { |
| mCallbackCompletionBlock.block(); |
| } |
| } |
| |
| private void setReadHttpFlagsInManifest(boolean value) { |
| Bundle metaData = new Bundle(); |
| metaData.putBoolean(CronetManifest.READ_HTTP_FLAGS_META_DATA_KEY, value); |
| mTestRule.getTestFramework().interceptContext(new CronetManifestInterceptor(metaData)); |
| } |
| |
| private void setLogFlag(String marker, String appId, String minVersion) { |
| FlagValue.ConstrainedValue.Builder constrainedValueBuilder = |
| FlagValue.ConstrainedValue.newBuilder() |
| .setStringValue("Test log flag value " + marker); |
| if (appId != null) { |
| constrainedValueBuilder.setAppId(appId); |
| } |
| if (minVersion != null) { |
| constrainedValueBuilder.setMinVersion(minVersion); |
| } |
| mTestRule |
| .getTestFramework() |
| .setHttpFlags( |
| Flags.newBuilder() |
| .putFlags( |
| CronetLibraryLoader.LOG_FLAG_NAME, |
| FlagValue.newBuilder() |
| .addConstrainedValues(constrainedValueBuilder) |
| .build()) |
| .build()); |
| } |
| |
| private void runOneRequest() { |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| } |
| |
| private void runRequestWhileExpectingLog(String marker, boolean shouldBeLogged) |
| throws Exception { |
| try (LogcatCapture logcatSink = |
| new LogcatCapture( |
| Arrays.asList( |
| Log.normalizeTag(CronetLibraryLoader.TAG + ":I"), |
| Log.normalizeTag(TAG + ":I"), |
| "chromium:I"))) { |
| // Use the engine at least once to ensure we do not race against Cronet initialization. |
| runOneRequest(); |
| |
| String stopMarker = UUID.randomUUID().toString(); |
| Log.i(TAG, "%s --- ENGINE STARTED ---", stopMarker); |
| |
| if (shouldBeLogged) { |
| while (true) { |
| String line = logcatSink.readLine(); |
| assertThat(line).doesNotContain(stopMarker); |
| if (line.contains(marker)) break; |
| } |
| while (!logcatSink.readLine().contains(stopMarker)) {} |
| } else { |
| while (true) { |
| String line = logcatSink.readLine(); |
| assertThat(line).doesNotContain(marker); |
| if (line.contains(stopMarker)) break; |
| } |
| } |
| } |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = |
| "HTTP flags are only supported on native Cronet for now. " |
| + "crbug.com/1495401: Emulator image does not have HttpFlags code yet") |
| public void testHttpFlagsAreLoaded() throws Exception { |
| setReadHttpFlagsInManifest(true); |
| String marker = UUID.randomUUID().toString(); |
| setLogFlag(marker, /* appId= */ null, /* minVersion= */ null); |
| runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ true); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = |
| "HTTP flags are only supported on native Cronet for now. " |
| + "crbug.com/1495401: Emulator image does not have HttpFlags code yet") |
| public void testHttpFlagsAreNotLoadedIfDisabledInManifest() throws Exception { |
| setReadHttpFlagsInManifest(false); |
| String marker = UUID.randomUUID().toString(); |
| setLogFlag(marker, /* appId= */ null, /* minVersion= */ null); |
| runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ false); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = |
| "HTTP flags are only supported on native Cronet for now. " |
| + "crbug.com/1495401: Emulator image does not have HttpFlags code yet") |
| public void testHttpFlagsNotAppliedIfAppIdDoesntMatch() throws Exception { |
| setReadHttpFlagsInManifest(true); |
| String marker = UUID.randomUUID().toString(); |
| setLogFlag(marker, /* appId= */ "org.chromium.fake.app.id", /* minVersion= */ null); |
| runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ false); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = |
| "HTTP flags are only supported on native Cronet for now. " |
| + "crbug.com/1495401: Emulator image does not have HttpFlags code yet") |
| public void testHttpFlagsAppliedIfAppIdMatches() throws Exception { |
| setReadHttpFlagsInManifest(true); |
| String marker = UUID.randomUUID().toString(); |
| setLogFlag( |
| marker, |
| /* appId= */ mTestRule.getTestFramework().getContext().getPackageName(), |
| /* minVersion= */ ImplVersion.getCronetVersion()); |
| runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ true); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = |
| "HTTP flags are only supported on native Cronet for now. " |
| + "crbug.com/1495401: Emulator image does not have HttpFlags code yet") |
| public void testHttpFlagsNotAppliedIfBelowMinVersion() throws Exception { |
| setReadHttpFlagsInManifest(true); |
| String marker = UUID.randomUUID().toString(); |
| setLogFlag(marker, /* appId= */ null, /* minVersion= */ "999999.0.0.0"); |
| runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ false); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = |
| "HTTP flags are only supported on native Cronet for now. " |
| + "crbug.com/1495401: Emulator image does not have HttpFlags code yet") |
| public void testHttpFlagsAppliedIfAtMinVersion() throws Exception { |
| setReadHttpFlagsInManifest(true); |
| String marker = UUID.randomUUID().toString(); |
| setLogFlag(marker, /* appId= */ null, /* minVersion= */ ImplVersion.getCronetVersion()); |
| runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ true); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = |
| "HTTP flags are only supported on native Cronet for now. " |
| + "crbug.com/1495401: Emulator image does not have HttpFlags code yet") |
| public void testHttpFlagsAppliedIfAboveMinVersion() throws Exception { |
| setReadHttpFlagsInManifest(true); |
| String marker = UUID.randomUUID().toString(); |
| setLogFlag(marker, /* appId= */ null, /* minVersion= */ "100.0.0.0"); |
| runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ true); |
| } |
| |
| private void setChromiumBaseFeatureLogFlag(boolean enable, String marker) { |
| var flags = |
| Flags.newBuilder() |
| .putFlags( |
| BaseFeature.FLAG_PREFIX + "CronetLogMe", |
| FlagValue.newBuilder() |
| .addConstrainedValues( |
| FlagValue.ConstrainedValue.newBuilder() |
| .setBoolValue(enable)) |
| .build()) |
| .putFlags( |
| BaseFeature.FLAG_PREFIX |
| + "CronetLogMe" |
| + BaseFeature.PARAM_DELIMITER |
| + "message", |
| FlagValue.newBuilder() |
| .addConstrainedValues( |
| FlagValue.ConstrainedValue.newBuilder() |
| .setStringValue(marker)) |
| .build()) |
| .build(); |
| mTestRule.getTestFramework().setHttpFlags(flags); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = |
| "HTTP flags are only supported on native Cronet for now. " |
| + "crbug.com/1495401: Emulator image does not have HttpFlags code yet") |
| public void testBaseFeatureFlagsOverridesEnabled() throws Exception { |
| setReadHttpFlagsInManifest(true); |
| String marker = UUID.randomUUID().toString(); |
| setChromiumBaseFeatureLogFlag(true, marker); |
| runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ true); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "HTTP flags are only supported on native Cronet for now") |
| public void testBaseFeatureFlagsOverridesDisabled() throws Exception { |
| setReadHttpFlagsInManifest(true); |
| String marker = UUID.randomUUID().toString(); |
| setChromiumBaseFeatureLogFlag(false, marker); |
| runRequestWhileExpectingLog(marker, /* shouldBeLogged= */ false); |
| } |
| |
| @Test |
| @SmallTest |
| @SuppressWarnings("deprecation") |
| public void testConfigUserAgent() throws Exception { |
| String userAgentName = "User-Agent"; |
| String userAgentValue = "User-Agent-Value"; |
| |
| mTestRule |
| .getTestFramework() |
| .applyEngineBuilderPatch((builder) -> builder.setUserAgent(userAgentValue)); |
| |
| CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| NativeTestServer.shutdownNativeTestServer(); // startNativeTestServer returns false if it's |
| // already running |
| assertThat( |
| NativeTestServer.startNativeTestServer( |
| mTestRule.getTestFramework().getContext())) |
| .isTrue(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder( |
| NativeTestServer.getEchoHeaderURL(userAgentName), |
| callback, |
| callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| assertThat(callback.mResponseAsString).isEqualTo(userAgentValue); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "Fallback implementation does not check for outstanding requests") |
| public void testShutdown() throws Exception { |
| CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| ShutdownTestUrlRequestCallback callback = new ShutdownTestUrlRequestCallback(cronetEngine); |
| // Block callback when response starts to verify that shutdown fails |
| // if there are active requests. |
| callback.setAutoAdvance(false); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| UrlRequest urlRequest = urlRequestBuilder.build(); |
| urlRequest.start(); |
| |
| Exception e = assertThrows(Exception.class, cronetEngine::shutdown); |
| assertThat(e).hasMessageThat().matches("Cannot shutdown with (running|active) requests."); |
| |
| callback.waitForNextStep(); |
| assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED); |
| |
| e = assertThrows(Exception.class, cronetEngine::shutdown); |
| assertThat(e).hasMessageThat().matches("Cannot shutdown with (running|active) requests."); |
| |
| callback.startNextRead(urlRequest); |
| |
| callback.waitForNextStep(); |
| |
| assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_READ_COMPLETED); |
| e = assertThrows(Exception.class, cronetEngine::shutdown); |
| assertThat(e).hasMessageThat().matches("Cannot shutdown with (running|active) requests."); |
| |
| // May not have read all the data, in theory. Just enable auto-advance |
| // and finish the request. |
| callback.setAutoAdvance(true); |
| callback.startNextRead(urlRequest); |
| callback.blockForDone(); |
| callback.blockForCallbackToComplete(); |
| callback.shutdownExecutor(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "Tests native implementation internals") |
| public void testShutdownDuringInit() throws Exception { |
| final ConditionVariable block = new ConditionVariable(false); |
| |
| // Post a task to main thread to block until shutdown is called to test |
| // scenario when shutdown is called right after construction before |
| // context is fully initialized on the main thread. |
| Runnable blockingTask = |
| new Runnable() { |
| @Override |
| public void run() { |
| block.block(); |
| } |
| }; |
| // Ensure that test is not running on the main thread. |
| assertThat(Looper.getMainLooper()).isNotEqualTo(Looper.myLooper()); |
| new Handler(Looper.getMainLooper()).post(blockingTask); |
| |
| // Create new request context, but its initialization on the main thread |
| // will be stuck behind blockingTask. |
| CronetUrlRequestContext cronetEngine = |
| (CronetUrlRequestContext) |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder( |
| mTestRule.getTestFramework().getContext()) |
| .build(); |
| // Unblock the main thread, so context gets initialized and shutdown on |
| // it. |
| block.open(); |
| // Shutdown will wait for init to complete on main thread. |
| cronetEngine.shutdown(); |
| // Verify that context is shutdown. |
| Exception e = assertThrows(Exception.class, cronetEngine::getUrlRequestContextAdapter); |
| assertThat(e).hasMessageThat().isEqualTo("Engine is shut down."); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "Tests native implementation internals") |
| public void testInitAndShutdownOnMainThread() throws Exception { |
| final ConditionVariable block = new ConditionVariable(false); |
| |
| // Post a task to main thread to init and shutdown on the main thread. |
| Runnable blockingTask = |
| () -> { |
| // Create new request context, loading the library. |
| final CronetUrlRequestContext cronetEngine = |
| (CronetUrlRequestContext) |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder( |
| mTestRule.getTestFramework().getContext()) |
| .build(); |
| // Shutdown right after init. |
| cronetEngine.shutdown(); |
| // Verify that context is shutdown. |
| Exception e = |
| assertThrows( |
| Exception.class, cronetEngine::getUrlRequestContextAdapter); |
| assertThat(e).hasMessageThat().isEqualTo("Engine is shut down."); |
| block.open(); |
| }; |
| new Handler(Looper.getMainLooper()).post(blockingTask); |
| // Wait for shutdown to complete on main thread. |
| block.block(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "JavaCronetEngine doesn't support throwing on repeat shutdown()") |
| public void testMultipleShutdown() throws Exception { |
| CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| cronetEngine.shutdown(); |
| Exception e = assertThrows(Exception.class, cronetEngine::shutdown); |
| assertThat(e).hasMessageThat().isEqualTo("Engine is shut down."); |
| } |
| |
| @Test |
| @SmallTest |
| public void testShutdownAfterError() throws Exception { |
| CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| ShutdownTestUrlRequestCallback callback = new ShutdownTestUrlRequestCallback(cronetEngine); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder( |
| MOCK_CRONET_TEST_FAILED_URL, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| assertThat(callback.mOnErrorCalled).isTrue(); |
| callback.blockForCallbackToComplete(); |
| callback.shutdownExecutor(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "JavaCronetEngine doesn't support throwing on shutdown()") |
| public void testShutdownAfterCancel() throws Exception { |
| CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| // Block callback when response starts to verify that shutdown fails |
| // if there are active requests. |
| callback.setAutoAdvance(false); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| UrlRequest urlRequest = urlRequestBuilder.build(); |
| urlRequest.start(); |
| |
| Exception e = assertThrows(Exception.class, cronetEngine::shutdown); |
| assertThat(e).hasMessageThat().matches("Cannot shutdown with (running|active) requests."); |
| |
| callback.waitForNextStep(); |
| assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED); |
| urlRequest.cancel(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "Tests native implementation internals") |
| @RequiresMinAndroidApi(Build.VERSION_CODES.M) // Multi-network API is supported from Marshmallow |
| public void testNetworkBoundContextLifetime() throws Exception { |
| // Multi-network API is available starting from Android Lollipop. |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| ConnectivityManagerDelegate delegate = |
| new ConnectivityManagerDelegate(mTestRule.getTestFramework().getContext()); |
| Network defaultNetwork = delegate.getDefaultNetwork(); |
| assume().that(defaultNetwork).isNotNull(); |
| |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| // Allows to check the underlying network-bound context state while the request is in |
| // progress. |
| callback.setAutoAdvance(false); |
| |
| ExperimentalUrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.bindToNetwork(defaultNetwork.getNetworkHandle()); |
| UrlRequest urlRequest = urlRequestBuilder.build(); |
| assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isFalse(); |
| urlRequest.start(); |
| assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isTrue(); |
| |
| // Resume callback execution. |
| callback.waitForNextStep(); |
| assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED); |
| callback.setAutoAdvance(true); |
| callback.startNextRead(urlRequest); |
| callback.blockForDone(); |
| assertThat(callback.mError).isNull(); |
| |
| // The default network should still be active, hence the underlying network-bound context |
| // should still be there. |
| assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isTrue(); |
| |
| // Fake disconnect event for the default network, this should destroy the underlying |
| // network-bound context. |
| FutureTask<Void> task = |
| new FutureTask<Void>( |
| new Callable<Void>() { |
| @Override |
| public Void call() { |
| NetworkChangeNotifier.fakeNetworkDisconnected( |
| defaultNetwork.getNetworkHandle()); |
| return null; |
| } |
| }); |
| CronetLibraryLoader.postToInitThread(task); |
| task.get(); |
| assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "Tests native implementation internals") |
| @RequiresMinAndroidApi(Build.VERSION_CODES.M) // Multi-network API is supported from Marshmallow |
| public void testNetworkBoundRequestCancel() throws Exception { |
| // Upon a network disconnection, NCN posts a tasks onto the network thread that calls |
| // CronetContext::NetworkTasks::OnNetworkDisconnected. |
| // Calling urlRequest.cancel() also, after some hoops, ends up in a posted tasks onto the |
| // network thread that calls CronetURLRequest::NetworkTasks::Destroy. |
| // Depending on their implementation this can lead to UAF, this test is here to prevent that |
| // from being introduced in the future. |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| callback.setAutoAdvance(false); |
| ExperimentalUrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| ConnectivityManagerDelegate delegate = |
| new ConnectivityManagerDelegate(mTestRule.getTestFramework().getContext()); |
| Network defaultNetwork = delegate.getDefaultNetwork(); |
| assume().that(defaultNetwork).isNotNull(); |
| |
| urlRequestBuilder.bindToNetwork(defaultNetwork.getNetworkHandle()); |
| UrlRequest urlRequest = urlRequestBuilder.build(); |
| |
| assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isFalse(); |
| urlRequest.start(); |
| assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isTrue(); |
| |
| callback.waitForNextStep(); |
| assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_RESPONSE_STARTED); |
| assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isTrue(); |
| // Cronet registers for NCN notifications on the init thread (see |
| // CronetLibraryLoader#ensureInitializedOnInitThread), hence we need to trigger fake |
| // notifications from there. |
| CronetLibraryLoader.postToInitThread( |
| new Runnable() { |
| @Override |
| public void run() { |
| NetworkChangeNotifier.fakeNetworkDisconnected( |
| defaultNetwork.getNetworkHandle()); |
| // Queue cancel after disconnect event. |
| urlRequest.cancel(); |
| } |
| }); |
| // Wait until the cancel call propagates (this would block undefinitely without that since |
| // we previously set auto advance to false). |
| callback.blockForDone(); |
| // mError should be null due to urlRequest.cancel(). |
| assertThat(callback.mError).isNull(); |
| // urlRequest.cancel(); should destroy the underlying network bound context. |
| assertThat(ApiHelper.doesContextExistForNetwork(cronetEngine, defaultNetwork)).isFalse(); |
| } |
| |
| @Test |
| @RequiresMinAndroidApi(Build.VERSION_CODES.M) |
| public void testBindToInvalidNetworkFails() { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| if (mTestRule.implementationUnderTest() == CronetImplementation.AOSP_PLATFORM) { |
| // HttpEngine#bindToNetwork requires an android.net.Network object. So, in this case, it |
| // will be the wrapper layer that will fail to translate that to a Network, not |
| // something in net's code. Hence, the failure will manifest itself at bind time, not at |
| // request execution time. |
| // Note: this will never happen in prod, as translation failure can only happen if we're |
| // given a fake networkHandle. |
| assertThrows( |
| IllegalArgumentException.class, |
| () -> cronetEngine.bindToNetwork(-150 /* invalid network handle */)); |
| return; |
| } |
| |
| cronetEngine.bindToNetwork(-150 /* invalid network handle */); |
| ExperimentalUrlRequest.Builder builder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| builder.build().start(); |
| callback.blockForDone(); |
| |
| assertThat(callback.mError).isNotNull(); |
| if (mTestRule.implementationUnderTest() == CronetImplementation.FALLBACK) { |
| assertThat(callback.mError).isInstanceOf(CronetExceptionImpl.class); |
| assertThat(callback.mError).hasCauseThat().isInstanceOf(NetworkExceptionImpl.class); |
| } else { |
| assertThat(callback.mError).isInstanceOf(NetworkExceptionImpl.class); |
| } |
| } |
| |
| @Test |
| @RequiresMinAndroidApi(Build.VERSION_CODES.M) |
| public void testBindToDefaultNetworkSucceeds() { |
| ConnectivityManagerDelegate delegate = |
| new ConnectivityManagerDelegate(mTestRule.getTestFramework().getContext()); |
| Network defaultNetwork = delegate.getDefaultNetwork(); |
| assume().that(defaultNetwork).isNotNull(); |
| |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| cronetEngine.bindToNetwork(defaultNetwork.getNetworkHandle()); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| ExperimentalUrlRequest.Builder builder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| builder.build().start(); |
| callback.blockForDone(); |
| |
| assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| public void testNetLog() throws Exception { |
| File directory = new File(PathUtils.getDataDirectory()); |
| File file = File.createTempFile("cronet", "json", directory); |
| CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| // Start NetLog immediately after the request context is created to make |
| // sure that the call won't crash the app even when the native request |
| // context is not fully initialized. See crbug.com/470196. |
| cronetEngine.startNetLogToFile(file.getPath(), false); |
| |
| // Start a request. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| cronetEngine.stopNetLog(); |
| assertThat(file.exists()).isTrue(); |
| assertThat(file.length()).isNotEqualTo(0); |
| assertThat(hasBytesInNetLog(file)).isFalse(); |
| assertThat(file.delete()).isTrue(); |
| assertThat(file.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| public void testBoundedFileNetLog() throws Exception { |
| File directory = new File(PathUtils.getDataDirectory()); |
| File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis()); |
| assertThat(netLogDir.exists()).isFalse(); |
| assertThat(netLogDir.mkdir()).isTrue(); |
| File logFile = new File(netLogDir, "netlog.json"); |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| // Start NetLog immediately after the request context is created to make |
| // sure that the call won't crash the app even when the native request |
| // context is not fully initialized. See crbug.com/470196. |
| cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE); |
| |
| // Start a request. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| cronetEngine.stopNetLog(); |
| assertThat(logFile.exists()).isTrue(); |
| assertThat(logFile.length()).isNotEqualTo(0); |
| assertThat(hasBytesInNetLog(logFile)).isFalse(); |
| FileUtils.recursivelyDeleteFile(netLogDir); |
| assertThat(netLogDir.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| // Tests that if stopNetLog is not explicitly called, CronetEngine.shutdown() |
| // will take care of it. crbug.com/623701. |
| public void testNoStopNetLog() throws Exception { |
| File directory = new File(PathUtils.getDataDirectory()); |
| File file = File.createTempFile("cronet", "json", directory); |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| cronetEngine.startNetLogToFile(file.getPath(), false); |
| |
| // Start a request. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| // Shut down the engine without calling stopNetLog. |
| cronetEngine.shutdown(); |
| assertThat(file.exists()).isTrue(); |
| assertThat(file.length()).isNotEqualTo(0); |
| assertThat(hasBytesInNetLog(file)).isFalse(); |
| assertThat(file.delete()).isTrue(); |
| assertThat(file.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| // Tests that if stopNetLog is not explicitly called, CronetEngine.shutdown() |
| // will take care of it. crbug.com/623701. |
| public void testNoStopBoundedFileNetLog() throws Exception { |
| File directory = new File(PathUtils.getDataDirectory()); |
| File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis()); |
| assertThat(netLogDir.exists()).isFalse(); |
| assertThat(netLogDir.mkdir()).isTrue(); |
| File logFile = new File(netLogDir, "netlog.json"); |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE); |
| |
| // Start a request. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| // Shut down the engine without calling stopNetLog. |
| cronetEngine.shutdown(); |
| assertThat(logFile.exists()).isTrue(); |
| assertThat(logFile.length()).isNotEqualTo(0); |
| |
| FileUtils.recursivelyDeleteFile(netLogDir); |
| assertThat(netLogDir.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @SkipPresubmit(reason = "b/293141085 flaky test") |
| @IgnoreFor( |
| implementations = {CronetImplementation.AOSP_PLATFORM}, |
| reason = "ActiveRequestCount is not available in AOSP") |
| public void testGetActiveRequestCount() throws Exception { |
| CronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback1 = new TestUrlRequestCallback(); |
| TestUrlRequestCallback callback2 = new TestUrlRequestCallback(); |
| callback1.setAutoAdvance(false); |
| callback2.setAutoAdvance(false); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| UrlRequest request1 = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback1, callback1.getExecutor()).build(); |
| UrlRequest request2 = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback2, callback2.getExecutor()).build(); |
| request1.start(); |
| request2.start(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(2); |
| callback1.waitForNextStep(); |
| callback1.setAutoAdvance(true); |
| callback1.startNextRead(request1); |
| callback1.blockForDone(); |
| waitForActiveRequestCount(cronetEngine, 1); |
| callback2.waitForNextStep(); |
| callback2.setAutoAdvance(true); |
| callback2.startNextRead(request2); |
| callback2.blockForDone(); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.AOSP_PLATFORM}, |
| reason = "ActiveRequestCount is not available in AOSP") |
| public void testGetActiveRequestCountOnReachingSucceeded() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| callback.setAutoAdvance(false); |
| callback.setBlockOnTerminalState(true); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| UrlRequest request = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build(); |
| request.start(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| callback.waitForNextStep(); |
| callback.startNextRead(request); |
| callback.waitForNextStep(); |
| callback.startNextRead(request); |
| callback.blockForDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| callback.setBlockOnTerminalState(false); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.AOSP_PLATFORM}, |
| reason = "ActiveRequestCount is not available in AOSP") |
| public void testGetActiveRequestCountOnReachingCancel() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| callback.setAutoAdvance(false); |
| callback.setBlockOnTerminalState(true); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| UrlRequest request = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build(); |
| request.start(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| request.cancel(); |
| callback.blockForDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| callback.setBlockOnTerminalState(false); |
| assertThat(callback.mOnCanceledCalled).isTrue(); |
| assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_CANCELED); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.AOSP_PLATFORM}, |
| reason = "ActiveRequestCount is not available in AOSP") |
| public void testGetActiveRequestCountOnReachingFail() throws Exception { |
| final String badUrl = "www.unreachable-url.com"; |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| callback.setAutoAdvance(false); |
| callback.setBlockOnTerminalState(true); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| UrlRequest request = |
| cronetEngine.newUrlRequestBuilder(badUrl, callback, callback.getExecutor()).build(); |
| request.start(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| callback.blockForDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| callback.setBlockOnTerminalState(false); |
| assertThat(callback.mOnErrorCalled).isTrue(); |
| assertThat(callback.mResponseStep).isEqualTo(ResponseStep.ON_FAILED); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = |
| "crbug.com/1494901: Broken for JavaCronetEngine. " |
| + "ActiveRequestCount is not available in AOSP") |
| public void testGetActiveRequestCountOnDoubleStart() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| callback.setAutoAdvance(false); |
| UrlRequest request = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| request.start(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| assertThrows(Exception.class, request::start); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| callback.setAutoAdvance(true); |
| callback.blockForDone(); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = |
| "JavaCronetEngine currently never throws directly from start. " |
| + "ActiveRequestCount is not available in AOSP") |
| public void testGetActiveRequestCountOnInvalidRequest() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest request = |
| cronetEngine |
| .newUrlRequestBuilder("", callback, callback.getExecutor()) |
| .setHttpMethod("") |
| .build(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| assertThrows(Exception.class, request::start); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.AOSP_PLATFORM}, |
| reason = "ActiveRequestCount is not available in AOSP") |
| public void testGetActiveRequestCountWithCancel() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback1 = new TestUrlRequestCallback(); |
| TestUrlRequestCallback callback2 = new TestUrlRequestCallback(); |
| callback1.setAutoAdvance(false); |
| callback2.setAutoAdvance(false); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| UrlRequest request1 = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback1, callback1.getExecutor()).build(); |
| UrlRequest request2 = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback2, callback2.getExecutor()).build(); |
| request1.start(); |
| request2.start(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(2); |
| request1.cancel(); |
| callback1.blockForDone(); |
| assertThat(callback1.mOnCanceledCalled).isTrue(); |
| assertThat(callback1.mResponseStep).isEqualTo(ResponseStep.ON_CANCELED); |
| waitForActiveRequestCount(cronetEngine, 1); |
| callback2.waitForNextStep(); |
| callback2.setAutoAdvance(true); |
| callback2.startNextRead(request2); |
| callback2.blockForDone(); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @SkipPresubmit(reason = "b/293141085 flaky test") |
| @IgnoreFor( |
| implementations = {CronetImplementation.AOSP_PLATFORM}, |
| reason = "ActiveRequestCount is not available in AOSP") |
| public void testGetActiveRequestCountWithError() throws Exception { |
| final String badUrl = "www.unreachable-url.com"; |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback1 = new TestUrlRequestCallback(); |
| TestUrlRequestCallback callback2 = new TestUrlRequestCallback(); |
| callback1.setAutoAdvance(false); |
| callback1.setBlockOnTerminalState(true); |
| callback2.setAutoAdvance(false); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| UrlRequest request1 = |
| cronetEngine |
| .newUrlRequestBuilder(badUrl, callback1, callback1.getExecutor()) |
| .build(); |
| UrlRequest request2 = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback2, callback2.getExecutor()).build(); |
| request1.start(); |
| request2.start(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(2); |
| callback1.setBlockOnTerminalState(false); |
| callback1.blockForDone(); |
| assertThat(callback1.mOnErrorCalled).isTrue(); |
| assertThat(callback1.mResponseStep).isEqualTo(ResponseStep.ON_FAILED); |
| waitForActiveRequestCount(cronetEngine, 1); |
| callback2.waitForNextStep(); |
| callback2.setAutoAdvance(true); |
| callback2.startNextRead(request2); |
| callback2.blockForDone(); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "Request finished listeners are only supported by native Cronet") |
| public void testGetActiveRequestCountOnRequestFinishedListener() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener(); |
| requestFinishedListener.blockListener(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest request = |
| cronetEngine |
| .newUrlRequestBuilder(mUrl, callback, callback.getExecutor()) |
| .setRequestFinishedListener(requestFinishedListener) |
| .build(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| request.start(); |
| callback.blockForDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| requestFinishedListener.blockUntilDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| requestFinishedListener.unblockListener(); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "Request finished listeners are only supported by native Cronet") |
| public void testGetActiveRequestCountOnThrowingRequestFinishedListener() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener(); |
| requestFinishedListener.makeListenerThrow(); |
| requestFinishedListener.blockListener(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest request = |
| cronetEngine |
| .newUrlRequestBuilder(mUrl, callback, callback.getExecutor()) |
| .setRequestFinishedListener(requestFinishedListener) |
| .build(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| request.start(); |
| callback.blockForDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| requestFinishedListener.blockUntilDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| requestFinishedListener.unblockListener(); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "Request finished listeners are only supported by native Cronet") |
| public void testGetActiveRequestCountOnThrowingEngineRequestFinishedListener() |
| throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener(); |
| requestFinishedListener.makeListenerThrow(); |
| requestFinishedListener.blockListener(); |
| cronetEngine.addRequestFinishedListener(requestFinishedListener); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest request = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| request.start(); |
| callback.blockForDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| requestFinishedListener.blockUntilDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| requestFinishedListener.unblockListener(); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "Request finished listeners are only supported by native Cronet") |
| public void testGetActiveRequestCountOnEngineRequestFinishedListener() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestRequestFinishedListener requestFinishedListener = new TestRequestFinishedListener(); |
| requestFinishedListener.blockListener(); |
| cronetEngine.addRequestFinishedListener(requestFinishedListener); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest request = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()).build(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(0); |
| request.start(); |
| callback.blockForDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| requestFinishedListener.blockUntilDone(); |
| assertThat(cronetEngine.getActiveRequestCount()).isEqualTo(1); |
| requestFinishedListener.unblockListener(); |
| waitForActiveRequestCount(cronetEngine, 0); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| // Tests that NetLog contains events emitted by all live CronetEngines. |
| public void testNetLogContainEventsFromAllLiveEngines() throws Exception { |
| File directory = new File(PathUtils.getDataDirectory()); |
| File file1 = File.createTempFile("cronet1", "json", directory); |
| File file2 = File.createTempFile("cronet2", "json", directory); |
| CronetEngine cronetEngine1 = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()) |
| .build(); |
| CronetEngine cronetEngine2 = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()) |
| .build(); |
| |
| cronetEngine1.startNetLogToFile(file1.getPath(), false); |
| cronetEngine2.startNetLogToFile(file2.getPath(), false); |
| |
| // Warm CronetEngine and make sure both CronetUrlRequestContexts are |
| // initialized before testing the logs. |
| makeRequestAndCheckStatus(cronetEngine1, mUrl, 200); |
| makeRequestAndCheckStatus(cronetEngine2, mUrl, 200); |
| |
| // Use cronetEngine1 to make a request to mUrl404. |
| makeRequestAndCheckStatus(cronetEngine1, mUrl404, 404); |
| |
| // Use cronetEngine2 to make a request to mUrl500. |
| makeRequestAndCheckStatus(cronetEngine2, mUrl500, 500); |
| |
| cronetEngine1.stopNetLog(); |
| cronetEngine2.stopNetLog(); |
| assertThat(file1.exists()).isTrue(); |
| assertThat(file2.exists()).isTrue(); |
| // Make sure both files contain the two requests made separately using |
| // different engines. |
| assertThat(containsStringInNetLog(file1, mUrl404)).isTrue(); |
| assertThat(containsStringInNetLog(file1, mUrl500)).isTrue(); |
| assertThat(containsStringInNetLog(file2, mUrl404)).isTrue(); |
| assertThat(containsStringInNetLog(file2, mUrl500)).isTrue(); |
| assertThat(file1.delete()).isTrue(); |
| assertThat(file2.delete()).isTrue(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| // Tests that NetLog contains events emitted by all live CronetEngines. |
| public void testBoundedFileNetLogContainEventsFromAllLiveEngines() throws Exception { |
| File directory = new File(PathUtils.getDataDirectory()); |
| File netLogDir1 = new File(directory, "NetLog1" + System.currentTimeMillis()); |
| assertThat(netLogDir1.exists()).isFalse(); |
| assertThat(netLogDir1.mkdir()).isTrue(); |
| File netLogDir2 = new File(directory, "NetLog2" + System.currentTimeMillis()); |
| assertThat(netLogDir2.exists()).isFalse(); |
| assertThat(netLogDir2.mkdir()).isTrue(); |
| File logFile1 = new File(netLogDir1, "netlog.json"); |
| File logFile2 = new File(netLogDir2, "netlog.json"); |
| |
| CronetEngine cronetEngine1 = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()) |
| .build(); |
| CronetEngine cronetEngine2 = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()) |
| .build(); |
| |
| cronetEngine1.startNetLogToDisk(netLogDir1.getPath(), false, MAX_FILE_SIZE); |
| cronetEngine2.startNetLogToDisk(netLogDir2.getPath(), false, MAX_FILE_SIZE); |
| |
| // Warm CronetEngine and make sure both CronetUrlRequestContexts are |
| // initialized before testing the logs. |
| makeRequestAndCheckStatus(cronetEngine1, mUrl, 200); |
| makeRequestAndCheckStatus(cronetEngine2, mUrl, 200); |
| |
| // Use cronetEngine1 to make a request to mUrl404. |
| makeRequestAndCheckStatus(cronetEngine1, mUrl404, 404); |
| |
| // Use cronetEngine2 to make a request to mUrl500. |
| makeRequestAndCheckStatus(cronetEngine2, mUrl500, 500); |
| |
| cronetEngine1.stopNetLog(); |
| cronetEngine2.stopNetLog(); |
| |
| assertThat(logFile1.exists()).isTrue(); |
| assertThat(logFile2.exists()).isTrue(); |
| assertThat(logFile1.length()).isNotEqualTo(0); |
| assertThat(logFile2.length()).isNotEqualTo(0); |
| |
| // Make sure both files contain the two requests made separately using |
| // different engines. |
| assertThat(containsStringInNetLog(logFile1, mUrl404)).isTrue(); |
| assertThat(containsStringInNetLog(logFile1, mUrl500)).isTrue(); |
| assertThat(containsStringInNetLog(logFile2, mUrl404)).isTrue(); |
| assertThat(containsStringInNetLog(logFile2, mUrl500)).isTrue(); |
| |
| FileUtils.recursivelyDeleteFile(netLogDir1); |
| assertThat(netLogDir1.exists()).isFalse(); |
| FileUtils.recursivelyDeleteFile(netLogDir2); |
| assertThat(netLogDir2.exists()).isFalse(); |
| } |
| |
| private CronetEngine createCronetEngineWithCache(int cacheType) { |
| CronetEngine.Builder builder = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()); |
| if (cacheType == CronetEngine.Builder.HTTP_CACHE_DISK |
| || cacheType == CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP) { |
| builder.setStoragePath(getTestStorage(mTestRule.getTestFramework().getContext())); |
| } |
| builder.enableHttpCache(cacheType, 100 * 1024); |
| // Don't check the return value here, because startNativeTestServer() returns false when the |
| // NativeTestServer is already running and this method needs to be called twice without |
| // shutting down the NativeTestServer in between. |
| NativeTestServer.startNativeTestServer(mTestRule.getTestFramework().getContext()); |
| return builder.build(); |
| } |
| |
| @Test |
| @SmallTest |
| @SkipPresubmit(reason = "b/293141085 flaky test") |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "Fallback implementation does not have a network thread.") |
| // Tests that if CronetEngine is shut down on the network thread, an appropriate exception |
| // is thrown. |
| public void testShutDownEngineOnNetworkThread() throws Exception { |
| final CronetEngine cronetEngine = |
| createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK); |
| String url = NativeTestServer.getFileURL("/cacheable.txt"); |
| // Make a request to a cacheable resource. |
| checkRequestCaching(cronetEngine, url, false); |
| |
| final AtomicReference<Throwable> thrown = new AtomicReference<>(); |
| // Shut down the server. |
| NativeTestServer.shutdownNativeTestServer(); |
| class CancelUrlRequestCallback extends TestUrlRequestCallback { |
| @Override |
| public void onResponseStarted(UrlRequest request, UrlResponseInfo info) { |
| super.onResponseStarted(request, info); |
| request.cancel(); |
| // Shut down CronetEngine immediately after request is destroyed. |
| try { |
| cronetEngine.shutdown(); |
| } catch (Exception e) { |
| thrown.set(e); |
| } |
| } |
| |
| @Override |
| public void onSucceeded(UrlRequest request, UrlResponseInfo info) { |
| // onSucceeded will not happen, because the request is canceled |
| // after sending first read and the executor is single threaded. |
| throw new AssertionError("Unexpected"); |
| } |
| |
| @Override |
| public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) { |
| throw new AssertionError("Unexpected"); |
| } |
| } |
| Executor directExecutor = |
| new Executor() { |
| @Override |
| public void execute(Runnable command) { |
| command.run(); |
| } |
| }; |
| CancelUrlRequestCallback callback = new CancelUrlRequestCallback(); |
| callback.setAllowDirectExecutor(true); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(url, callback, directExecutor); |
| urlRequestBuilder.allowDirectExecutor(); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| assertThat(thrown.get()).isInstanceOf(RuntimeException.class); |
| cronetEngine.shutdown(); |
| } |
| |
| @Test |
| @SmallTest |
| @SkipPresubmit(reason = "b/293141085 flaky test") |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "Fallback implementation has no support for caches") |
| // Tests that if CronetEngine is shut down when reading from disk cache, |
| // there isn't a crash. See crbug.com/486120. |
| public void testShutDownEngineWhenReadingFromDiskCache() throws Exception { |
| final CronetEngine cronetEngine = |
| createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK); |
| String url = NativeTestServer.getFileURL("/cacheable.txt"); |
| // Make a request to a cacheable resource. |
| checkRequestCaching(cronetEngine, url, false); |
| |
| // Shut down the server. |
| NativeTestServer.shutdownNativeTestServer(); |
| class CancelUrlRequestCallback extends TestUrlRequestCallback { |
| @Override |
| public void onResponseStarted(UrlRequest request, UrlResponseInfo info) { |
| super.onResponseStarted(request, info); |
| request.cancel(); |
| // Shut down CronetEngine immediately after request is destroyed. |
| cronetEngine.shutdown(); |
| } |
| |
| @Override |
| public void onSucceeded(UrlRequest request, UrlResponseInfo info) { |
| // onSucceeded will not happen, because the request is canceled |
| // after sending first read and the executor is single threaded. |
| throw new RuntimeException("Unexpected"); |
| } |
| |
| @Override |
| public void onFailed(UrlRequest request, UrlResponseInfo info, CronetException error) { |
| throw new RuntimeException("Unexpected"); |
| } |
| } |
| CancelUrlRequestCallback callback = new CancelUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(url, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200); |
| assertThat(callback.getResponseInfoWithChecks()).wasCached(); |
| assertThat(callback.mOnCanceledCalled).isTrue(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| public void testNetLogAfterShutdown() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| cronetEngine.shutdown(); |
| |
| File directory = new File(PathUtils.getDataDirectory()); |
| File file = File.createTempFile("cronet", "json", directory); |
| |
| Exception e = |
| assertThrows( |
| Exception.class, |
| () -> cronetEngine.startNetLogToFile(file.getPath(), false)); |
| assertThat(e).hasMessageThat().isEqualTo("Engine is shut down."); |
| assertThat(hasBytesInNetLog(file)).isFalse(); |
| assertThat(file.delete()).isTrue(); |
| assertThat(file.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| public void testBoundedFileNetLogAfterShutdown() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| cronetEngine.shutdown(); |
| |
| File directory = new File(PathUtils.getDataDirectory()); |
| File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis()); |
| assertThat(netLogDir.exists()).isFalse(); |
| assertThat(netLogDir.mkdir()).isTrue(); |
| File logFile = new File(netLogDir, "netlog.json"); |
| Exception e = |
| assertThrows( |
| Exception.class, |
| () -> |
| cronetEngine.startNetLogToDisk( |
| netLogDir.getPath(), false, MAX_FILE_SIZE)); |
| assertThat(e).hasMessageThat().isEqualTo("Engine is shut down."); |
| assertThat(logFile.exists()).isFalse(); |
| FileUtils.recursivelyDeleteFile(netLogDir); |
| assertThat(netLogDir.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| public void testNetLogStartMultipleTimes() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| File directory = new File(PathUtils.getDataDirectory()); |
| File file = File.createTempFile("cronet", "json", directory); |
| // Start NetLog multiple times. |
| cronetEngine.startNetLogToFile(file.getPath(), false); |
| cronetEngine.startNetLogToFile(file.getPath(), false); |
| cronetEngine.startNetLogToFile(file.getPath(), false); |
| cronetEngine.startNetLogToFile(file.getPath(), false); |
| // Start a request. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| cronetEngine.stopNetLog(); |
| assertThat(file.exists()).isTrue(); |
| assertThat(file.length()).isNotEqualTo(0); |
| assertThat(hasBytesInNetLog(file)).isFalse(); |
| assertThat(file.delete()).isTrue(); |
| assertThat(file.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| public void testBoundedFileNetLogStartMultipleTimes() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| File directory = new File(PathUtils.getDataDirectory()); |
| File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis()); |
| assertThat(netLogDir.exists()).isFalse(); |
| assertThat(netLogDir.mkdir()).isTrue(); |
| File logFile = new File(netLogDir, "netlog.json"); |
| // Start NetLog multiple times. This should be equivalent to starting NetLog |
| // once. Each subsequent start (without calling stopNetLog) should be a no-op. |
| cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE); |
| cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE); |
| cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE); |
| cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE); |
| // Start a request. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| cronetEngine.stopNetLog(); |
| assertThat(logFile.exists()).isTrue(); |
| assertThat(logFile.length()).isNotEqualTo(0); |
| assertThat(hasBytesInNetLog(logFile)).isFalse(); |
| FileUtils.recursivelyDeleteFile(netLogDir); |
| assertThat(netLogDir.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| public void testNetLogStopMultipleTimes() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| File directory = new File(PathUtils.getDataDirectory()); |
| File file = File.createTempFile("cronet", "json", directory); |
| cronetEngine.startNetLogToFile(file.getPath(), false); |
| // Start a request. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| // Stop NetLog multiple times. |
| cronetEngine.stopNetLog(); |
| cronetEngine.stopNetLog(); |
| cronetEngine.stopNetLog(); |
| cronetEngine.stopNetLog(); |
| cronetEngine.stopNetLog(); |
| assertThat(file.exists()).isTrue(); |
| assertThat(file.length()).isNotEqualTo(0); |
| assertThat(hasBytesInNetLog(file)).isFalse(); |
| assertThat(file.delete()).isTrue(); |
| assertThat(file.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| public void testBoundedFileNetLogStopMultipleTimes() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| File directory = new File(PathUtils.getDataDirectory()); |
| File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis()); |
| assertThat(netLogDir.exists()).isFalse(); |
| assertThat(netLogDir.mkdir()).isTrue(); |
| File logFile = new File(netLogDir, "netlog.json"); |
| cronetEngine.startNetLogToDisk(netLogDir.getPath(), false, MAX_FILE_SIZE); |
| // Start a request. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| // Stop NetLog multiple times. This should be equivalent to stopping NetLog once. |
| // Each subsequent stop (without calling startNetLogToDisk first) should be a no-op. |
| cronetEngine.stopNetLog(); |
| cronetEngine.stopNetLog(); |
| cronetEngine.stopNetLog(); |
| cronetEngine.stopNetLog(); |
| cronetEngine.stopNetLog(); |
| assertThat(logFile.exists()).isTrue(); |
| assertThat(logFile.length()).isNotEqualTo(0); |
| assertThat(hasBytesInNetLog(logFile)).isFalse(); |
| FileUtils.recursivelyDeleteFile(netLogDir); |
| assertThat(netLogDir.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| public void testNetLogWithBytes() throws Exception { |
| File directory = new File(PathUtils.getDataDirectory()); |
| File file = File.createTempFile("cronet", "json", directory); |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| // Start NetLog with logAll as true. |
| cronetEngine.startNetLogToFile(file.getPath(), true); |
| // Start a request. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| cronetEngine.stopNetLog(); |
| assertThat(file.exists()).isTrue(); |
| assertThat(file.length()).isNotEqualTo(0); |
| assertThat(hasBytesInNetLog(file)).isTrue(); |
| assertThat(file.delete()).isTrue(); |
| assertThat(file.exists()).isFalse(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "NetLog is supported only by the native implementation") |
| @DisableAutomaticNetLog(reason = "Test is targeting NetLog") |
| public void testBoundedFileNetLogWithBytes() throws Exception { |
| File directory = new File(PathUtils.getDataDirectory()); |
| File netLogDir = new File(directory, "NetLog" + System.currentTimeMillis()); |
| assertThat(netLogDir.exists()).isFalse(); |
| assertThat(netLogDir.mkdir()).isTrue(); |
| File logFile = new File(netLogDir, "netlog.json"); |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| // Start NetLog with logAll as true. |
| cronetEngine.startNetLogToDisk(netLogDir.getPath(), true, MAX_FILE_SIZE); |
| // Start a request. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| cronetEngine.stopNetLog(); |
| |
| assertThat(logFile.exists()).isTrue(); |
| assertThat(logFile.length()).isNotEqualTo(0); |
| assertThat(hasBytesInNetLog(logFile)).isTrue(); |
| FileUtils.recursivelyDeleteFile(netLogDir); |
| assertThat(netLogDir.exists()).isFalse(); |
| } |
| |
| private boolean hasBytesInNetLog(File logFile) throws Exception { |
| return containsStringInNetLog(logFile, "\"bytes\""); |
| } |
| |
| private boolean containsStringInNetLog(File logFile, String content) throws Exception { |
| BufferedReader logReader = new BufferedReader(new FileReader(logFile)); |
| try { |
| String logLine; |
| while ((logLine = logReader.readLine()) != null) { |
| if (logLine.contains(content)) { |
| return true; |
| } |
| } |
| return false; |
| } finally { |
| logReader.close(); |
| } |
| } |
| |
| /** |
| * Helper method to make a request to {@code url}, wait for it to complete, and check that the |
| * status code is the same as {@code expectedStatusCode}. |
| */ |
| private void makeRequestAndCheckStatus( |
| CronetEngine engine, String url, int expectedStatusCode) { |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest request = |
| engine.newUrlRequestBuilder(url, callback, callback.getExecutor()).build(); |
| request.start(); |
| callback.blockForDone(); |
| assertThat(callback.getResponseInfoWithChecks()) |
| .hasHttpStatusCodeThat() |
| .isEqualTo(expectedStatusCode); |
| } |
| |
| private void checkRequestCaching(CronetEngine engine, String url, boolean expectCached) { |
| checkRequestCaching(engine, url, expectCached, false); |
| } |
| |
| private void checkRequestCaching( |
| CronetEngine engine, String url, boolean expectCached, boolean disableCache) { |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| engine.newUrlRequestBuilder(url, callback, callback.getExecutor()); |
| if (disableCache) { |
| urlRequestBuilder.disableCache(); |
| } |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| assertThat(callback.getResponseInfoWithChecks().wasCached()).isEqualTo(expectCached); |
| assertThat(callback.mResponseAsString).isEqualTo("this is a cacheable file\n"); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "No caches support for fallback implementation") |
| public void testEnableHttpCacheDisabled() throws Exception { |
| CronetEngine cronetEngine = |
| createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISABLED); |
| String url = NativeTestServer.getFileURL("/cacheable.txt"); |
| checkRequestCaching(cronetEngine, url, false); |
| checkRequestCaching(cronetEngine, url, false); |
| checkRequestCaching(cronetEngine, url, false); |
| cronetEngine.shutdown(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "No caches support for fallback implementation") |
| public void testEnableHttpCacheInMemory() throws Exception { |
| CronetEngine cronetEngine = |
| createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_IN_MEMORY); |
| String url = NativeTestServer.getFileURL("/cacheable.txt"); |
| checkRequestCaching(cronetEngine, url, false); |
| checkRequestCaching(cronetEngine, url, true); |
| NativeTestServer.shutdownNativeTestServer(); |
| checkRequestCaching(cronetEngine, url, true); |
| cronetEngine.shutdown(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "No caches support for fallback implementation") |
| public void testEnableHttpCacheDisk() throws Exception { |
| CronetEngine cronetEngine = |
| createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK); |
| String url = NativeTestServer.getFileURL("/cacheable.txt"); |
| checkRequestCaching(cronetEngine, url, false); |
| checkRequestCaching(cronetEngine, url, true); |
| NativeTestServer.shutdownNativeTestServer(); |
| checkRequestCaching(cronetEngine, url, true); |
| cronetEngine.shutdown(); |
| } |
| |
| @Test |
| @SmallTest |
| @SkipPresubmit(reason = "b/293141085 flaky test") |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "No caches support for fallback implementation") |
| public void testNoConcurrentDiskUsage() throws Exception { |
| CronetEngine cronetEngine = |
| createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK); |
| |
| IllegalStateException e = |
| assertThrows( |
| IllegalStateException.class, |
| () -> createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK)); |
| assertThat(e).hasMessageThat().isEqualTo("Disk cache storage path already in use"); |
| |
| String url = NativeTestServer.getFileURL("/cacheable.txt"); |
| checkRequestCaching(cronetEngine, url, false); |
| checkRequestCaching(cronetEngine, url, true); |
| NativeTestServer.shutdownNativeTestServer(); |
| checkRequestCaching(cronetEngine, url, true); |
| cronetEngine.shutdown(); |
| } |
| |
| @Test |
| @SmallTest |
| @SkipPresubmit(reason = "b/293141085 flaky test") |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "No caches support for fallback implementation") |
| public void testEnableHttpCacheDiskNoHttp() throws Exception { |
| CronetEngine cronetEngine = |
| createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP); |
| String url = NativeTestServer.getFileURL("/cacheable.txt"); |
| checkRequestCaching(cronetEngine, url, false); |
| checkRequestCaching(cronetEngine, url, false); |
| checkRequestCaching(cronetEngine, url, false); |
| |
| // Make a new CronetEngine and try again to make sure the response didn't get cached on the |
| // first request. See https://crbug.com/743232. |
| cronetEngine.shutdown(); |
| cronetEngine = createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP); |
| checkRequestCaching(cronetEngine, url, false); |
| checkRequestCaching(cronetEngine, url, false); |
| checkRequestCaching(cronetEngine, url, false); |
| cronetEngine.shutdown(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "No caches support for fallback implementation") |
| public void testDisableCache() throws Exception { |
| CronetEngine cronetEngine = |
| createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK); |
| String url = NativeTestServer.getFileURL("/cacheable.txt"); |
| |
| // When cache is disabled, making a request does not write to the cache. |
| checkRequestCaching( |
| cronetEngine, url, false, true |
| /** disable cache */ |
| ); |
| checkRequestCaching(cronetEngine, url, false); |
| |
| // When cache is enabled, the second request is cached. |
| checkRequestCaching( |
| cronetEngine, url, false, true |
| /** disable cache */ |
| ); |
| checkRequestCaching(cronetEngine, url, true); |
| |
| // Shut down the server, next request should have a cached response. |
| NativeTestServer.shutdownNativeTestServer(); |
| checkRequestCaching(cronetEngine, url, true); |
| |
| // Cache is disabled after server is shut down, request should fail. |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(url, callback, callback.getExecutor()); |
| urlRequestBuilder.disableCache(); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| assertThat(callback.mError) |
| .hasMessageThat() |
| .contains("Exception in CronetUrlRequest: net::ERR_CONNECTION_REFUSED"); |
| cronetEngine.shutdown(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "No caches support for fallback implementation") |
| public void testEnableHttpCacheDiskNewEngine() throws Exception { |
| CronetEngine cronetEngine = |
| createCronetEngineWithCache(CronetEngine.Builder.HTTP_CACHE_DISK); |
| String url = NativeTestServer.getFileURL("/cacheable.txt"); |
| checkRequestCaching(cronetEngine, url, false); |
| checkRequestCaching(cronetEngine, url, true); |
| NativeTestServer.shutdownNativeTestServer(); |
| checkRequestCaching(cronetEngine, url, true); |
| |
| // Shutdown original context and create another that uses the same cache. |
| cronetEngine.shutdown(); |
| cronetEngine = |
| mTestRule |
| .getTestFramework() |
| .enableDiskCache( |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder( |
| mTestRule.getTestFramework().getContext())) |
| .build(); |
| checkRequestCaching(cronetEngine, url, true); |
| cronetEngine.shutdown(); |
| } |
| |
| @Test |
| @SmallTest |
| public void testInitEngineAndStartRequest() { |
| // Immediately make a request after initializing the engine. |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200); |
| } |
| |
| @Test |
| @SmallTest |
| public void testInitEngineStartTwoRequests() throws Exception { |
| // Make two requests after initializing the context. |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| int[] statusCodes = {0, 0}; |
| String[] urls = {mUrl, mUrl404}; |
| for (int i = 0; i < 2; i++) { |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder(urls[i], callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| statusCodes[i] = callback.getResponseInfoWithChecks().getHttpStatusCode(); |
| } |
| assertThat(statusCodes).asList().containsExactly(200, 404).inOrder(); |
| } |
| |
| @Test |
| @SmallTest |
| public void testInitTwoEnginesSimultaneously() throws Exception { |
| // Threads will block on runBlocker to ensure simultaneous execution. |
| ConditionVariable runBlocker = new ConditionVariable(false); |
| RequestThread thread1 = new RequestThread(mUrl, runBlocker); |
| RequestThread thread2 = new RequestThread(mUrl404, runBlocker); |
| |
| thread1.start(); |
| thread2.start(); |
| runBlocker.open(); |
| thread1.join(); |
| thread2.join(); |
| assertThat(thread1.mCallback.getResponseInfoWithChecks()) |
| .hasHttpStatusCodeThat() |
| .isEqualTo(200); |
| assertThat(thread2.mCallback.getResponseInfoWithChecks()) |
| .hasHttpStatusCodeThat() |
| .isEqualTo(404); |
| } |
| |
| @Test |
| @SmallTest |
| public void testInitTwoEnginesInSequence() throws Exception { |
| ConditionVariable runBlocker = new ConditionVariable(true); |
| RequestThread thread1 = new RequestThread(mUrl, runBlocker); |
| RequestThread thread2 = new RequestThread(mUrl404, runBlocker); |
| |
| thread1.start(); |
| thread1.join(); |
| thread2.start(); |
| thread2.join(); |
| assertThat(thread1.mCallback.getResponseInfoWithChecks()) |
| .hasHttpStatusCodeThat() |
| .isEqualTo(200); |
| assertThat(thread2.mCallback.getResponseInfoWithChecks()) |
| .hasHttpStatusCodeThat() |
| .isEqualTo(404); |
| } |
| |
| @Test |
| @SmallTest |
| public void testInitDifferentEngines() throws Exception { |
| // Test that concurrently instantiating Cronet context's upon various |
| // different versions of the same Android Context does not cause crashes |
| // like crbug.com/453845 |
| CronetEngine firstEngine = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()) |
| .build(); |
| CronetEngine secondEngine = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()) |
| .build(); |
| CronetEngine thirdEngine = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()) |
| .build(); |
| firstEngine.shutdown(); |
| secondEngine.shutdown(); |
| thirdEngine.shutdown(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "Global metrics delta is supported only by the native implementation") |
| public void testGetGlobalMetricsDeltas() throws Exception { |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| |
| byte[] delta1 = cronetEngine.getGlobalMetricsDeltas(); |
| |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| UrlRequest.Builder builder = |
| cronetEngine.newUrlRequestBuilder(mUrl, callback, callback.getExecutor()); |
| builder.build().start(); |
| callback.blockForDone(); |
| // Fetch deltas on a different thread the second time to make sure this is permitted. |
| // See crbug.com/719448 |
| FutureTask<byte[]> task = |
| new FutureTask<byte[]>( |
| new Callable<byte[]>() { |
| @Override |
| public byte[] call() { |
| return cronetEngine.getGlobalMetricsDeltas(); |
| } |
| }); |
| new Thread(task).start(); |
| byte[] delta2 = task.get(); |
| assertThat(delta2).isNotEmpty(); |
| assertThat(delta2).isNotEqualTo(delta1); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "Deliberate manual creation of native engines") |
| public void testCronetEngineBuilderConfig() throws Exception { |
| // This is to prompt load of native library. |
| mTestRule.getTestFramework().startEngine(); |
| // Verify CronetEngine.Builder config is passed down accurately to native code. |
| ExperimentalCronetEngine.Builder builder = |
| new ExperimentalCronetEngine.Builder(mTestRule.getTestFramework().getContext()); |
| builder.enableHttp2(false); |
| builder.enableQuic(true); |
| builder.addQuicHint("example.com", 12, 34); |
| builder.enableHttpCache(HTTP_CACHE_IN_MEMORY, 54321); |
| builder.setUserAgent("efgh"); |
| builder.setExperimentalOptions(""); |
| builder.setStoragePath(getTestStorage(mTestRule.getTestFramework().getContext())); |
| builder.enablePublicKeyPinningBypassForLocalTrustAnchors(false); |
| CronetUrlRequestContextTestJni.get() |
| .verifyUrlRequestContextConfig( |
| CronetUrlRequestContext.createNativeUrlRequestContextConfig( |
| CronetTestUtil.getCronetEngineBuilderImpl(builder)), |
| getTestStorage(mTestRule.getTestFramework().getContext())); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK}, |
| reason = "Deliberate manual creation of native engines") |
| public void testCronetEngineQuicOffConfig() throws Exception { |
| // This is to prompt load of native library. |
| mTestRule.getTestFramework().startEngine(); |
| // Verify CronetEngine.Builder config is passed down accurately to native code. |
| ExperimentalCronetEngine.Builder builder = |
| new ExperimentalCronetEngine.Builder(mTestRule.getTestFramework().getContext()); |
| builder.enableHttp2(false); |
| // QUIC is on by default. Disabling it here to make sure the built config can correctly |
| // reflect the change. |
| builder.enableQuic(false); |
| builder.enableHttpCache(HTTP_CACHE_IN_MEMORY, 54321); |
| builder.setExperimentalOptions(""); |
| builder.setUserAgent("efgh"); |
| builder.setStoragePath(getTestStorage(mTestRule.getTestFramework().getContext())); |
| builder.enablePublicKeyPinningBypassForLocalTrustAnchors(false); |
| CronetUrlRequestContextTestJni.get() |
| .verifyUrlRequestContextQuicOffConfig( |
| CronetUrlRequestContext.createNativeUrlRequestContextConfig( |
| CronetTestUtil.getCronetEngineBuilderImpl(builder)), |
| getTestStorage(mTestRule.getTestFramework().getContext())); |
| } |
| |
| private static class TestBadLibraryLoader extends CronetEngine.Builder.LibraryLoader { |
| private boolean mWasCalled; |
| |
| @Override |
| public void loadLibrary(String libName) { |
| // Report that this method was called, but don't load the library |
| mWasCalled = true; |
| } |
| |
| boolean wasCalled() { |
| return mWasCalled; |
| } |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "LibraryLoader is supported only by the native implementation") |
| public void testSetLibraryLoaderIsEnforcedByDefaultEmbeddedProvider() throws Exception { |
| CronetEngine.Builder builder = |
| new CronetEngine.Builder(mTestRule.getTestFramework().getContext()); |
| TestBadLibraryLoader loader = new TestBadLibraryLoader(); |
| builder.setLibraryLoader(loader); |
| |
| assertThrows( |
| "Native library should not be loaded", UnsatisfiedLinkError.class, builder::build); |
| assertThat(loader.wasCalled()).isTrue(); |
| |
| // The init thread is started *before* the library is loaded, so the init thread is running |
| // despite the library loading failure. Init thread initialization can race against test |
| // cleanup (e.g. Context access). We work around the issue by ensuring test cleanup will |
| // call shutdown() on a real engine, which will block until the init thread initialization |
| // is done. |
| mTestRule.getTestFramework().startEngine(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "LibraryLoader is supported only by the native implementation") |
| public void testSetLibraryLoaderIsIgnoredInNativeCronetEngineBuilderImpl() throws Exception { |
| CronetEngine.Builder builder = |
| new CronetEngine.Builder( |
| new NativeCronetEngineBuilderImpl( |
| mTestRule.getTestFramework().getContext())); |
| TestBadLibraryLoader loader = new TestBadLibraryLoader(); |
| builder.setLibraryLoader(loader); |
| CronetEngine engine = builder.build(); |
| assertThat(engine).isNotNull(); |
| assertThat(loader.wasCalled()).isFalse(); |
| engine.shutdown(); |
| } |
| |
| // Creates a CronetEngine on another thread and then one on the main thread. This shouldn't |
| // crash. |
| @Test |
| @SmallTest |
| public void testThreadedStartup() throws Exception { |
| final ConditionVariable otherThreadDone = new ConditionVariable(); |
| final ConditionVariable uiThreadDone = new ConditionVariable(); |
| new Handler(Looper.getMainLooper()) |
| .post( |
| new Runnable() { |
| @Override |
| public void run() { |
| final ExperimentalCronetEngine.Builder builder = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder( |
| mTestRule.getTestFramework().getContext()); |
| new Thread() { |
| @Override |
| public void run() { |
| CronetEngine cronetEngine = builder.build(); |
| otherThreadDone.open(); |
| cronetEngine.shutdown(); |
| } |
| }.start(); |
| otherThreadDone.block(); |
| builder.build().shutdown(); |
| uiThreadDone.open(); |
| } |
| }); |
| assertThat(uiThreadDone.block(1000)).isTrue(); |
| } |
| |
| @Test |
| @SmallTest |
| @IgnoreFor( |
| implementations = {CronetImplementation.FALLBACK, CronetImplementation.AOSP_PLATFORM}, |
| reason = "JSON experimental options are supported only by the native implementation") |
| public void testHostResolverRules() throws Exception { |
| String resolverTestHostname = "some-weird-hostname"; |
| URL testUrl = new URL(mUrl); |
| JSONObject hostResolverRules = |
| new JSONObject() |
| .put( |
| "host_resolver_rules", |
| "MAP " + resolverTestHostname + " " + testUrl.getHost()); |
| mTestRule |
| .getTestFramework() |
| .applyEngineBuilderPatch( |
| (builder) -> { |
| JSONObject experimentalOptions = |
| new JSONObject().put("HostResolverRules", hostResolverRules); |
| builder.setExperimentalOptions(experimentalOptions.toString()); |
| }); |
| |
| ExperimentalCronetEngine cronetEngine = mTestRule.getTestFramework().startEngine(); |
| |
| TestUrlRequestCallback callback = new TestUrlRequestCallback(); |
| URL requestUrl = |
| new URL("http", resolverTestHostname, testUrl.getPort(), testUrl.getFile()); |
| UrlRequest.Builder urlRequestBuilder = |
| cronetEngine.newUrlRequestBuilder( |
| requestUrl.toString(), callback, callback.getExecutor()); |
| urlRequestBuilder.build().start(); |
| callback.blockForDone(); |
| assertThat(callback.getResponseInfoWithChecks()).hasHttpStatusCodeThat().isEqualTo(200); |
| } |
| |
| /** Runs {@code r} on {@code engine}'s network thread. */ |
| private static void postToNetworkThread(final CronetEngine engine, final Runnable r) { |
| // Works by requesting an invalid URL which results in onFailed() being called, which is |
| // done through a direct executor which causes onFailed to be run on the network thread. |
| Executor directExecutor = |
| new Executor() { |
| @Override |
| public void execute(Runnable runable) { |
| runable.run(); |
| } |
| }; |
| UrlRequest.Callback callback = |
| new UrlRequest.Callback() { |
| @Override |
| public void onRedirectReceived( |
| UrlRequest request, |
| UrlResponseInfo responseInfo, |
| String newLocationUrl) {} |
| |
| @Override |
| public void onResponseStarted( |
| UrlRequest request, UrlResponseInfo responseInfo) {} |
| |
| @Override |
| public void onReadCompleted( |
| UrlRequest request, |
| UrlResponseInfo responseInfo, |
| ByteBuffer byteBuffer) {} |
| |
| @Override |
| public void onSucceeded(UrlRequest request, UrlResponseInfo responseInfo) {} |
| |
| @Override |
| public void onFailed( |
| UrlRequest request, |
| UrlResponseInfo responseInfo, |
| CronetException error) { |
| r.run(); |
| } |
| }; |
| engine.newUrlRequestBuilder("", callback, directExecutor).build().start(); |
| } |
| |
| /** @returns the thread priority of {@code engine}'s network thread. */ |
| private static class ApiHelper { |
| public static boolean doesContextExistForNetwork(CronetEngine engine, Network network) |
| throws Exception { |
| FutureTask<Boolean> task = |
| new FutureTask<Boolean>( |
| new Callable<Boolean>() { |
| @Override |
| public Boolean call() { |
| return CronetTestUtil.doesURLRequestContextExistForTesting( |
| engine, network); |
| } |
| }); |
| postToNetworkThread(engine, task); |
| return task.get(); |
| } |
| } |
| |
| /** @returns the thread priority of {@code engine}'s network thread. */ |
| private int getThreadPriority(CronetEngine engine) throws Exception { |
| FutureTask<Integer> task = |
| new FutureTask<Integer>( |
| new Callable<Integer>() { |
| @Override |
| public Integer call() { |
| return Process.getThreadPriority(Process.myTid()); |
| } |
| }); |
| postToNetworkThread(engine, task); |
| return task.get(); |
| } |
| |
| /** |
| * Cronet does not currently provide an API to wait for the active request count to change. We |
| * can't just wait for the terminal callback to fire because Cronet updates the count some time |
| * *after* we return from the callback. We hack around this by polling the active request count |
| * in a loop. |
| */ |
| private static void waitForActiveRequestCount(CronetEngine engine, int expectedCount) |
| throws Exception { |
| while (engine.getActiveRequestCount() != expectedCount) Thread.sleep(100); |
| } |
| |
| @Test |
| @SmallTest |
| @RequiresMinApi(6) // setThreadPriority added in API 6: crrev.com/472449 |
| @IgnoreFor( |
| implementations = {CronetImplementation.AOSP_PLATFORM}, |
| reason = "ThreadPriority is not available in AOSP") |
| public void testCronetEngineThreadPriority() throws Exception { |
| ExperimentalCronetEngine.Builder builder = |
| mTestRule |
| .getTestFramework() |
| .createNewSecondaryBuilder(mTestRule.getTestFramework().getContext()); |
| // Try out of bounds thread priorities. |
| IllegalArgumentException e = |
| assertThrows(IllegalArgumentException.class, () -> builder.setThreadPriority(-21)); |
| assertThat(e).hasMessageThat().isEqualTo("Thread priority invalid"); |
| |
| e = assertThrows(IllegalArgumentException.class, () -> builder.setThreadPriority(20)); |
| assertThat(e).hasMessageThat().isEqualTo("Thread priority invalid"); |
| |
| // Test that valid thread priority range (-20..19) is working. |
| for (int threadPriority = -20; threadPriority < 20; threadPriority++) { |
| builder.setThreadPriority(threadPriority); |
| CronetEngine engine = builder.build(); |
| try { |
| assertThat(getThreadPriority(engine)).isEqualTo(threadPriority); |
| } finally { |
| engine.shutdown(); |
| } |
| } |
| } |
| |
| @NativeMethods("cronet_tests") |
| interface Natives { |
| // Verifies that CronetEngine.Builder config from testCronetEngineBuilderConfig() is |
| // properly translated to a native UrlRequestContextConfig. |
| void verifyUrlRequestContextConfig(long config, String storagePath); |
| |
| // Verifies that CronetEngine.Builder config from testCronetEngineQuicOffConfig() is |
| // properly translated to a native UrlRequestContextConfig and QUIC is turned off. |
| void verifyUrlRequestContextQuicOffConfig(long config, String storagePath); |
| } |
| } |