Support shared library compilation.
Bug: 254487975
Test: atest ArtServiceTests
Test: adb shell pm art optimize-package -m speed-profile -f --include-dependencies com.android.chrome
Ignore-AOSP-First: ART Services
Change-Id: I38b1acdf109f336ee6617cd6f22d30839dd070d8
diff --git a/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
index b8faf09..f71f46d 100644
--- a/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
+++ b/libartservice/service/javatests/com/android/server/art/DexOptHelperTest.java
@@ -27,7 +27,9 @@
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.same;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -38,16 +40,16 @@
import androidx.test.filters.SmallTest;
+import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.OptimizeParams;
import com.android.server.art.model.OptimizeResult;
-import com.android.server.art.testing.OnSuccessRule;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.AndroidPackageSplit;
import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedLibrary;
import org.junit.Before;
-import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
@@ -55,37 +57,46 @@
import org.mockito.junit.MockitoJUnitRunner;
import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
@SmallTest
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class DexOptHelperTest {
- private static final String PKG_NAME = "com.example.foo";
+ private static final String PKG_NAME_FOO = "com.example.foo";
+ private static final String PKG_NAME_BAR = "com.example.bar";
+ private static final String PKG_NAME_LIB1 = "com.example.lib1";
+ private static final String PKG_NAME_LIB2 = "com.example.lib2";
+ private static final String PKG_NAME_LIB3 = "com.example.lib3";
+ private static final String PKG_NAME_LIB4 = "com.example.lib4";
+ private static final String PKG_NAME_LIBBAZ = "com.example.libbaz";
@Mock private DexOptHelper.Injector mInjector;
@Mock private PrimaryDexOptimizer mPrimaryDexOptimizer;
+ @Mock private SecondaryDexOptimizer mSecondaryDexOptimizer;
@Mock private AppHibernationManager mAhm;
@Mock private PowerManager mPowerManager;
@Mock private PowerManager.WakeLock mWakeLock;
- private PackageState mPkgState;
- private AndroidPackage mPkg;
+ @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
+ private PackageState mPkgStateFoo;
+ private PackageState mPkgStateBar;
+ private PackageState mPkgStateLib1;
+ private PackageState mPkgStateLib2;
+ private PackageState mPkgStateLib4;
+ private PackageState mPkgStateLibbaz;
+ private AndroidPackage mPkgFoo;
+ private AndroidPackage mPkgBar;
+ private AndroidPackage mPkgLib1;
+ private AndroidPackage mPkgLib2;
+ private AndroidPackage mPkgLib4;
+ private AndroidPackage mPkgLibbaz;
private CancellationSignal mCancellationSignal;
-
- @Rule
- public OnSuccessRule onSuccessRule = new OnSuccessRule(() -> {
- // Don't do this on failure because it will make the failure hard to understand.
- verifyNoMoreInteractions(mPrimaryDexOptimizer);
- });
-
- private final OptimizeParams mParams =
- new OptimizeParams.Builder("install").setCompilerFilter("speed-profile").build();
- private final List<DexContainerFileOptimizeResult> mPrimaryResults = List.of(
- new DexContainerFileOptimizeResult("/data/app/foo/base.apk", true /* isPrimaryAbi */,
- "arm64-v8a", "verify", OptimizeResult.OPTIMIZE_PERFORMED,
- 100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */),
- new DexContainerFileOptimizeResult("/data/app/foo/base.apk", false /* isPrimaryAbi */,
- "armeabi-v7a", "verify", OptimizeResult.OPTIMIZE_FAILED,
- 100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */));
-
+ private ExecutorService mExecutor = Executors.newSingleThreadExecutor();
+ private List<DexContainerFileOptimizeResult> mPrimaryResults;
+ private List<DexContainerFileOptimizeResult> mSecondaryResults;
+ private OptimizeParams mParams;
+ private List<String> mRequestedPackages;
private DexOptHelper mDexOptHelper;
@Before
@@ -97,84 +108,283 @@
.when(mPowerManager.newWakeLock(eq(PowerManager.PARTIAL_WAKE_LOCK), any()))
.thenReturn(mWakeLock);
- lenient().when(mAhm.isHibernatingGlobally(PKG_NAME)).thenReturn(false);
+ lenient().when(mAhm.isHibernatingGlobally(any())).thenReturn(false);
lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(true);
- mPkgState = createPackageState();
- mPkg = mPkgState.getAndroidPackage();
mCancellationSignal = new CancellationSignal();
+ preparePackagesAndLibraries();
+
+ mPrimaryResults = createResults("/data/app/foo/base.apk", false /* partialFailure */);
+ mSecondaryResults =
+ createResults("/data/user_de/0/foo/foo.apk", false /* partialFailure */);
+
lenient()
- .when(mInjector.getPrimaryDexOptimizer(
- same(mPkgState), same(mPkg), same(mParams), same(mCancellationSignal)))
+ .when(mInjector.getPrimaryDexOptimizer(any(), any(), any(), any()))
.thenReturn(mPrimaryDexOptimizer);
+ lenient().when(mPrimaryDexOptimizer.dexopt()).thenReturn(mPrimaryResults);
+
+ lenient()
+ .when(mInjector.getSecondaryDexOptimizer(any(), any(), any(), any()))
+ .thenReturn(mSecondaryDexOptimizer);
+ lenient().when(mSecondaryDexOptimizer.dexopt()).thenReturn(mSecondaryResults);
+
+ mParams = new OptimizeParams.Builder("install")
+ .setCompilerFilter("speed-profile")
+ .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX
+ | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+ ArtFlags.FLAG_FOR_SECONDARY_DEX
+ | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+ .build();
mDexOptHelper = new DexOptHelper(mInjector);
}
@Test
public void testDexopt() throws Exception {
- when(mPrimaryDexOptimizer.dexopt()).thenReturn(mPrimaryResults);
+ // Only package libbaz fails.
+ var failingPrimaryDexOptimizer = mock(PrimaryDexOptimizer.class);
+ List<DexContainerFileOptimizeResult> partialFailureResults =
+ createResults("/data/app/foo/base.apk", true /* partialFailure */);
+ lenient().when(failingPrimaryDexOptimizer.dexopt()).thenReturn(partialFailureResults);
+ when(mInjector.getPrimaryDexOptimizer(same(mPkgStateLibbaz), any(), any(), any()))
+ .thenReturn(failingPrimaryDexOptimizer);
OptimizeResult result = mDexOptHelper.dexopt(
- mock(PackageManagerLocal.FilteredSnapshot.class), mPkgState, mPkg, mParams,
- mCancellationSignal);
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
assertThat(result.getRequestedCompilerFilter()).isEqualTo("speed-profile");
assertThat(result.getReason()).isEqualTo("install");
assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_FAILED);
- assertThat(result.getPackageOptimizeResults()).hasSize(1);
- PackageOptimizeResult packageResult = result.getPackageOptimizeResults().get(0);
- assertThat(packageResult.getPackageName()).isEqualTo(PKG_NAME);
- assertThat(packageResult.getDexContainerFileOptimizeResults())
- .containsExactlyElementsIn(mPrimaryResults);
+ // The requested packages must come first.
+ assertThat(result.getPackageOptimizeResults()).hasSize(6);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ, OptimizeResult.OPTIMIZE_FAILED,
+ List.of(partialFailureResults, mSecondaryResults));
+ checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
- InOrder inOrder = inOrder(mPrimaryDexOptimizer, mWakeLock);
+ // The order matters. It should acquire the wake lock only once, at the beginning, and
+ // release the wake lock at the end. When running in a single thread, it should dexopt
+ // primary dex files and the secondary dex files together for each package, and it should
+ // dexopt requested packages, in the given order, and then dexopt dependencies.
+ InOrder inOrder = inOrder(mInjector, mWakeLock);
+ inOrder.verify(mWakeLock).setWorkSource(any());
inOrder.verify(mWakeLock).acquire(anyLong());
- inOrder.verify(mPrimaryDexOptimizer).dexopt();
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateFoo), same(mPkgFoo), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateFoo), same(mPkgFoo), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateBar), same(mPkgBar), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateBar), same(mPkgBar), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateLibbaz), same(mPkgLibbaz), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateLib1), same(mPkgLib1), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateLib1), same(mPkgLib1), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateLib2), same(mPkgLib2), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateLib2), same(mPkgLib2), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateLib4), same(mPkgLib4), same(mParams), same(mCancellationSignal));
+ inOrder.verify(mInjector).getSecondaryDexOptimizer(
+ same(mPkgStateLib4), same(mPkgLib4), same(mParams), same(mCancellationSignal));
inOrder.verify(mWakeLock).release();
+
+ verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 6 /* expectedSecondaryTimes */);
+
+ verifyNoMoreInteractions(mWakeLock);
+ }
+
+ @Test
+ public void testDexoptNoDependencies() throws Exception {
+ mParams = new OptimizeParams.Builder("install")
+ .setCompilerFilter("speed-profile")
+ .setFlags(ArtFlags.FLAG_FOR_SECONDARY_DEX,
+ ArtFlags.FLAG_FOR_SECONDARY_DEX
+ | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+ .build();
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getPackageOptimizeResults()).hasSize(3);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ,
+ OptimizeResult.OPTIMIZE_PERFORMED, List.of(mPrimaryResults, mSecondaryResults));
+
+ verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 3 /* expectedSecondaryTimes */);
+ }
+
+ @Test
+ public void testDexoptPrimaryOnly() throws Exception {
+ mParams = new OptimizeParams.Builder("install")
+ .setCompilerFilter("speed-profile")
+ .setFlags(ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+ ArtFlags.FLAG_FOR_SECONDARY_DEX
+ | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+ .build();
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getPackageOptimizeResults()).hasSize(6);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ,
+ OptimizeResult.OPTIMIZE_PERFORMED, List.of(mPrimaryResults));
+ checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+
+ verifyNoMoreDexopt(6 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+ }
+
+ @Test
+ public void testDexoptPrimaryOnlyNoDependencies() throws Exception {
+ mParams = new OptimizeParams.Builder("install")
+ .setCompilerFilter("speed-profile")
+ .setFlags(0,
+ ArtFlags.FLAG_FOR_SECONDARY_DEX
+ | ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES)
+ .build();
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getPackageOptimizeResults()).hasSize(3);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults));
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ,
+ OptimizeResult.OPTIMIZE_PERFORMED, List.of(mPrimaryResults));
+
+ verifyNoMoreDexopt(3 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
+ }
+
+ @Test
+ public void testDexoptCancelledBetweenDex2oatInvocations() throws Exception {
+ when(mPrimaryDexOptimizer.dexopt()).thenAnswer(invocation -> {
+ mCancellationSignal.cancel();
+ return mPrimaryResults;
+ });
+
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_CANCELLED);
+
+ assertThat(result.getPackageOptimizeResults()).hasSize(6);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_CANCELLED,
+ List.of(mPrimaryResults));
+ checkPackageResult(
+ result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_CANCELLED, List.of());
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ,
+ OptimizeResult.OPTIMIZE_CANCELLED, List.of());
+ checkPackageResult(
+ result, 3 /* index */, PKG_NAME_LIB1, OptimizeResult.OPTIMIZE_CANCELLED, List.of());
+ checkPackageResult(
+ result, 4 /* index */, PKG_NAME_LIB2, OptimizeResult.OPTIMIZE_CANCELLED, List.of());
+ checkPackageResult(
+ result, 5 /* index */, PKG_NAME_LIB4, OptimizeResult.OPTIMIZE_CANCELLED, List.of());
+
+ verify(mInjector).getPrimaryDexOptimizer(
+ same(mPkgStateFoo), same(mPkgFoo), same(mParams), same(mCancellationSignal));
+
+ verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 0 /* expectedSecondaryTimes */);
}
@Test
public void testDexoptNoCode() throws Exception {
- when(mPkg.getSplits().get(0).isHasCode()).thenReturn(false);
+ when(mPkgFoo.getSplits().get(0).isHasCode()).thenReturn(false);
+ mRequestedPackages = List.of(PKG_NAME_FOO);
OptimizeResult result = mDexOptHelper.dexopt(
- mock(PackageManagerLocal.FilteredSnapshot.class), mPkgState, mPkg, mParams,
- mCancellationSignal);
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_SKIPPED);
- assertThat(result.getPackageOptimizeResults().get(0).getDexContainerFileOptimizeResults())
- .isEmpty();
+ assertThat(result.getPackageOptimizeResults()).hasSize(1);
+ checkPackageResult(
+ result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_SKIPPED, List.of());
+
+ verifyNoDexopt();
+ }
+
+ @Test
+ public void testDexoptLibraryNoCode() throws Exception {
+ when(mPkgLib1.getSplits().get(0).isHasCode()).thenReturn(false);
+
+ mRequestedPackages = List.of(PKG_NAME_FOO);
+ OptimizeResult result = mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_PERFORMED);
+ assertThat(result.getPackageOptimizeResults()).hasSize(1);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+
+ verifyNoMoreDexopt(1 /* expectedPrimaryTimes */, 1 /* expectedSecondaryTimes */);
}
@Test
public void testDexoptIsHibernating() throws Exception {
- lenient().when(mAhm.isHibernatingGlobally(PKG_NAME)).thenReturn(true);
+ lenient().when(mAhm.isHibernatingGlobally(PKG_NAME_FOO)).thenReturn(true);
+ mRequestedPackages = List.of(PKG_NAME_FOO);
OptimizeResult result = mDexOptHelper.dexopt(
- mock(PackageManagerLocal.FilteredSnapshot.class), mPkgState, mPkg, mParams,
- mCancellationSignal);
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
assertThat(result.getFinalStatus()).isEqualTo(OptimizeResult.OPTIMIZE_SKIPPED);
- assertThat(result.getPackageOptimizeResults().get(0).getDexContainerFileOptimizeResults())
- .isEmpty();
+ checkPackageResult(
+ result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_SKIPPED, List.of());
+
+ verifyNoDexopt();
}
@Test
public void testDexoptIsHibernatingButOatArtifactDeletionDisabled() throws Exception {
- lenient().when(mAhm.isHibernatingGlobally(PKG_NAME)).thenReturn(true);
+ lenient().when(mAhm.isHibernatingGlobally(PKG_NAME_FOO)).thenReturn(true);
lenient().when(mAhm.isOatArtifactDeletionEnabled()).thenReturn(false);
- when(mPrimaryDexOptimizer.dexopt()).thenReturn(mPrimaryResults);
-
OptimizeResult result = mDexOptHelper.dexopt(
- mock(PackageManagerLocal.FilteredSnapshot.class), mPkgState, mPkg, mParams,
- mCancellationSignal);
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
- assertThat(result.getPackageOptimizeResults().get(0).getDexContainerFileOptimizeResults())
- .containsExactlyElementsIn(mPrimaryResults);
+ assertThat(result.getPackageOptimizeResults()).hasSize(6);
+ checkPackageResult(result, 0 /* index */, PKG_NAME_FOO, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 1 /* index */, PKG_NAME_BAR, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 2 /* index */, PKG_NAME_LIBBAZ,
+ OptimizeResult.OPTIMIZE_PERFORMED, List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 3 /* index */, PKG_NAME_LIB1, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 4 /* index */, PKG_NAME_LIB2, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
+ checkPackageResult(result, 5 /* index */, PKG_NAME_LIB4, OptimizeResult.OPTIMIZE_PERFORMED,
+ List.of(mPrimaryResults, mSecondaryResults));
}
@Test
@@ -182,14 +392,34 @@
when(mPrimaryDexOptimizer.dexopt()).thenThrow(IllegalStateException.class);
try {
- mDexOptHelper.dexopt(mock(PackageManagerLocal.FilteredSnapshot.class), mPkgState, mPkg,
- mParams, mCancellationSignal);
+ mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
} catch (Exception ignored) {
}
verify(mWakeLock).release();
}
+ @Test(expected = IllegalArgumentException.class)
+ public void testDexoptPackageNotFound() throws Exception {
+ when(mSnapshot.getPackageState(any())).thenReturn(null);
+
+ mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ verifyNoDexopt();
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testDexoptNoPackage() throws Exception {
+ lenient().when(mPkgStateFoo.getAndroidPackage()).thenReturn(null);
+
+ mDexOptHelper.dexopt(
+ mSnapshot, mRequestedPackages, mParams, mCancellationSignal, mExecutor);
+
+ verifyNoDexopt();
+ }
+
private AndroidPackage createPackage() {
AndroidPackage pkg = mock(AndroidPackage.class);
var baseSplit = mock(AndroidPackageSplit.class);
@@ -198,12 +428,109 @@
return pkg;
}
- private PackageState createPackageState() {
+ private PackageState createPackageState(String packageName, List<SharedLibrary> deps) {
PackageState pkgState = mock(PackageState.class);
- lenient().when(pkgState.getPackageName()).thenReturn(PKG_NAME);
+ lenient().when(pkgState.getPackageName()).thenReturn(packageName);
lenient().when(pkgState.getAppId()).thenReturn(12345);
+ lenient().when(pkgState.getUsesLibraries()).thenReturn(deps);
AndroidPackage pkg = createPackage();
lenient().when(pkgState.getAndroidPackage()).thenReturn(pkg);
return pkgState;
}
+
+ private SharedLibrary createLibrary(
+ String libraryName, String packageName, List<SharedLibrary> deps) {
+ SharedLibrary library = mock(SharedLibrary.class);
+ lenient().when(library.getName()).thenReturn(libraryName);
+ lenient().when(library.getPackageName()).thenReturn(packageName);
+ lenient().when(library.getDependencies()).thenReturn(deps);
+ return library;
+ }
+
+ private void preparePackagesAndLibraries() {
+ // Dependency graph:
+ // foo bar
+ // | |
+ // lib1a (lib1) lib1b (lib1) lib1c (lib1)
+ // / \ / \ |
+ // / \ / \ |
+ // libbaz (libbaz) lib2 (lib2) lib4 (lib4) lib3 (lib3)
+ //
+ // "lib1a", "lib1b", and "lib1c" belong to the same package "lib1".
+
+ mRequestedPackages = List.of(PKG_NAME_FOO, PKG_NAME_BAR, PKG_NAME_LIBBAZ);
+
+ SharedLibrary libbaz = createLibrary("libbaz", PKG_NAME_LIBBAZ, List.of());
+ SharedLibrary lib4 = createLibrary("lib4", PKG_NAME_LIB4, List.of());
+ SharedLibrary lib3 = createLibrary("lib3", PKG_NAME_LIB3, List.of());
+ SharedLibrary lib2 = createLibrary("lib2", PKG_NAME_LIB2, List.of());
+ SharedLibrary lib1a = createLibrary("lib1a", PKG_NAME_LIB1, List.of(libbaz, lib2));
+ SharedLibrary lib1b = createLibrary("lib1b", PKG_NAME_LIB1, List.of(lib2, lib4));
+ SharedLibrary lib1c = createLibrary("lib1c", PKG_NAME_LIB1, List.of(lib3));
+
+ mPkgStateFoo = createPackageState(PKG_NAME_FOO, List.of(lib1a));
+ mPkgFoo = mPkgStateFoo.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_FOO)).thenReturn(mPkgStateFoo);
+
+ mPkgStateBar = createPackageState(PKG_NAME_BAR, List.of(lib1b));
+ mPkgBar = mPkgStateBar.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_BAR)).thenReturn(mPkgStateBar);
+
+ mPkgStateLib1 = createPackageState(PKG_NAME_LIB1, List.of(libbaz, lib2, lib3, lib4));
+ mPkgLib1 = mPkgStateLib1.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB1)).thenReturn(mPkgStateLib1);
+
+ mPkgStateLib2 = createPackageState(PKG_NAME_LIB2, List.of());
+ mPkgLib2 = mPkgStateLib2.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB2)).thenReturn(mPkgStateLib2);
+
+ // This should not be considered as a transitive dependency of any requested package, even
+ // though it is a dependency of package "lib1".
+ PackageState pkgStateLib3 = createPackageState(PKG_NAME_LIB3, List.of());
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB3)).thenReturn(pkgStateLib3);
+
+ mPkgStateLib4 = createPackageState(PKG_NAME_LIB4, List.of());
+ mPkgLib4 = mPkgStateLib4.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_LIB4)).thenReturn(mPkgStateLib4);
+
+ mPkgStateLibbaz = createPackageState(PKG_NAME_LIBBAZ, List.of());
+ mPkgLibbaz = mPkgStateLibbaz.getAndroidPackage();
+ lenient().when(mSnapshot.getPackageState(PKG_NAME_LIBBAZ)).thenReturn(mPkgStateLibbaz);
+ }
+
+ private void verifyNoDexopt() {
+ verify(mInjector, never()).getPrimaryDexOptimizer(any(), any(), any(), any());
+ verify(mInjector, never()).getSecondaryDexOptimizer(any(), any(), any(), any());
+ }
+
+ private void verifyNoMoreDexopt(int expectedPrimaryTimes, int expectedSecondaryTimes) {
+ verify(mInjector, times(expectedPrimaryTimes))
+ .getPrimaryDexOptimizer(any(), any(), any(), any());
+ verify(mInjector, times(expectedSecondaryTimes))
+ .getSecondaryDexOptimizer(any(), any(), any(), any());
+ }
+
+ private List<DexContainerFileOptimizeResult> createResults(
+ String dexPath, boolean partialFailure) {
+ return List.of(new DexContainerFileOptimizeResult(dexPath, true /* isPrimaryAbi */,
+ "arm64-v8a", "verify", OptimizeResult.OPTIMIZE_PERFORMED,
+ 100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */),
+ new DexContainerFileOptimizeResult(dexPath, false /* isPrimaryAbi */, "armeabi-v7a",
+ "verify",
+ partialFailure ? OptimizeResult.OPTIMIZE_FAILED
+ : OptimizeResult.OPTIMIZE_PERFORMED,
+ 100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */));
+ }
+
+ private void checkPackageResult(OptimizeResult result, int index, String packageName,
+ @OptimizeResult.OptimizeStatus int status,
+ List<List<DexContainerFileOptimizeResult>> dexContainerFileOptimizeResults) {
+ PackageOptimizeResult packageResult = result.getPackageOptimizeResults().get(index);
+ assertThat(packageResult.getPackageName()).isEqualTo(packageName);
+ assertThat(packageResult.getStatus()).isEqualTo(status);
+ assertThat(packageResult.getDexContainerFileOptimizeResults())
+ .containsExactlyElementsIn(dexContainerFileOptimizeResults.stream()
+ .flatMap(r -> r.stream())
+ .collect(Collectors.toList()));
+ }
}