| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * 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 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| package android.compat.sjp.cts; |
| |
| import static android.compat.testing.Classpaths.ClasspathType.BOOTCLASSPATH; |
| import static android.compat.testing.Classpaths.ClasspathType.SYSTEMSERVERCLASSPATH; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import static org.junit.Assume.assumeTrue; |
| |
| import android.compat.testing.Classpaths; |
| |
| import com.android.compatibility.common.util.ApiLevelUtil; |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| import com.android.tradefed.log.LogUtil; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; |
| import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; |
| |
| import com.google.common.collect.ArrayListMultimap; |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.ImmutableCollection; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Multimap; |
| import com.google.common.collect.Multimaps; |
| |
| import org.jf.dexlib2.iface.ClassDef; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| /** |
| * Tests for detecting no duplicate class files are present on BOOTCLASSPATH and |
| * SYSTEMSERVERCLASSPATH. |
| * |
| * <p>Duplicate class files are not safe as some of the jars on *CLASSPATH are updated outside of |
| * the main dessert release cycle; they also contribute to unnecessary disk space usage. |
| */ |
| @RunWith(DeviceJUnit4ClassRunner.class) |
| public class StrictJavaPackagesTest extends BaseHostJUnit4Test { |
| |
| /** |
| * This is the list of classes that are currently duplicated and should be addressed. |
| * |
| * <p> DO NOT ADD CLASSES TO THIS LIST! |
| */ |
| private static final Set<String> BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST = |
| ImmutableSet.of( |
| "Landroid/annotation/AnimatorRes;", |
| "Landroid/annotation/AnimRes;", |
| "Landroid/annotation/AnyRes;", |
| "Landroid/annotation/AnyThread;", |
| "Landroid/annotation/AppIdInt;", |
| "Landroid/annotation/ArrayRes;", |
| "Landroid/annotation/AttrRes;", |
| "Landroid/annotation/BinderThread;", |
| "Landroid/annotation/BoolRes;", |
| "Landroid/annotation/BroadcastBehavior;", |
| "Landroid/annotation/BytesLong;", |
| "Landroid/annotation/CallbackExecutor;", |
| "Landroid/annotation/CallSuper;", |
| "Landroid/annotation/CheckResult;", |
| "Landroid/annotation/ColorInt;", |
| "Landroid/annotation/ColorLong;", |
| "Landroid/annotation/ColorRes;", |
| "Landroid/annotation/Condemned;", |
| "Landroid/annotation/CurrentTimeMillisLong;", |
| "Landroid/annotation/CurrentTimeSecondsLong;", |
| "Landroid/annotation/DimenRes;", |
| "Landroid/annotation/Dimension;", |
| "Landroid/annotation/Discouraged;", |
| "Landroid/annotation/DisplayContext;", |
| "Landroid/annotation/DrawableRes;", |
| "Landroid/annotation/DurationMillisLong;", |
| "Landroid/annotation/ElapsedRealtimeLong;", |
| "Landroid/annotation/EnforcePermission;", |
| "Landroid/annotation/FloatRange;", |
| "Landroid/annotation/FontRes;", |
| "Landroid/annotation/FractionRes;", |
| "Landroid/annotation/HalfFloat;", |
| "Landroid/annotation/Hide;", |
| "Landroid/annotation/IdRes;", |
| "Landroid/annotation/IntDef;", |
| "Landroid/annotation/IntegerRes;", |
| "Landroid/annotation/InterpolatorRes;", |
| "Landroid/annotation/IntRange;", |
| "Landroid/annotation/LayoutRes;", |
| "Landroid/annotation/LongDef;", |
| "Landroid/annotation/MainThread;", |
| "Landroid/annotation/MenuRes;", |
| "Landroid/annotation/NavigationRes;", |
| "Landroid/annotation/NonNull;", |
| "Landroid/annotation/NonUiContext;", |
| "Landroid/annotation/Nullable;", |
| "Landroid/annotation/PluralsRes;", |
| "Landroid/annotation/Px;", |
| "Landroid/annotation/RawRes;", |
| "Landroid/annotation/RequiresFeature;", |
| "Landroid/annotation/RequiresNoPermission;", |
| "Landroid/annotation/RequiresPermission;", |
| "Landroid/annotation/SdkConstant;", |
| "Landroid/annotation/Size;", |
| "Landroid/annotation/StringDef;", |
| "Landroid/annotation/StringRes;", |
| "Landroid/annotation/StyleableRes;", |
| "Landroid/annotation/StyleRes;", |
| "Landroid/annotation/SuppressAutoDoc;", |
| "Landroid/annotation/SuppressLint;", |
| "Landroid/annotation/SystemApi;", |
| "Landroid/annotation/SystemService;", |
| "Landroid/annotation/TargetApi;", |
| "Landroid/annotation/TestApi;", |
| "Landroid/annotation/TransitionRes;", |
| "Landroid/annotation/UiContext;", |
| "Landroid/annotation/UiThread;", |
| "Landroid/annotation/UptimeMillisLong;", |
| "Landroid/annotation/UserHandleAware;", |
| "Landroid/annotation/UserIdInt;", |
| "Landroid/annotation/Widget;", |
| "Landroid/annotation/WorkerThread;", |
| "Landroid/annotation/XmlRes;", |
| "Landroid/gsi/AvbPublicKey;", |
| "Landroid/gsi/GsiProgress;", |
| "Landroid/gsi/IGsiService;", |
| "Landroid/gsi/IGsiServiceCallback;", |
| "Landroid/gsi/IImageService;", |
| "Landroid/gsi/IProgressCallback;", |
| "Landroid/gsi/MappedImage;", |
| "Landroid/gui/TouchOcclusionMode;", |
| // TODO(b/227752875): contexthub V1 APIs can be removed |
| // from T+ with the fix in aosp/2050305. |
| "Landroid/hardware/contexthub/V1_0/AsyncEventType;", |
| "Landroid/hardware/contexthub/V1_0/ContextHub;", |
| "Landroid/hardware/contexthub/V1_0/ContextHubMsg;", |
| "Landroid/hardware/contexthub/V1_0/HostEndPoint;", |
| "Landroid/hardware/contexthub/V1_0/HubAppInfo;", |
| "Landroid/hardware/contexthub/V1_0/HubMemoryFlag;", |
| "Landroid/hardware/contexthub/V1_0/HubMemoryType;", |
| "Landroid/hardware/contexthub/V1_0/IContexthub;", |
| "Landroid/hardware/contexthub/V1_0/IContexthubCallback;", |
| "Landroid/hardware/contexthub/V1_0/MemRange;", |
| "Landroid/hardware/contexthub/V1_0/NanoAppBinary;", |
| "Landroid/hardware/contexthub/V1_0/NanoAppFlags;", |
| "Landroid/hardware/contexthub/V1_0/PhysicalSensor;", |
| "Landroid/hardware/contexthub/V1_0/Result;", |
| "Landroid/hardware/contexthub/V1_0/SensorType;", |
| "Landroid/hardware/contexthub/V1_0/TransactionResult;", |
| "Landroid/hardware/usb/gadget/V1_0/GadgetFunction;", |
| "Landroid/hardware/usb/gadget/V1_0/IUsbGadget;", |
| "Landroid/hardware/usb/gadget/V1_0/IUsbGadgetCallback;", |
| "Landroid/hardware/usb/gadget/V1_0/Status;", |
| "Landroid/os/IDumpstate;", |
| "Landroid/os/IDumpstateListener;", |
| "Landroid/os/IInstalld;", |
| "Landroid/os/IStoraged;", |
| "Landroid/os/IVold;", |
| "Landroid/os/IVoldListener;", |
| "Landroid/os/IVoldMountCallback;", |
| "Landroid/os/IVoldTaskListener;", |
| "Landroid/os/storage/CrateMetadata;", |
| "Landroid/view/LayerMetadataKey;", |
| "Lcom/android/internal/annotations/CompositeRWLock;", |
| "Lcom/android/internal/annotations/GuardedBy;", |
| "Lcom/android/internal/annotations/Immutable;", |
| "Lcom/android/internal/annotations/VisibleForNative;", |
| "Lcom/android/internal/annotations/VisibleForTesting;", |
| // TODO(b/173649240): due to an oversight, some new overlaps slipped through |
| // in S. |
| "Landroid/hardware/usb/gadget/V1_1/IUsbGadget;", |
| "Landroid/hardware/usb/gadget/V1_2/GadgetFunction;", |
| "Landroid/hardware/usb/gadget/V1_2/IUsbGadget;", |
| "Landroid/hardware/usb/gadget/V1_2/IUsbGadgetCallback;", |
| "Landroid/hardware/usb/gadget/V1_2/UsbSpeed;", |
| "Landroid/os/BlockUntrustedTouchesMode;", |
| "Landroid/os/CreateAppDataArgs;", |
| "Landroid/os/CreateAppDataResult;", |
| "Landroid/os/IInputConstants;", |
| "Landroid/os/InputEventInjectionResult;", |
| "Landroid/os/InputEventInjectionSync;", |
| "Landroid/os/ReconcileSdkDataArgs;", |
| "Lcom/android/internal/util/FrameworkStatsLog;" |
| ); |
| |
| private static final String FEATURE_WEARABLE = "android.hardware.type.watch"; |
| private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive"; |
| |
| private static final Set<String> WEAR_HIDL_OVERLAP_BURNDOWN_LIST = |
| ImmutableSet.of( |
| "Landroid/hidl/base/V1_0/DebugInfo$Architecture;", |
| "Landroid/hidl/base/V1_0/IBase;", |
| "Landroid/hidl/base/V1_0/IBase$Proxy;", |
| "Landroid/hidl/base/V1_0/IBase$Stub;", |
| "Landroid/hidl/base/V1_0/DebugInfo;", |
| "Landroid/hidl/safe_union/V1_0/Monostate;" |
| ); |
| |
| private static final Set<String> AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST = |
| ImmutableSet.of( |
| "Landroid/hidl/base/V1_0/DebugInfo$Architecture;", |
| "Landroid/hidl/base/V1_0/IBase;", |
| "Landroid/hidl/base/V1_0/IBase$Proxy;", |
| "Landroid/hidl/base/V1_0/IBase$Stub;", |
| "Landroid/hidl/base/V1_0/DebugInfo;" |
| ); |
| |
| /** |
| * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH. |
| */ |
| @Test |
| public void testBootclasspath_nonDuplicateClasses() throws Exception { |
| assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29)); |
| ImmutableList<String> jars = |
| Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH); |
| assertThat(getDuplicateClasses(jars)).isEmpty(); |
| } |
| |
| /** |
| * Ensure that there are no duplicate classes among jars listed in SYSTEMSERVERCLASSPATH. |
| */ |
| @Test |
| public void testSystemServerClasspath_nonDuplicateClasses() throws Exception { |
| assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29)); |
| ImmutableList<String> jars = |
| Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH); |
| ImmutableSet<String> overlapBurndownList; |
| if (hasFeature(FEATURE_AUTOMOTIVE)) { |
| overlapBurndownList = ImmutableSet.copyOf(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST); |
| } else if (hasFeature(FEATURE_WEARABLE)) { |
| overlapBurndownList = ImmutableSet.copyOf(WEAR_HIDL_OVERLAP_BURNDOWN_LIST); |
| } else { |
| overlapBurndownList = ImmutableSet.of(); |
| } |
| Multimap<String, String> duplicates = getDuplicateClasses(jars); |
| Multimap<String, String> filtered = Multimaps.filterKeys(duplicates, |
| duplicate -> !overlapBurndownList.contains(duplicate)); |
| |
| assertThat(filtered).isEmpty(); |
| } |
| |
| /** |
| * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH and |
| * SYSTEMSERVERCLASSPATH. |
| */ |
| @Test |
| public void testBootClasspathAndSystemServerClasspath_nonDuplicateClasses() throws Exception { |
| assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29)); |
| ImmutableList.Builder<String> jars = ImmutableList.builder(); |
| jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH)); |
| jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH)); |
| ImmutableSet<String> overlapBurndownList; |
| if (hasFeature(FEATURE_AUTOMOTIVE)) { |
| overlapBurndownList = ImmutableSet.<String>builder() |
| .addAll(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST) |
| .addAll(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST).build(); |
| } else if (hasFeature(FEATURE_WEARABLE)) { |
| overlapBurndownList = ImmutableSet.<String>builder() |
| .addAll(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST) |
| .addAll(WEAR_HIDL_OVERLAP_BURNDOWN_LIST).build(); |
| } else { |
| overlapBurndownList = ImmutableSet.copyOf(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST); |
| } |
| Multimap<String, String> duplicates = getDuplicateClasses(jars.build()); |
| Multimap<String, String> filtered = Multimaps.filterKeys(duplicates, |
| duplicate -> !overlapBurndownList.contains(duplicate)); |
| |
| assertThat(filtered).isEmpty(); |
| } |
| |
| /** |
| * Ensure that there are no duplicate classes among APEX jars listed in BOOTCLASSPATH. |
| */ |
| @Test |
| public void testBootClasspath_nonDuplicateApexJarClasses() throws Exception { |
| ImmutableList<String> jars = |
| Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH); |
| |
| Multimap<String, String> duplicates = getDuplicateClasses(jars); |
| Multimap<String, String> filtered = |
| Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/")); |
| |
| assertThat(filtered).isEmpty(); |
| } |
| |
| /** |
| * Ensure that there are no duplicate classes among APEX jars listed in SYSTEMSERVERCLASSPATH. |
| */ |
| @Test |
| public void testSystemServerClasspath_nonDuplicateApexJarClasses() throws Exception { |
| ImmutableList<String> jars = |
| Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH); |
| |
| Multimap<String, String> duplicates = getDuplicateClasses(jars); |
| Multimap<String, String> filtered = |
| Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/")); |
| |
| assertThat(filtered).isEmpty(); |
| } |
| |
| /** |
| * Ensure that there are no duplicate classes among APEX jars listed in BOOTCLASSPATH and |
| * SYSTEMSERVERCLASSPATH. |
| */ |
| @Test |
| public void testBootClasspathAndSystemServerClasspath_nonApexDuplicateClasses() |
| throws Exception { |
| ImmutableList.Builder<String> jars = ImmutableList.builder(); |
| jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH)); |
| jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH)); |
| |
| Multimap<String, String> duplicates = getDuplicateClasses(jars.build()); |
| Multimap<String, String> filtered = Multimaps.filterKeys(duplicates, |
| duplicate -> !BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST.contains(duplicate)); |
| filtered = Multimaps.filterValues(filtered, jar -> jar.startsWith("/apex/")); |
| |
| assertThat(filtered).isEmpty(); |
| } |
| |
| /** |
| * Gets the duplicate classes within a list of jar files. |
| * |
| * @param jars a list of jar files. |
| * @return a multimap with the class name as a key and the jar files as a value. |
| */ |
| private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars) |
| throws Exception { |
| final Multimap<String, String> allClasses = HashMultimap.create(); |
| for (String jar : jars) { |
| ImmutableSet<ClassDef> classes = Classpaths.getClassDefsFromJar(getDevice(), jar); |
| for (ClassDef classDef : classes) { |
| // No need to worry about inner classes, as they always go with their parent. |
| if (!classDef.getType().contains("$")) { |
| allClasses.put(classDef.getType(), jar); |
| } |
| } |
| } |
| |
| final Multimap<String, String> duplicates = HashMultimap.create(); |
| for (String clazz : allClasses.keySet()) { |
| Collection<String> jarsWithClazz = allClasses.get(clazz); |
| if (jarsWithClazz.size() > 1) { |
| CLog.i("Class %s is duplicated in %s", clazz, jarsWithClazz); |
| duplicates.putAll(clazz, jarsWithClazz); |
| } |
| } |
| |
| return duplicates; |
| } |
| |
| private boolean hasFeature(String featureName) throws DeviceNotAvailableException { |
| return getDevice().executeShellCommand("pm list features").contains(featureName); |
| } |
| } |