summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefano Duo <stefanoduo@google.com>2023-08-08 12:39:48 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-08-08 12:39:48 +0000
commit61192f8c0bbef2ddcfbc241276f7f808c0e52616 (patch)
tree5bc207a56ac5369e9e3e661c61515772a7081174
parent01d46eea6b265a64bea0dfc983e9383b3ea9be4b (diff)
parent8c40b44bf9ffa410fb5ef1c1c815b6bbd6e5d1f4 (diff)
downloadcronet-61192f8c0bbef2ddcfbc241276f7f808c0e52616.tar.gz
Cherry-pick https://crrev.com/c/4667153 am: 8c40b44bf9
Original change: https://android-review.googlesource.com/c/platform/external/cronet/+/2687908 Change-Id: Iec9e0177831b1757acb0c7f62e4317bdef33e6f8 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--Android.extras.bp7
-rw-r--r--components/cronet/android/BUILD.gn1
-rw-r--r--components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java66
-rw-r--r--components/cronet/android/test/javatests/src/org/chromium/net/ContextInterceptor.java25
-rw-r--r--components/cronet/android/test/javatests/src/org/chromium/net/CronetTestRuleNew.java509
-rw-r--r--components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestInterceptor.java56
-rw-r--r--components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestTest.java108
7 files changed, 689 insertions, 83 deletions
diff --git a/Android.extras.bp b/Android.extras.bp
index 12139c3c7..1e3283722 100644
--- a/Android.extras.bp
+++ b/Android.extras.bp
@@ -4,7 +4,7 @@
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
-// http://www.apache.org/licenses/LICENSE-2.0
+// http://www.apache.org/licensNew.javaues/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
@@ -265,11 +265,16 @@ filegroup {
"components/cronet/android/test/javatests/**/UrlResponseInfoTest.java",
"components/cronet/android/test/javatests/**/BidirectionalStreamQuicTest.java",
"components/cronet/android/test/javatests/**/BrotliTest.java",
+ "components/cronet/android/test/javatests/**/ContextInterceptor.java",
"components/cronet/android/test/javatests/**/CronetLoggerTestRule.java",
"components/cronet/android/test/javatests/**/CronetLoggerTest.java",
+ "components/cronet/android/test/javatests/**/CronetManifestInterceptor.java",
"components/cronet/android/test/javatests/**/CronetManifestTest.java",
"components/cronet/android/test/javatests/**/CronetStressTest.java",
"components/cronet/android/test/javatests/**/CronetTestRule.java",
+ // TODO: Remove once https://crrev.com/c/4667153 (i.e., Cronet 117.0.5911.0) gets
+ // imported. See https://android-review.git.corp.google.com/c/platform/external/cronet/+/2687908/comment/46c6fff9_6778590e/
+ "components/cronet/android/test/javatests/**/CronetTestRuleNew.java",
"components/cronet/android/test/javatests/**/CronetTestRuleTest.java",
"components/cronet/android/test/javatests/**/CronetUploadTest.java",
"components/cronet/android/test/javatests/**/CronetUrlRequestTest.java",
diff --git a/components/cronet/android/BUILD.gn b/components/cronet/android/BUILD.gn
index 3f4c0b51b..679ea00df 100644
--- a/components/cronet/android/BUILD.gn
+++ b/components/cronet/android/BUILD.gn
@@ -877,6 +877,7 @@ android_library("cronet_common_javatests") {
"test/javatests/src/org/chromium/net/TestRequestFinishedListener.java",
"test/javatests/src/org/chromium/net/TestUploadDataProvider.java",
"test/javatests/src/org/chromium/net/TestUrlRequestCallback.java",
+ "test/javatests/src/org/chromium/net/impl/CronetManifestInterceptor.java",
]
deps = [
":cronet_api_java",
diff --git a/components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java b/components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java
index 6cee84819..eda0f6299 100644
--- a/components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java
+++ b/components/cronet/android/java/src/org/chromium/net/impl/CronetManifest.java
@@ -4,41 +4,71 @@
package org.chromium.net.impl;
+import android.content.ComponentName;
import android.content.Context;
-import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
import androidx.annotation.VisibleForTesting;
import org.chromium.net.impl.CronetLogger.CronetSource;
/**
- * Utility class for working with the AndroidManifest flags.
+ * Utilities for working with Cronet Android manifest flags.
+ *
+ * Cronet manifest flags must be defined within a service definition named after {@link
+ * #META_DATA_HOLDER_SERVICE_NAME} (the reason this is not defined at the application level is to
+ * avoid scalability issues with PackageManager queries). For example, to enable telemetry, add the
+ * following to {@code AndroidManifest.xml}:
+ *
+ * <pre>{@code
+ * <manifest ...>
+ * ...
+ * <application ...>
+ * ...
+ * <service android:name="android.net.http.MetaDataHolder"
+ * android:enabled="false" android:exported="false">
+ * <meta-data android:name="android.net.http.EnableTelemetry"
+ * android:value="true" />
+ * </service>
+ * </application>
+ * </manifest>
+ * }</pre>
*/
@VisibleForTesting
public final class CronetManifest {
private CronetManifest() {}
- // Individual apps can use this meta-data tag in their manifest to opt in for telemetry.
- // Todo (colibie): Add this to the android documentation
- static final String TELEMETRY_OPT_IN_META_DATA_STR = "org.chromium.net.EnableCronetTelemetry";
@VisibleForTesting
- public static boolean isAppOptedInForTelemetry(Context ctx, CronetSource source) {
- try {
- // Check if app is opted in
- ApplicationInfo info = ctx.getPackageManager().getApplicationInfo(
- ctx.getPackageName(), PackageManager.GET_META_DATA);
+ static final String META_DATA_HOLDER_SERVICE_NAME = "android.net.http.MetaDataHolder";
+ @VisibleForTesting
+ static final String ENABLE_TELEMETRY_META_DATA_KEY = "android.net.http.EnableTelemetry";
- // TODO(b/226553652): Enable logging if loaded from CRONET_PLAY_SERVICES, after testing
- // with select users
+ /**
+ * @return True if telemetry should be enabled, based on the {@link
+ * #ENABLE_TELEMETRY_META_DATA_KEY} meta-data entry in the Android manifest.
+ */
+ public static boolean isAppOptedInForTelemetry(Context context, CronetSource source) {
+ return getMetaData(context).getBoolean(ENABLE_TELEMETRY_META_DATA_KEY, /*default=*/false);
+ }
- // getBoolean returns false if the key is not found, which is what we want.
- return info.metaData == null ? false
- : info.metaData.getBoolean(TELEMETRY_OPT_IN_META_DATA_STR);
+ /**
+ * @return The meta-data contained within the Cronet meta-data holder service definition in the
+ * Android manifest, or an empty Bundle if there is no such definition. Never returns null.
+ */
+ private static Bundle getMetaData(Context context) {
+ ServiceInfo serviceInfo;
+ try {
+ serviceInfo = context.getPackageManager().getServiceInfo(
+ new ComponentName(context, META_DATA_HOLDER_SERVICE_NAME),
+ PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_COMPONENTS
+ | PackageManager.MATCH_DIRECT_BOOT_AWARE
+ | PackageManager.MATCH_DIRECT_BOOT_UNAWARE);
} catch (PackageManager.NameNotFoundException e) {
- // This should never happen.
- // The conservative thing is to assume the app HAS opted out.
- return false;
+ serviceInfo = null;
}
+ Bundle metaData = serviceInfo != null ? serviceInfo.metaData : null;
+ return metaData != null ? metaData : new Bundle();
}
}
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/ContextInterceptor.java b/components/cronet/android/test/javatests/src/org/chromium/net/ContextInterceptor.java
new file mode 100644
index 000000000..bcd03ae80
--- /dev/null
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/ContextInterceptor.java
@@ -0,0 +1,25 @@
+// Copyright 2023 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 android.content.Context;
+import android.content.ContextWrapper;
+
+/**
+ * An interface for providing a replacement for an Android {@link Context}. Useful for
+ * faking/mocking Context calls in tests.
+ *
+ * @see CronetTestRule.CronetTestFramework#interceptContext
+ */
+public interface ContextInterceptor {
+ /**
+ * Provides a {@link Context} that the current context should be replaced with.
+ *
+ * @param context the original Context to be replaced
+ * @return the new Context. Typically this would forward most calls to the original context, for
+ * example using {@link ContextWrapper}.
+ */
+ public Context interceptContext(Context context);
+}
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestRuleNew.java b/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestRuleNew.java
new file mode 100644
index 000000000..988fc1673
--- /dev/null
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/CronetTestRuleNew.java
@@ -0,0 +1,509 @@
+// Copyright 2017 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 org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.content.MutableContextWrapper;
+import android.os.Build;
+import android.os.StrictMode;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+import org.chromium.base.ContextUtils;
+import org.chromium.base.Log;
+import org.chromium.base.PathUtils;
+import org.chromium.net.impl.NativeCronetProvider;
+import org.chromium.net.impl.UserAgent;
+
+import java.io.File;
+import java.lang.annotation.Annotation;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Custom TestRule for Cronet instrumentation tests.
+ */
+public class CronetTestRuleNew implements TestRule {
+ private static final String PRIVATE_DATA_DIRECTORY_SUFFIX = "cronet_test";
+ private static final String TAG = "CronetTestRuleNew";
+
+ private CronetTestFramework mCronetTestFramework;
+ private CronetImplementation mImplementation;
+
+ private final EngineStartupMode mEngineStartupMode;
+
+ private CronetTestRuleNew(EngineStartupMode engineStartupMode) {
+ this.mEngineStartupMode = engineStartupMode;
+ }
+
+ /**
+ * Requires the user to call {@code CronetTestFramework.startEngine()} but allows to customize
+ * the builder parameters.
+ */
+ public static CronetTestRuleNew withManualEngineStartup() {
+ return new CronetTestRuleNew(EngineStartupMode.MANUAL);
+ }
+
+ /**
+ * Starts the Cronet engine automatically for each test case, but doesn't allow any
+ * customizations to the builder.
+ */
+ public static CronetTestRuleNew withAutomaticEngineStartup() {
+ return new CronetTestRuleNew(EngineStartupMode.AUTOMATIC);
+ }
+
+ public CronetTestFramework getTestFramework() {
+ return mCronetTestFramework;
+ }
+
+ public void assertResponseEquals(UrlResponseInfo expected, UrlResponseInfo actual) {
+ assertThat(actual.getAllHeaders()).isEqualTo(expected.getAllHeaders());
+ assertThat(actual.getAllHeadersAsList()).isEqualTo(expected.getAllHeadersAsList());
+ assertThat(actual.getHttpStatusCode()).isEqualTo(expected.getHttpStatusCode());
+ assertThat(actual.getHttpStatusText()).isEqualTo(expected.getHttpStatusText());
+ assertThat(actual.getUrlChain()).isEqualTo(expected.getUrlChain());
+ assertThat(actual.getUrl()).isEqualTo(expected.getUrl());
+ // Transferred bytes and proxy server are not supported in pure java
+ if (!testingJavaImpl()) {
+ assertThat(actual.getReceivedByteCount()).isEqualTo(expected.getReceivedByteCount());
+ assertThat(actual.getProxyServer()).isEqualTo(expected.getProxyServer());
+ // This is a place where behavior intentionally differs between native and java
+ assertThat(actual.getNegotiatedProtocol()).isEqualTo(expected.getNegotiatedProtocol());
+ }
+ }
+
+ /**
+ * Returns {@code true} when test is being run against the java implementation of CronetEngine.
+ *
+ * @deprecated use the implementation enum
+ */
+ @Deprecated
+ public boolean testingJavaImpl() {
+ return false;
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description desc) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ runBase(base, desc);
+ }
+ };
+ }
+
+ // TODO(yolandyan): refactor this using parameterize framework
+ private void runBase(Statement base, Description desc) throws Throwable {
+ setImplementationUnderTest(CronetImplementation.STATICALLY_LINKED);
+ String packageName = desc.getTestClass().getPackage().getName();
+
+ boolean onlyRunTestForNative = desc.getAnnotation(OnlyRunNativeCronet.class) != null
+ || desc.getTestClass().getAnnotation(OnlyRunNativeCronet.class) != null;
+ boolean onlyRunTestForJava = desc.getAnnotation(OnlyRunJavaCronet.class) != null;
+ if (onlyRunTestForNative && onlyRunTestForJava) {
+ throw new IllegalArgumentException(desc.getMethodName()
+ + " skipped because it specified both "
+ + "OnlyRunNativeCronet and OnlyRunJavaCronet annotations");
+ }
+ boolean doRunTestForNative = onlyRunTestForNative || !onlyRunTestForJava;
+ boolean doRunTestForJava = onlyRunTestForJava || !onlyRunTestForNative;
+
+ // Find the API version required by the test.
+ int requiredApiVersion = getMaximumAvailableApiLevel();
+ int requiredAndroidApiVersion = Build.VERSION_CODES.KITKAT;
+ for (Annotation a : desc.getTestClass().getAnnotations()) {
+ if (a instanceof RequiresMinApi) {
+ requiredApiVersion = ((RequiresMinApi) a).value();
+ }
+ if (a instanceof RequiresMinAndroidApi) {
+ requiredAndroidApiVersion = ((RequiresMinAndroidApi) a).value();
+ }
+ }
+ for (Annotation a : desc.getAnnotations()) {
+ // Method scoped requirements take precedence over class scoped
+ // requirements.
+ if (a instanceof RequiresMinApi) {
+ requiredApiVersion = ((RequiresMinApi) a).value();
+ }
+ if (a instanceof RequiresMinAndroidApi) {
+ requiredAndroidApiVersion = ((RequiresMinAndroidApi) a).value();
+ }
+ }
+ assumeTrue(desc.getMethodName() + " skipped because it requires API " + requiredApiVersion
+ + " but only API " + getMaximumAvailableApiLevel() + " is present.",
+ getMaximumAvailableApiLevel() >= requiredApiVersion);
+ assumeTrue(desc.getMethodName() + " skipped because it Android's API level "
+ + requiredAndroidApiVersion + " but test device supports only API "
+ + Build.VERSION.SDK_INT,
+ Build.VERSION.SDK_INT >= requiredAndroidApiVersion);
+
+ if (packageName.startsWith("org.chromium.net")) {
+ try {
+ if (doRunTestForNative) {
+ Log.i(TAG, "Running test against Native implementation.");
+ evaluateWithFramework(base);
+ }
+ } catch (Throwable e) {
+ Log.e(TAG, "CronetTestBase#runTest failed for %s implementation.", mImplementation);
+ throw e;
+ }
+ } else {
+ evaluateWithFramework(base);
+ }
+ }
+
+ private void evaluateWithFramework(Statement statement) throws Throwable {
+ try (CronetTestFramework framework = createCronetTestFramework()) {
+ statement.evaluate();
+ } finally {
+ mCronetTestFramework = null;
+ }
+ }
+
+ private CronetTestFramework createCronetTestFramework() {
+ mCronetTestFramework = new CronetTestFramework(mImplementation);
+ if (mEngineStartupMode.equals(EngineStartupMode.AUTOMATIC)) {
+ mCronetTestFramework.startEngine();
+ }
+ return mCronetTestFramework;
+ }
+
+ static int getMaximumAvailableApiLevel() {
+ // Prior to M59 the ApiVersion.getMaximumAvailableApiLevel API didn't exist
+ int cronetMajorVersion = Integer.parseInt(ApiVersion.getCronetVersion().split("\\.")[0]);
+ if (cronetMajorVersion < 59) {
+ return 3;
+ }
+ return ApiVersion.getMaximumAvailableApiLevel();
+ }
+
+ /**
+ * Annotation for test classes or methods in org.chromium.net package that disables rerunning
+ * the test against the Java-only implementation. When this annotation is present the test is
+ * only run against the native implementation.
+ */
+ @Target({ElementType.TYPE, ElementType.METHOD})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface OnlyRunNativeCronet {}
+
+ /**
+ * Annotation for test methods in org.chromium.net package that disables rerunning the test
+ * against the Native/Chromium implementation. When this annotation is present the test is only
+ * run against the Java implementation.
+ */
+ @Target(ElementType.METHOD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface OnlyRunJavaCronet {}
+
+ /**
+ * Annotation allowing classes or individual tests to be skipped based on the version of the
+ * Cronet API present. Takes the minimum API version upon which the test should be run.
+ * For example if a test should only be run with API version 2 or greater:
+ * @RequiresMinApi(2)
+ * public void testFoo() {}
+ */
+ @Target({ElementType.TYPE, ElementType.METHOD})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface RequiresMinApi {
+ int value();
+ }
+
+ /**
+ * Annotation allowing classes or individual tests to be skipped based on the Android OS version
+ * installed in the deviced used for testing. Takes the minimum API version upon which the test
+ * should be run. For example if a test should only be run with Android Oreo or greater:
+ * @RequiresMinApi(Build.VERSION_CODES.O)
+ * public void testFoo() {}
+ */
+ @Target({ElementType.TYPE, ElementType.METHOD})
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface RequiresMinAndroidApi {
+ int value();
+ }
+
+ /**
+ * Prepares the path for the test storage (http cache, QUIC server info).
+ */
+ public static void prepareTestStorage(Context context) {
+ File storage = new File(getTestStorageDirectory());
+ if (storage.exists()) {
+ assertThat(recursiveDelete(storage)).isTrue();
+ }
+ ensureTestStorageExists();
+ }
+
+ /**
+ * Returns the path for the test storage (http cache, QUIC server info).
+ * Also ensures it exists.
+ */
+ public static String getTestStorage(Context context) {
+ ensureTestStorageExists();
+ return getTestStorageDirectory();
+ }
+
+ /**
+ * Returns the path for the test storage (http cache, QUIC server info).
+ * NOTE: Does not ensure it exists; tests should use {@link #getTestStorage}.
+ */
+ private static String getTestStorageDirectory() {
+ return PathUtils.getDataDirectory() + "/test_storage";
+ }
+
+ /**
+ * Ensures test storage directory exists, i.e. creates one if it does not exist.
+ */
+ private static void ensureTestStorageExists() {
+ File storage = new File(getTestStorageDirectory());
+ if (!storage.exists()) {
+ assertThat(storage.mkdir()).isTrue();
+ }
+ }
+
+ private static boolean recursiveDelete(File path) {
+ if (path.isDirectory()) {
+ for (File c : path.listFiles()) {
+ if (!recursiveDelete(c)) {
+ return false;
+ }
+ }
+ }
+ return path.delete();
+ }
+
+ private void setImplementationUnderTest(CronetImplementation implementation) {
+ mImplementation = implementation;
+ }
+
+ /**
+ * Creates and holds pointer to CronetEngine.
+ */
+ public static class CronetTestFramework implements AutoCloseable {
+ private final CronetImplementation mImplementation;
+ private final ExperimentalCronetEngine.Builder mBuilder;
+ private final MutableContextWrapper mContextWrapper;
+ private final StrictMode.VmPolicy mOldVmPolicy;
+
+ private ExperimentalCronetEngine mCronetEngine;
+ private boolean mClosed;
+
+ private CronetTestFramework(CronetImplementation implementation) {
+ this.mContextWrapper =
+ new MutableContextWrapper(ApplicationProvider.getApplicationContext());
+ this.mBuilder = implementation.createBuilder(mContextWrapper)
+ .enableQuic(true);
+ this.mImplementation = implementation;
+
+ System.loadLibrary("cronet_tests");
+ ContextUtils.initApplicationContext(getContext().getApplicationContext());
+ PathUtils.setPrivateDataDirectorySuffix(PRIVATE_DATA_DIRECTORY_SUFFIX);
+ prepareTestStorage(getContext());
+ mOldVmPolicy = StrictMode.getVmPolicy();
+ // Only enable StrictMode testing after leaks were fixed in crrev.com/475945
+ if (getMaximumAvailableApiLevel() >= 7) {
+ StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
+ .detectLeakedClosableObjects()
+ .penaltyLog()
+ .penaltyDeath()
+ .build());
+ }
+ }
+
+ /**
+ * Replaces the {@link Context} implementation that the Cronet engine calls into. Useful for
+ * faking/mocking Android context calls.
+ *
+ * @throws IllegalStateException if called after the Cronet engine has already been built.
+ * Intercepting context calls while the code under test is running is racy and runs the risk
+ * that the code under test will not pick up the change.
+ */
+ public void interceptContext(ContextInterceptor contextInterceptor) {
+ checkNotClosed();
+
+ if (mCronetEngine != null) {
+ throw new IllegalStateException(
+ "Refusing to intercept context after the Cronet engine has been built");
+ }
+
+ mContextWrapper.setBaseContext(
+ contextInterceptor.interceptContext(mContextWrapper.getBaseContext()));
+ }
+
+ /**
+ * @return the context to be used by the Cronet engine
+ *
+ * @see #interceptContext
+ */
+ public Context getContext() {
+ checkNotClosed();
+ return mContextWrapper;
+ }
+
+ public CronetEngine.Builder enableDiskCache(CronetEngine.Builder cronetEngineBuilder) {
+ cronetEngineBuilder.setStoragePath(getTestStorage(getContext()));
+ cronetEngineBuilder.enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK, 1000 * 1024);
+ return cronetEngineBuilder;
+ }
+
+ public ExperimentalCronetEngine startEngine() {
+ checkNotClosed();
+
+ if (mCronetEngine != null) {
+ throw new IllegalStateException("Engine is already started!");
+ }
+
+ mCronetEngine = mBuilder.build();
+ mImplementation.verifyCronetEngineInstance(mCronetEngine);
+
+ // Start collecting metrics.
+ mCronetEngine.getGlobalMetricsDeltas();
+
+ return mCronetEngine;
+ }
+
+ public ExperimentalCronetEngine getEngine() {
+ checkNotClosed();
+
+ if (mCronetEngine == null) {
+ throw new IllegalStateException("Engine not started yet!");
+ }
+
+ return mCronetEngine;
+ }
+
+ /**
+ * Applies the given patch to the primary Cronet Engine builder associated with this run.
+ */
+ public void applyEngineBuilderPatch(CronetBuilderPatch patch) {
+ checkNotClosed();
+
+ if (mCronetEngine != null) {
+ throw new IllegalStateException("The engine was already built!");
+ }
+
+ try {
+ patch.apply(mBuilder);
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Cannot apply the given patch!", e);
+ }
+ }
+
+ /**
+ * Returns a new instance of a Cronet builder corresponding to the implementation under
+ * test.
+ *
+ * <p>Some test cases need to create multiple instances of Cronet engines to test
+ * interactions between them, so we provide the capability to do so and reliably obtain
+ * the correct Cronet implementation.
+ *
+ * <p>Note that this builder and derived Cronet engine is not managed by the framework! The
+ * caller is responsible for cleaning up resources (e.g. calling {@code engine.shutdown()}
+ * at the end of the test).
+ *
+ */
+ public ExperimentalCronetEngine.Builder createNewSecondaryBuilder(Context context) {
+ return mImplementation.createBuilder(context);
+ }
+
+ @Override
+ public void close() {
+ if (mClosed) {
+ return;
+ }
+ shutdownEngine();
+ mClosed = true;
+
+ try {
+ // Run GC and finalizers a few times to pick up leaked closeables
+ for (int i = 0; i < 10; i++) {
+ System.gc();
+ System.runFinalization();
+ }
+ } finally {
+ StrictMode.setVmPolicy(mOldVmPolicy);
+ }
+ }
+
+ private void shutdownEngine() {
+ if (mCronetEngine == null) {
+ return;
+ }
+ try {
+ mCronetEngine.shutdown();
+ } catch (IllegalStateException e) {
+ if (e.getMessage().contains("Engine is shut down")) {
+ // We're trying to shut the engine down repeatedly. Make such calls idempotent
+ // instead of failing, as there's no API to query whether an engine is shut down
+ // and some tests shut the engine down deliberately (e.g. to make sure
+ // everything is flushed properly).
+ Log.d(TAG, "Cronet engine already shut down by the test.", e);
+ } else {
+ throw e;
+ }
+ }
+ mCronetEngine = null;
+ }
+
+ private void checkNotClosed() {
+ if (mClosed) {
+ throw new IllegalStateException(
+ "Unable to interact with a closed CronetTestFramework!");
+ }
+ }
+ }
+
+ /**
+ * A functional interface that allows Cronet tests to modify parameters of the Cronet engine
+ * provided by {@code CronetTestFramework}.
+ *
+ * <p>The builder itself isn't exposed directly as a getter to tests to stress out ownership
+ * and make accidental local access less likely.
+ */
+ public static interface CronetBuilderPatch {
+ public void apply(ExperimentalCronetEngine.Builder builder) throws Exception;
+ }
+
+ private enum EngineStartupMode {
+ MANUAL,
+ AUTOMATIC,
+ }
+
+ // This is a replacement for java.util.function.Function as Function is only available
+ // starting android API level 24.
+ private interface EngineBuilderSupplier {
+ ExperimentalCronetEngine.Builder getCronetEngineBuilder(Context context);
+ }
+
+ public enum CronetImplementation {
+ STATICALLY_LINKED(context
+ -> (ExperimentalCronetEngine.Builder) new NativeCronetProvider(context)
+ .createBuilder()),
+ AOSP_PLATFORM(
+ (context) -> { throw new UnsupportedOperationException("Not implemented yet"); });
+
+ private final EngineBuilderSupplier mEngineSupplier;
+
+ private CronetImplementation(EngineBuilderSupplier engineSupplier) {
+ this.mEngineSupplier = engineSupplier;
+ }
+
+ ExperimentalCronetEngine.Builder createBuilder(Context context) {
+ return mEngineSupplier.getCronetEngineBuilder(context);
+ }
+
+ private void verifyCronetEngineInstance(CronetEngine engine) {
+ // TODO(danstahr): Add assertions for expected class
+ }
+ }
+}
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestInterceptor.java b/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestInterceptor.java
new file mode 100644
index 000000000..c7906f69f
--- /dev/null
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestInterceptor.java
@@ -0,0 +1,56 @@
+// Copyright 2023 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.impl;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Bundle;
+
+import org.chromium.base.test.util.PackageManagerWrapper;
+import org.chromium.net.ContextInterceptor;
+
+/**
+ * A {@link ContextInterceptor} that makes the intercepted Context advertise a specific set of
+ * Cronet manifest meta-data.
+ *
+ * @see org.chromium.net.impl.CronetManifest
+ */
+public final class CronetManifestInterceptor implements ContextInterceptor {
+ private final Bundle mMetaData;
+
+ /**
+ * @param metaData the meta-data to return in Cronet manifest meta-data queries on intercepted
+ * Contexts.
+ */
+ public CronetManifestInterceptor(Bundle metaData) {
+ mMetaData = metaData;
+ }
+
+ @Override
+ public Context interceptContext(Context context) {
+ return new ContextWrapper(context) {
+ @Override
+ public PackageManager getPackageManager() {
+ return new PackageManagerWrapper(super.getPackageManager()) {
+ @Override
+ public ServiceInfo getServiceInfo(ComponentName componentName, int flags)
+ throws NameNotFoundException {
+ if (!componentName.equals(new ComponentName(getBaseContext(),
+ CronetManifest.META_DATA_HOLDER_SERVICE_NAME))) {
+ return super.getServiceInfo(componentName, flags);
+ }
+
+ ServiceInfo serviceInfo = new ServiceInfo();
+ serviceInfo.metaData = mMetaData;
+ return serviceInfo;
+ }
+ };
+ }
+ };
+ }
+}
diff --git a/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestTest.java b/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestTest.java
index 9dd119c04..7e1bee85a 100644
--- a/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestTest.java
+++ b/components/cronet/android/test/javatests/src/org/chromium/net/impl/CronetManifestTest.java
@@ -4,15 +4,8 @@
package org.chromium.net.impl;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
-import static org.chromium.net.CronetTestRule.getContext;
-
-import android.content.Context;
-import android.content.ContextWrapper;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
import android.os.Bundle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -23,86 +16,73 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.chromium.base.test.util.PackageManagerWrapper;
-import org.chromium.net.CronetTestRule;
-import org.chromium.net.CronetTestRule.OnlyRunNativeCronet;
+import org.chromium.net.CronetTestRuleNew;
+import org.chromium.net.CronetTestRuleNew.CronetTestFramework;
+import org.chromium.net.CronetTestRuleNew.OnlyRunNativeCronet;
import org.chromium.net.impl.CronetLogger.CronetSource;
/**
* Tests {@link CronetManifest}
*/
@RunWith(AndroidJUnit4.class)
+@OnlyRunNativeCronet
public class CronetManifestTest {
@Rule
- public final CronetTestRule mTestRule = new CronetTestRule();
-
- private Context mMockContext;
- private Bundle mMetadata;
- private ApplicationInfo mAppInfo;
+ public final CronetTestRuleNew mTestRule = CronetTestRuleNew.withManualEngineStartup();
+ public CronetTestFramework mCronetTestFramework;
@Before
- public void setUp() throws Exception {
- mAppInfo = new ApplicationInfo();
- mMockContext = new MockContext(getContext());
- mMetadata = new Bundle();
+ public void setUp() {
+ mCronetTestFramework = mTestRule.getTestFramework();
+ }
+
+ private void setTelemetryOptIn(boolean value) {
+ Bundle metaData = new Bundle();
+ metaData.putBoolean(CronetManifest.ENABLE_TELEMETRY_META_DATA_KEY, value);
+ mCronetTestFramework.interceptContext(new CronetManifestInterceptor(metaData));
}
@Test
@SmallTest
- @OnlyRunNativeCronet
public void testTelemetryOptIn_whenNoMetadata() throws Exception {
- assertFalse(CronetManifest.isAppOptedInForTelemetry(
- mMockContext, CronetSource.CRONET_SOURCE_STATICALLY_LINKED));
- assertFalse(CronetManifest.isAppOptedInForTelemetry(
- mMockContext, CronetSource.CRONET_SOURCE_PLAY_SERVICES));
- assertFalse(CronetManifest.isAppOptedInForTelemetry(
- mMockContext, CronetSource.CRONET_SOURCE_FALLBACK));
+ assertThat(CronetManifest.isAppOptedInForTelemetry(mCronetTestFramework.getContext(),
+ CronetSource.CRONET_SOURCE_STATICALLY_LINKED))
+ .isFalse();
+ assertThat(CronetManifest.isAppOptedInForTelemetry(mCronetTestFramework.getContext(),
+ CronetSource.CRONET_SOURCE_PLAY_SERVICES))
+ .isFalse();
+ assertThat(CronetManifest.isAppOptedInForTelemetry(
+ mCronetTestFramework.getContext(), CronetSource.CRONET_SOURCE_FALLBACK))
+ .isFalse();
}
@Test
@SmallTest
- @OnlyRunNativeCronet
public void testTelemetryOptIn_whenMetadataIsTrue() throws Exception {
- mMetadata.putBoolean(CronetManifest.TELEMETRY_OPT_IN_META_DATA_STR, true);
- mAppInfo.metaData = mMetadata;
-
- assertTrue(CronetManifest.isAppOptedInForTelemetry(
- mMockContext, CronetSource.CRONET_SOURCE_STATICALLY_LINKED));
- assertTrue(CronetManifest.isAppOptedInForTelemetry(
- mMockContext, CronetSource.CRONET_SOURCE_PLAY_SERVICES));
- assertTrue(CronetManifest.isAppOptedInForTelemetry(
- mMockContext, CronetSource.CRONET_SOURCE_FALLBACK));
+ setTelemetryOptIn(true);
+ assertThat(CronetManifest.isAppOptedInForTelemetry(mCronetTestFramework.getContext(),
+ CronetSource.CRONET_SOURCE_STATICALLY_LINKED))
+ .isTrue();
+ assertThat(CronetManifest.isAppOptedInForTelemetry(mCronetTestFramework.getContext(),
+ CronetSource.CRONET_SOURCE_PLAY_SERVICES))
+ .isTrue();
+ assertThat(CronetManifest.isAppOptedInForTelemetry(
+ mCronetTestFramework.getContext(), CronetSource.CRONET_SOURCE_FALLBACK))
+ .isTrue();
}
@Test
@SmallTest
- @OnlyRunNativeCronet
public void testTelemetryOptIn_whenMetadataIsFalse() throws Exception {
- mMetadata.putBoolean(CronetManifest.TELEMETRY_OPT_IN_META_DATA_STR, false);
- mAppInfo.metaData = mMetadata;
-
- assertFalse(CronetManifest.isAppOptedInForTelemetry(
- mMockContext, CronetSource.CRONET_SOURCE_STATICALLY_LINKED));
- assertFalse(CronetManifest.isAppOptedInForTelemetry(
- mMockContext, CronetSource.CRONET_SOURCE_PLAY_SERVICES));
- assertFalse(CronetManifest.isAppOptedInForTelemetry(
- mMockContext, CronetSource.CRONET_SOURCE_FALLBACK));
- }
-
- private class MockContext extends ContextWrapper {
- public MockContext(Context base) {
- super(base);
- }
-
- @Override
- public PackageManager getPackageManager() {
- return new PackageManagerWrapper(super.getPackageManager()) {
- @Override
- public ApplicationInfo getApplicationInfo(String packageName, int flags)
- throws PackageManager.NameNotFoundException {
- return mAppInfo;
- }
- };
- }
+ setTelemetryOptIn(false);
+ assertThat(CronetManifest.isAppOptedInForTelemetry(mCronetTestFramework.getContext(),
+ CronetSource.CRONET_SOURCE_STATICALLY_LINKED))
+ .isFalse();
+ assertThat(CronetManifest.isAppOptedInForTelemetry(mCronetTestFramework.getContext(),
+ CronetSource.CRONET_SOURCE_PLAY_SERVICES))
+ .isFalse();
+ assertThat(CronetManifest.isAppOptedInForTelemetry(
+ mCronetTestFramework.getContext(), CronetSource.CRONET_SOURCE_FALLBACK))
+ .isFalse();
}
}