blob: b9b8bd31e45d4bab9fa5501918a45e7715bd903b [file] [log] [blame]
/*
* Copyright (C) 2022 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 com.android.server.art;
import static com.android.server.art.AidlUtils.buildFsPermission;
import static com.android.server.art.AidlUtils.buildOutputArtifacts;
import static com.android.server.art.AidlUtils.buildPermissionSettings;
import static com.android.server.art.OutputArtifacts.PermissionSettings;
import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
import static com.android.server.art.testing.TestingUtils.deepEq;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.anyBoolean;
import static org.mockito.Mockito.anyInt;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
import android.os.Process;
import android.os.ServiceSpecificException;
import android.os.SystemProperties;
import androidx.test.filters.SmallTest;
import com.android.server.art.model.ArtFlags;
import com.android.server.art.model.DexoptParams;
import com.android.server.art.model.DexoptResult;
import com.android.server.art.testing.TestingUtils;
import dalvik.system.DexFile;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameter;
import org.junit.runners.Parameterized.Parameters;
import org.mockito.ArgumentMatcher;
import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(Parameterized.class)
public class PrimaryDexopterParameterizedTest extends PrimaryDexopterTestBase {
private DexoptParams mDexoptParams;
private PrimaryDexopter mPrimaryDexopter;
@Parameter(0) public Params mParams;
@Parameters(name = "{0}")
public static Iterable<Params> data() {
List<Params> list = new ArrayList<>();
Params params;
// Baseline.
params = new Params();
list.add(params);
params = new Params();
params.mRequestedCompilerFilter = "speed";
params.mExpectedCompilerFilter = "speed";
list.add(params);
params = new Params();
params.mIsInDalvikCache = true;
list.add(params);
params = new Params();
params.mHiddenApiEnforcementPolicy = ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
params.mExpectedIsHiddenApiPolicyEnabled = false;
list.add(params);
params = new Params();
params.mIsDebuggable = true;
params.mRequestedCompilerFilter = "speed";
params.mExpectedCompilerFilter = "verify";
params.mExpectedIsDebuggable = true;
list.add(params);
params = new Params();
params.mIsVmSafeMode = true;
params.mRequestedCompilerFilter = "speed";
params.mExpectedCompilerFilter = "verify";
list.add(params);
params = new Params();
params.mIsUseEmbeddedDex = true;
params.mRequestedCompilerFilter = "speed";
params.mExpectedCompilerFilter = "verify";
list.add(params);
params = new Params();
params.mAlwaysDebuggable = true;
params.mExpectedIsDebuggable = true;
list.add(params);
params = new Params();
params.mIsSystemUi = true;
params.mExpectedCompilerFilter = "speed";
list.add(params);
params = new Params();
params.mIsLauncher = true;
params.mExpectedCompilerFilter = "speed-profile";
list.add(params);
params = new Params();
params.mForce = true;
params.mShouldDowngrade = false;
params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
| DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
| DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
list.add(params);
params = new Params();
params.mForce = true;
params.mShouldDowngrade = true;
params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
| DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE
| DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
list.add(params);
params = new Params();
params.mShouldDowngrade = true;
params.mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_WORSE;
list.add(params);
params = new Params();
// This should not change the result.
params.mSkipIfStorageLow = true;
list.add(params);
params = new Params();
params.mIgnoreProfile = true;
params.mRequestedCompilerFilter = "speed-profile";
params.mExpectedCompilerFilter = "verify";
list.add(params);
params = new Params();
params.mIgnoreProfile = true;
params.mRequestedCompilerFilter = "speed";
params.mExpectedCompilerFilter = "speed";
list.add(params);
// It respects the adjustment made by the callback.
params = new Params();
params.mRequestedCompilerFilter = "speed-profile";
params.mExpectedCallbackInputCompilerFilter = "speed-profile";
params.mCallbackReturnedCompilerFilter = "speed";
params.mExpectedCompilerFilter = "speed";
list.add(params);
// It upgrades the compiler filter before calling the callback.
params = new Params();
params.mRequestedCompilerFilter = "speed-profile";
params.mIsSystemUi = true;
params.mExpectedCallbackInputCompilerFilter = "speed";
params.mCallbackReturnedCompilerFilter = "speed";
params.mExpectedCompilerFilter = "speed";
list.add(params);
// It downgrades the compiler filter after calling the callback.
params = new Params();
params.mRequestedCompilerFilter = "speed-profile";
params.mExpectedCallbackInputCompilerFilter = "speed-profile";
params.mCallbackReturnedCompilerFilter = "speed-profile";
params.mIsUseEmbeddedDex = true;
params.mExpectedCompilerFilter = "verify";
list.add(params);
return list;
}
@Before
public void setUp() throws Exception {
super.setUp();
lenient().when(mInjector.isSystemUiPackage(any())).thenReturn(mParams.mIsSystemUi);
lenient().when(mInjector.isLauncherPackage(any())).thenReturn(mParams.mIsLauncher);
lenient()
.when(SystemProperties.getBoolean(eq("dalvik.vm.always_debuggable"), anyBoolean()))
.thenReturn(mParams.mAlwaysDebuggable);
lenient().when(mPkg.isVmSafeMode()).thenReturn(mParams.mIsVmSafeMode);
lenient().when(mPkg.isDebuggable()).thenReturn(mParams.mIsDebuggable);
lenient().when(mPkg.getTargetSdkVersion()).thenReturn(123);
lenient()
.when(mPkgState.getHiddenApiEnforcementPolicy())
.thenReturn(mParams.mHiddenApiEnforcementPolicy);
lenient().when(mPkg.isUseEmbeddedDex()).thenReturn(mParams.mIsUseEmbeddedDex);
// Make all profile-related operations succeed so that "speed-profile" doesn't fall back to
// "verify".
lenient().when(mArtd.isProfileUsable(any(), any())).thenReturn(true);
lenient().when(mArtd.getProfileVisibility(any())).thenReturn(FileVisibility.OTHER_READABLE);
lenient().when(mArtd.mergeProfiles(any(), any(), any(), any(), any())).thenReturn(false);
lenient().when(mArtd.isInDalvikCache(any())).thenReturn(mParams.mIsInDalvikCache);
if (mParams.mCallbackReturnedCompilerFilter != null) {
mConfig.setAdjustCompilerFilterCallback(
Runnable::run, (packageName, originalCompilerFilter, reason) -> {
assertThat(packageName).isEqualTo(PKG_NAME);
assertThat(originalCompilerFilter)
.isEqualTo(mParams.mExpectedCallbackInputCompilerFilter);
assertThat(reason).isEqualTo("install");
return mParams.mCallbackReturnedCompilerFilter;
});
}
mDexoptParams =
new DexoptParams.Builder("install")
.setCompilerFilter(mParams.mRequestedCompilerFilter)
.setPriorityClass(ArtFlags.PRIORITY_INTERACTIVE)
.setFlags(mParams.mForce ? ArtFlags.FLAG_FORCE : 0, ArtFlags.FLAG_FORCE)
.setFlags(mParams.mShouldDowngrade ? ArtFlags.FLAG_SHOULD_DOWNGRADE : 0,
ArtFlags.FLAG_SHOULD_DOWNGRADE)
.setFlags(mParams.mSkipIfStorageLow ? ArtFlags.FLAG_SKIP_IF_STORAGE_LOW : 0,
ArtFlags.FLAG_SKIP_IF_STORAGE_LOW)
.setFlags(mParams.mIgnoreProfile ? ArtFlags.FLAG_IGNORE_PROFILE : 0,
ArtFlags.FLAG_IGNORE_PROFILE)
.build();
mPrimaryDexopter =
new PrimaryDexopter(mInjector, mPkgState, mPkg, mDexoptParams, mCancellationSignal);
}
@Test
public void testDexopt() throws Exception {
PermissionSettings permissionSettings = buildPermissionSettings(
buildFsPermission(Process.SYSTEM_UID /* uid */, Process.SYSTEM_UID /* gid */,
false /* isOtherReadable */, true /* isOtherExecutable */),
buildFsPermission(Process.SYSTEM_UID /* uid */, SHARED_GID /* gid */,
true /* isOtherReadable */),
null /* seContext */);
// No need to check `generateAppImage`. It is checked in `PrimaryDexopterTest`.
ArgumentMatcher<DexoptOptions> dexoptOptionsMatcher = options
-> options.compilationReason.equals("install") && options.targetSdkVersion == 123
&& options.debuggable == mParams.mExpectedIsDebuggable
&& options.hiddenApiPolicyEnabled == mParams.mExpectedIsHiddenApiPolicyEnabled
&& options.comments.equals(String.format(
"app-name:%s,app-version-name:%s,app-version-code:%d,art-version:%d",
PKG_NAME, APP_VERSION_NAME, APP_VERSION_CODE, ART_VERSION));
when(mArtd.createCancellationSignal()).thenReturn(mock(IArtdCancellationSignal.class));
when(mArtd.getDmFileVisibility(any())).thenReturn(FileVisibility.NOT_FOUND);
// The first one is normal.
doReturn(dexoptIsNeeded())
.when(mArtd)
.getDexoptNeeded("/somewhere/app/foo/base.apk", "arm64", "PCL[]",
mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
doReturn(createArtdDexoptResult(false /* cancelled */, 100 /* wallTimeMs */,
400 /* cpuTimeMs */, 30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */))
.when(mArtd)
.dexopt(deepEq(buildOutputArtifacts("/somewhere/app/foo/base.apk", "arm64",
mParams.mIsInDalvikCache, permissionSettings)),
eq("/somewhere/app/foo/base.apk"), eq("arm64"), eq("PCL[]"),
eq(mParams.mExpectedCompilerFilter), any() /* profile */,
isNull() /* inputVdex */, isNull() /* dmFile */,
eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any());
// The second one fails on `dexopt`.
doReturn(dexoptIsNeeded())
.when(mArtd)
.getDexoptNeeded("/somewhere/app/foo/base.apk", "arm", "PCL[]",
mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
doThrow(ServiceSpecificException.class)
.when(mArtd)
.dexopt(deepEq(buildOutputArtifacts("/somewhere/app/foo/base.apk", "arm",
mParams.mIsInDalvikCache, permissionSettings)),
eq("/somewhere/app/foo/base.apk"), eq("arm"), eq("PCL[]"),
eq(mParams.mExpectedCompilerFilter), any() /* profile */,
isNull() /* inputVdex */, isNull() /* dmFile */,
eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any());
// The third one doesn't need dexopt.
doReturn(dexoptIsNotNeeded())
.when(mArtd)
.getDexoptNeeded("/somewhere/app/foo/split_0.apk", "arm64", "PCL[base.apk]",
mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
// The fourth one is normal.
doReturn(dexoptIsNeeded())
.when(mArtd)
.getDexoptNeeded("/somewhere/app/foo/split_0.apk", "arm", "PCL[base.apk]",
mParams.mExpectedCompilerFilter, mParams.mExpectedDexoptTrigger);
doReturn(createArtdDexoptResult(false /* cancelled */, 200 /* wallTimeMs */,
200 /* cpuTimeMs */, 10000 /* sizeBytes */, 0 /* sizeBeforeBytes */))
.when(mArtd)
.dexopt(deepEq(buildOutputArtifacts("/somewhere/app/foo/split_0.apk", "arm",
mParams.mIsInDalvikCache, permissionSettings)),
eq("/somewhere/app/foo/split_0.apk"), eq("arm"), eq("PCL[base.apk]"),
eq(mParams.mExpectedCompilerFilter), any() /* profile */,
isNull() /* inputVdex */, isNull() /* dmFile */,
eq(PriorityClass.INTERACTIVE), argThat(dexoptOptionsMatcher), any());
// Only delete runtime artifacts for successful dexopt operations, namely the first one and
// the fourth one.
doReturn(1l).when(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
PKG_NAME, "/somewhere/app/foo/base.apk", "arm64")));
doReturn(1l).when(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath(
PKG_NAME, "/somewhere/app/foo/split_0.apk", "arm")));
assertThat(mPrimaryDexopter.dexopt())
.comparingElementsUsing(TestingUtils.<DexContainerFileDexoptResult>deepEquality())
.containsExactly(
DexContainerFileDexoptResult.create("/somewhere/app/foo/base.apk",
true /* isPrimaryAbi */, "arm64-v8a",
mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_PERFORMED,
100 /* dex2oatWallTimeMillis */, 400 /* dex2oatCpuTimeMillis */,
30000 /* sizeBytes */, 32000 /* sizeBeforeBytes */,
0 /* extendedStatusFlags */, List.of() /* externalProfileErrors */),
DexContainerFileDexoptResult.create("/somewhere/app/foo/base.apk",
false /* isPrimaryAbi */, "armeabi-v7a",
mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_FAILED),
DexContainerFileDexoptResult.create("/somewhere/app/foo/split_0.apk",
true /* isPrimaryAbi */, "arm64-v8a",
mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_SKIPPED),
DexContainerFileDexoptResult.create("/somewhere/app/foo/split_0.apk",
false /* isPrimaryAbi */, "armeabi-v7a",
mParams.mExpectedCompilerFilter, DexoptResult.DEXOPT_PERFORMED,
200 /* dex2oatWallTimeMillis */, 200 /* dex2oatCpuTimeMillis */,
10000 /* sizeBytes */, 0 /* sizeBeforeBytes */,
0 /* extendedStatusFlags */,
List.of() /* externalProfileErrors */));
// Verify that there are no more calls than the ones above.
verify(mArtd, times(3))
.dexopt(any(), any(), any(), any(), any(), any(), any(), any(), anyInt(), any(),
any());
}
private static class Params {
// Package information.
public boolean mIsInDalvikCache = false;
public int mHiddenApiEnforcementPolicy = ApplicationInfo.HIDDEN_API_ENFORCEMENT_ENABLED;
public boolean mIsVmSafeMode = false;
public boolean mIsDebuggable = false;
public boolean mIsSystemUi = false;
public boolean mIsLauncher = false;
public boolean mIsUseEmbeddedDex = false;
// Options.
public String mRequestedCompilerFilter = "verify";
public String mCallbackReturnedCompilerFilter = null;
public boolean mForce = false;
public boolean mShouldDowngrade = false;
public boolean mSkipIfStorageLow = false;
public boolean mIgnoreProfile = false;
// System properties.
public boolean mAlwaysDebuggable = false;
// Expectations.
public String mExpectedCallbackInputCompilerFilter = "verify";
public String mExpectedCompilerFilter = "verify";
public int mExpectedDexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
| DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
public boolean mExpectedIsDebuggable = false;
public boolean mExpectedIsHiddenApiPolicyEnabled = true;
public String toString() {
return String.format("isInDalvikCache=%b,"
+ "hiddenApiEnforcementPolicy=%d,"
+ "isVmSafeMode=%b,"
+ "isDebuggable=%b,"
+ "isSystemUi=%b,"
+ "isLauncher=%b,"
+ "isUseEmbeddedDex=%b,"
+ "requestedCompilerFilter=%s,"
+ "callbackReturnedCompilerFilter=%s,"
+ "force=%b,"
+ "shouldDowngrade=%b,"
+ "skipIfStorageLow=%b,"
+ "ignoreProfile=%b,"
+ "alwaysDebuggable=%b"
+ " => "
+ "expectedCallbackInputCompilerFilter=%s,"
+ "expectedCompilerFilter=%s,"
+ "expectedDexoptTrigger=%d,"
+ "expectedIsDebuggable=%b,"
+ "expectedIsHiddenApiPolicyEnabled=%b",
mIsInDalvikCache, mHiddenApiEnforcementPolicy, mIsVmSafeMode, mIsDebuggable,
mIsSystemUi, mIsLauncher, mIsUseEmbeddedDex, mRequestedCompilerFilter,
mCallbackReturnedCompilerFilter, mForce, mShouldDowngrade, mSkipIfStorageLow,
mIgnoreProfile, mAlwaysDebuggable, mExpectedCallbackInputCompilerFilter,
mExpectedCompilerFilter, mExpectedDexoptTrigger, mExpectedIsDebuggable,
mExpectedIsHiddenApiPolicyEnabled);
}
}
}