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/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
index 7b85131..1685174 100644
--- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
+++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java
@@ -48,6 +48,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.Executors;
/**
* This class provides a system API for functionality provided by the ART module.
@@ -262,15 +263,15 @@
throw new IllegalArgumentException("Nothing to optimize");
}
- PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
- AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
-
- try {
- return mInjector.getDexOptHelper().dexopt(snapshot, pkgState, pkg, params,
- cancellationSignal);
- } catch (RemoteException e) {
- throw new IllegalStateException("An error occurred when calling artd", e);
+ if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) == 0
+ && (params.getFlags() & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0) {
+ throw new IllegalArgumentException(
+ "FLAG_SHOULD_INCLUDE_DEPENDENCIES must not set if FLAG_FOR_PRIMARY_DEX is not "
+ + "set.");
}
+
+ return mInjector.getDexOptHelper().dexopt(
+ snapshot, List.of(packageName), params, cancellationSignal, Runnable::run);
}
/**
diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
index 2c90fb6..0e0d80c 100644
--- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java
+++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java
@@ -95,6 +95,10 @@
ArtFlags.FLAG_FOR_PRIMARY_DEX
| ArtFlags.FLAG_FOR_SECONDARY_DEX);
break;
+ case "--include-dependencies":
+ paramsBuilder.setFlags(ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES,
+ ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES);
+ break;
default:
pw.println("Error: Unknown option: " + opt);
return 1;
@@ -232,6 +236,7 @@
pw.println(" -m Set the compiler filter.");
pw.println(" -f Force compilation.");
pw.println(" --secondary-dex Only compile secondary dex.");
+ pw.println(" --include-dependencies Include dependencies.");
pw.println(" cancel JOB_ID");
pw.println(" Cancel a job.");
pw.println(" dex-use-notify PACKAGE_NAME DEX_PATH CLASS_LOADER_CONTEXT");
diff --git a/libartservice/service/java/com/android/server/art/DexOptHelper.java b/libartservice/service/java/com/android/server/art/DexOptHelper.java
index bf84e48..b30e25d 100644
--- a/libartservice/service/java/com/android/server/art/DexOptHelper.java
+++ b/libartservice/service/java/com/android/server/art/DexOptHelper.java
@@ -35,10 +35,21 @@
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
+import com.android.server.pm.pkg.SharedLibrary;
import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
import java.util.List;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* A helper class to handle dexopt.
@@ -51,10 +62,10 @@
private static final String TAG = "DexoptHelper";
/**
- * Timeout of the wake lock. This is required by AndroidLint, but we set it to a value larger
- * than artd's {@code kLongTimeoutSec} so that it should normally never triggered.
+ * Timeout of the wake lock. This is required by AndroidLint, but we set it to a very large
+ * value so that it should normally never triggered.
*/
- private static final int WAKE_LOCK_TIMEOUT_MS = 11 * 60 * 1000; // 11 minutes.
+ private static final long WAKE_LOCK_TIMEOUT_MS = TimeUnit.DAYS.toMillis(1);
@NonNull private final Injector mInjector;
@@ -74,21 +85,24 @@
*/
@NonNull
public OptimizeResult dexopt(@NonNull PackageManagerLocal.FilteredSnapshot snapshot,
- @NonNull PackageState pkgState, @NonNull AndroidPackage pkg,
- @NonNull OptimizeParams params, @NonNull CancellationSignal cancellationSignal)
- throws RemoteException {
- List<DexContainerFileOptimizeResult> results = new ArrayList<>();
- Supplier<OptimizeResult> createResult = ()
- -> new OptimizeResult(params.getCompilerFilter(), params.getReason(),
- List.of(new PackageOptimizeResult(pkgState.getPackageName(), results)));
- Supplier<Boolean> hasCancelledResult = ()
- -> results.stream().anyMatch(
- result -> result.getStatus() == OptimizeResult.OPTIMIZE_CANCELLED);
+ @NonNull List<String> packageNames, @NonNull OptimizeParams params,
+ @NonNull CancellationSignal cancellationSignal, @NonNull Executor executor) {
+ return dexoptPackages(
+ getPackageStates(snapshot, packageNames,
+ (params.getFlags() & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0),
+ params, cancellationSignal, executor);
+ }
- if (!canOptimizePackage(pkgState, pkg)) {
- return createResult.get();
- }
-
+ /**
+ * DO NOT use this method directly. Use {@link
+ * ArtManagerLocal#optimizePackage(PackageManagerLocal.FilteredSnapshot, String,
+ * OptimizeParams)}.
+ */
+ @NonNull
+ private OptimizeResult dexoptPackages(@NonNull List<PackageState> pkgStates,
+ @NonNull OptimizeParams params, @NonNull CancellationSignal cancellationSignal,
+ @NonNull Executor executor) {
+ int callingUid = Binder.getCallingUid();
long identityToken = Binder.clearCallingIdentity();
PowerManager.WakeLock wakeLock = null;
@@ -96,39 +110,70 @@
// Acquire a wake lock.
PowerManager powerManager = mInjector.getPowerManager();
wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
- wakeLock.setWorkSource(new WorkSource(pkgState.getAppId()));
+ wakeLock.setWorkSource(new WorkSource(callingUid));
wakeLock.acquire(WAKE_LOCK_TIMEOUT_MS);
- if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
- results.addAll(
- mInjector.getPrimaryDexOptimizer(pkgState, pkg, params, cancellationSignal)
- .dexopt());
- if (hasCancelledResult.get()) {
- return createResult.get();
- }
+ List<Future<PackageOptimizeResult>> futures = new ArrayList<>();
+ for (PackageState pkgState : pkgStates) {
+ futures.add(Utils.execute(
+ executor, () -> dexoptPackage(pkgState, params, cancellationSignal)));
}
- if ((params.getFlags() & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
- results.addAll(
- mInjector
- .getSecondaryDexOptimizer(pkgState, pkg, params, cancellationSignal)
- .dexopt());
- if (hasCancelledResult.get()) {
- return createResult.get();
- }
- }
+ List<PackageOptimizeResult> results =
+ futures.stream().map(Utils::getFuture).collect(Collectors.toList());
- if ((params.getFlags() & ArtFlags.FLAG_SHOULD_INCLUDE_DEPENDENCIES) != 0) {
- // TODO(jiakaiz): Implement this.
- throw new UnsupportedOperationException(
- "Optimizing dependencies is not implemented yet");
- }
+ return new OptimizeResult(params.getCompilerFilter(), params.getReason(), results);
} finally {
if (wakeLock != null) {
wakeLock.release();
}
Binder.restoreCallingIdentity(identityToken);
}
+ }
+
+ /**
+ * DO NOT use this method directly. Use {@link
+ * ArtManagerLocal#optimizePackage(PackageManagerLocal.FilteredSnapshot, String,
+ * OptimizeParams)}.
+ */
+ @NonNull
+ private PackageOptimizeResult dexoptPackage(@NonNull PackageState pkgState,
+ @NonNull OptimizeParams params, @NonNull CancellationSignal cancellationSignal) {
+ List<DexContainerFileOptimizeResult> results = new ArrayList<>();
+ Supplier<PackageOptimizeResult> createResult = ()
+ -> new PackageOptimizeResult(
+ pkgState.getPackageName(), results, cancellationSignal.isCanceled());
+
+ AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+
+ if (!canOptimizePackage(pkgState, pkg)) {
+ return createResult.get();
+ }
+
+ try {
+ if ((params.getFlags() & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) {
+ if (cancellationSignal.isCanceled()) {
+ return createResult.get();
+ }
+
+ results.addAll(
+ mInjector.getPrimaryDexOptimizer(pkgState, pkg, params, cancellationSignal)
+ .dexopt());
+ }
+
+ if ((params.getFlags() & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) {
+ if (cancellationSignal.isCanceled()) {
+ return createResult.get();
+ }
+
+ results.addAll(
+ mInjector
+ .getSecondaryDexOptimizer(pkgState, pkg, params, cancellationSignal)
+ .dexopt());
+ }
+ } catch (RemoteException e) {
+ throw new IllegalStateException("An error occurred when calling artd", e);
+ }
return createResult.get();
}
@@ -149,6 +194,57 @@
return true;
}
+ @NonNull
+ private List<PackageState> getPackageStates(
+ @NonNull PackageManagerLocal.FilteredSnapshot snapshot,
+ @NonNull List<String> packageNames, boolean includeDependencies) {
+ var pkgStates = new LinkedHashMap<String, PackageState>();
+ Set<String> visitedLibraries = new HashSet<>();
+ Queue<SharedLibrary> queue = new LinkedList<>();
+
+ Consumer<SharedLibrary> maybeEnqueue = library -> {
+ // The package name is not null if the library is an APK.
+ // TODO(jiakaiz): Support JAR libraries.
+ if (library.getPackageName() != null && !visitedLibraries.contains(library.getName())) {
+ visitedLibraries.add(library.getName());
+ queue.add(library);
+ }
+ };
+
+ for (String packageName : packageNames) {
+ PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+ AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
+ pkgStates.put(packageName, pkgState);
+ if (includeDependencies && canOptimizePackage(pkgState, pkg)) {
+ for (SharedLibrary library : pkgState.getUsesLibraries()) {
+ maybeEnqueue.accept(library);
+ }
+ }
+ }
+
+ SharedLibrary library;
+ while ((library = queue.poll()) != null) {
+ String packageName = library.getPackageName();
+ PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName);
+ AndroidPackage pkg = pkgState.getAndroidPackage();
+ if (pkg != null && canOptimizePackage(pkgState, pkg)) {
+ pkgStates.put(packageName, pkgState);
+
+ // Note that `library.getDependencies()` is different from
+ // `pkgState.getUsesLibraries()`. Different libraries can belong to the same
+ // package. `pkgState.getUsesLibraries()` returns a union of dependencies of
+ // libraries that belong to the same package, which is not what we want here.
+ // Therefore, this loop cannot be unified with the one above.
+ for (SharedLibrary dep : library.getDependencies()) {
+ maybeEnqueue.accept(dep);
+ }
+ }
+ }
+
+ // `LinkedHashMap` guarantees deterministic order.
+ return new ArrayList<>(pkgStates.values());
+ }
+
/**
* Injector pattern for testing purpose.
*
diff --git a/libartservice/service/java/com/android/server/art/Utils.java b/libartservice/service/java/com/android/server/art/Utils.java
index 8136b0a..e4060c1 100644
--- a/libartservice/service/java/com/android/server/art/Utils.java
+++ b/libartservice/service/java/com/android/server/art/Utils.java
@@ -37,6 +37,11 @@
import java.util.Collection;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.concurrent.FutureTask;
import java.util.stream.Collectors;
/** @hide */
@@ -206,6 +211,26 @@
return str;
}
+ public static <T> Future<T> execute(@NonNull Executor executor, @NonNull Callable<T> callable) {
+ var future = new FutureTask<T>(callable);
+ executor.execute(future);
+ return future;
+ }
+
+ public static <T> T getFuture(Future<T> future) {
+ try {
+ return future.get();
+ } catch (ExecutionException e) {
+ Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ }
+ throw new RuntimeException(cause);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
@AutoValue
public abstract static class Abi {
static @NonNull Abi create(
diff --git a/libartservice/service/java/com/android/server/art/model/OptimizeResult.java b/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
index 5ee5016..6714f9a 100644
--- a/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
+++ b/libartservice/service/java/com/android/server/art/model/OptimizeResult.java
@@ -112,13 +112,16 @@
private final @NonNull String mPackageName;
private final
@NonNull List<DexContainerFileOptimizeResult> mDexContainerFileOptimizeResults;
+ private final boolean mIsCanceled;
/** @hide */
- public PackageOptimizeResult(@NonNull String packageName,
- @NonNull List<DexContainerFileOptimizeResult> dexContainerFileOptimizeResults) {
- mPackageName = packageName;
- mDexContainerFileOptimizeResults = dexContainerFileOptimizeResults;
- }
+ public PackageOptimizeResult(@NonNull String packageName,
+ @NonNull List<DexContainerFileOptimizeResult> dexContainerFileOptimizeResults,
+ boolean isCanceled) {
+ mPackageName = packageName;
+ mDexContainerFileOptimizeResults = dexContainerFileOptimizeResults;
+ mIsCanceled = isCanceled;
+ }
/** The package name. */
public @NonNull String getPackageName() {
@@ -136,10 +139,11 @@
/** The overall status of the package. */
public @OptimizeStatus int getStatus() {
- return mDexContainerFileOptimizeResults.stream()
- .mapToInt(result -> result.getStatus())
- .max()
- .orElse(OPTIMIZE_SKIPPED);
+ return mIsCanceled ? OPTIMIZE_CANCELLED
+ : mDexContainerFileOptimizeResults.stream()
+ .mapToInt(result -> result.getStatus())
+ .max()
+ .orElse(OPTIMIZE_SKIPPED);
}
}
diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
index 0dfc084..e9a2beb 100644
--- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
+++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java
@@ -17,6 +17,7 @@
package com.android.server.art;
import static com.android.server.art.model.OptimizationStatus.DexContainerFileOptimizationStatus;
+import static com.android.server.art.testing.TestingUtils.deepEq;
import static com.google.common.truth.Truth.assertThat;
@@ -277,8 +278,8 @@
var result = mock(OptimizeResult.class);
var cancellationSignal = new CancellationSignal();
- when(mDexOptHelper.dexopt(any(), same(mPkgState), same(mPkg), same(params),
- same(cancellationSignal)))
+ when(mDexOptHelper.dexopt(any(), deepEq(List.of(PKG_NAME)), same(params),
+ same(cancellationSignal), any()))
.thenReturn(result);
assertThat(mArtManagerLocal.optimizePackage(mSnapshot,
@@ -286,22 +287,6 @@
.isSameInstanceAs(result);
}
- @Test(expected = IllegalArgumentException.class)
- public void testOptimizePackagePackageNotFound() throws Exception {
- when(mSnapshot.getPackageState(anyString())).thenReturn(null);
-
- mArtManagerLocal.optimizePackage(mSnapshot, PKG_NAME,
- new OptimizeParams.Builder("install").build());
- }
-
- @Test(expected = IllegalArgumentException.class)
- public void testOptimizePackageNoPackage() throws Exception {
- lenient().when(mPkgState.getAndroidPackage()).thenReturn(null);
-
- mArtManagerLocal.optimizePackage(mSnapshot, PKG_NAME,
- new OptimizeParams.Builder("install").build());
- }
-
private AndroidPackage createPackage() {
AndroidPackage pkg = mock(AndroidPackage.class);
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()));
+ }
}