diff options
author | Stefano Duo <stefanoduo@google.com> | 2023-08-08 12:39:48 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-08-08 12:39:48 +0000 |
commit | 61192f8c0bbef2ddcfbc241276f7f808c0e52616 (patch) | |
tree | 5bc207a56ac5369e9e3e661c61515772a7081174 | |
parent | 01d46eea6b265a64bea0dfc983e9383b3ea9be4b (diff) | |
parent | 8c40b44bf9ffa410fb5ef1c1c815b6bbd6e5d1f4 (diff) | |
download | cronet-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>
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(); } } |