blob: 520cda0835f55ab958757ba294891beb67e05d98 [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.DexUseManagerLocal.CheckedSecondaryDexInfo;
import static com.android.server.art.DexUseManagerLocal.DexLoader;
import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo;
import static com.android.server.art.model.DexoptStatus.DexContainerFileDexoptStatus;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.argThat;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import android.annotation.NonNull;
import android.os.SystemProperties;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.server.art.model.DexoptStatus;
import com.android.server.art.testing.StaticMockitoRule;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@SmallTest
@RunWith(AndroidJUnit4.class)
public class DumpHelperTest {
private static final String PKG_NAME_FOO = "com.example1.foo";
private static final String PKG_NAME_BAR = "com.example2.bar";
@Rule
public StaticMockitoRule mockitoRule =
new StaticMockitoRule(SystemProperties.class, Constants.class, ArtJni.class);
@Mock private DumpHelper.Injector mInjector;
@Mock private ArtManagerLocal mArtManagerLocal;
@Mock private DexUseManagerLocal mDexUseManagerLocal;
@Mock private PackageManagerLocal.FilteredSnapshot mSnapshot;
private DumpHelper mDumpHelper;
@Before
public void setUp() throws Exception {
lenient().when(Constants.getPreferredAbi()).thenReturn("arm64-v8a");
lenient().when(Constants.getNative64BitAbi()).thenReturn("arm64-v8a");
lenient().when(Constants.getNative32BitAbi()).thenReturn("armeabi-v7a");
// No ISA translation.
lenient()
.when(SystemProperties.get(argThat(arg -> arg.startsWith("ro.dalvik.vm.isa."))))
.thenReturn("");
lenient().when(ArtJni.getGarbageCollector()).thenReturn("CollectorTypeCMC");
lenient().when(mInjector.getArtManagerLocal()).thenReturn(mArtManagerLocal);
lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManagerLocal);
Map<String, PackageState> pkgStates = createPackageStates();
lenient().when(mSnapshot.getPackageStates()).thenReturn(pkgStates);
for (var entry : pkgStates.entrySet()) {
lenient().when(mSnapshot.getPackageState(entry.getKey())).thenReturn(entry.getValue());
}
setUpForFoo();
setUpForBar();
mDumpHelper = new DumpHelper(mInjector);
}
@Test
public void testDump() throws Exception {
String expected = "[com.example1.foo]\n"
+ " path: /somewhere/app/foo/base.apk\n"
+ " arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]\n"
+ " [location is /somewhere/app/foo/oat/arm64/base.odex]\n"
+ " arm: [status=verify] [reason=install]\n"
+ " [location is /somewhere/app/foo/oat/arm/base.odex]\n"
+ " path: /somewhere/app/foo/split_0.apk\n"
+ " arm64: [status=verify] [reason=vdex] [primary-abi]\n"
+ " [location is primary.vdex in /somewhere/app/foo/split_0.dm]\n"
+ " arm: [status=verify] [reason=vdex]\n"
+ " [location is primary.vdex in /somewhere/app/foo/split_0.dm]\n"
+ " used by other apps: [com.example2.bar (isa=arm)]\n"
+ " known secondary dex files:\n"
+ " /data/user_de/0/foo/1.apk (removed)\n"
+ " arm: [status=run-from-apk] [reason=unknown]\n"
+ " [location is unknown]\n"
+ " class loader context: =VaryingClassLoaderContexts=\n"
+ " com.example1.foo (isolated): CLC1\n"
+ " com.example3.baz: CLC2\n"
+ " used by other apps: [com.example1.foo (isolated) (isa=arm64), com.example3.baz (removed)]\n"
+ " /data/user_de/0/foo/2.apk (public)\n"
+ " arm64: [status=speed-profile] [reason=bg-dexopt] [primary-abi]\n"
+ " [location is /data/user_de/0/foo/oat/arm64/2.odex]\n"
+ " arm: [status=verify] [reason=vdex]\n"
+ " [location is /data/user_de/0/foo/oat/arm/2.vdex]\n"
+ " class loader context: PCL[]\n"
+ "[com.example2.bar]\n"
+ " path: /somewhere/app/bar/base.apk\n"
+ " arm: [status=verify] [reason=install] [primary-abi]\n"
+ " [location is /somewhere/app/bar/oat/arm/base.odex]\n"
+ " arm64: [status=verify] [reason=install]\n"
+ " [location is /somewhere/app/bar/oat/arm64/base.odex]\n"
+ "\n"
+ "Current GC: CollectorTypeCMC\n";
var stringWriter = new StringWriter();
mDumpHelper.dump(new PrintWriter(stringWriter), mSnapshot);
assertThat(stringWriter.toString()).isEqualTo(expected);
}
private PackageState createPackageState(@NonNull String packageName, int appId,
boolean hasPackage, @NonNull String primaryAbi, @NonNull String secondaryAbi) {
var pkgState = mock(PackageState.class);
lenient().when(pkgState.getPackageName()).thenReturn(packageName);
lenient().when(pkgState.getAppId()).thenReturn(appId);
lenient()
.when(pkgState.getAndroidPackage())
.thenReturn(hasPackage ? mock(AndroidPackage.class) : null);
lenient().when(pkgState.getPrimaryCpuAbi()).thenReturn(primaryAbi);
lenient().when(pkgState.getSecondaryCpuAbi()).thenReturn(secondaryAbi);
return pkgState;
}
private Map<String, PackageState> createPackageStates() {
var pkgStates = new HashMap<String, PackageState>();
pkgStates.put(PKG_NAME_FOO,
createPackageState(PKG_NAME_FOO, 10001 /* appId */, true /* hasPackage */,
"arm64-v8a", "armeabi-v7a"));
pkgStates.put(PKG_NAME_BAR,
createPackageState(PKG_NAME_BAR, 10003 /* appId */, true /* hasPackage */,
"armeabi-v7a", "arm64-v8a"));
// This should not be included in the output because it has a negative app id.
pkgStates.put("com.android.art",
createPackageState("com.android.art", -1 /* appId */, true /* hasPackage */,
"arm64-v8a", "armeabi-v7a"));
// This should not be included in the output because it does't have AndroidPackage.
pkgStates.put("com.example.null",
createPackageState("com.example.null", 10010 /* appId */, false /* hasPackage */,
"arm64-v8a", "armeabi-v7a"));
return pkgStates;
}
private void setUpForFoo() throws Exception {
// The order of the primary dex files and the ABIs should be kept in the output. Secondary
// dex files should be reordered in lexicographical order.
var status = DexoptStatus.create(List.of(
DexContainerFileDexoptStatus.create("/somewhere/app/foo/base.apk",
true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
"speed-profile", "bg-dexopt", "/somewhere/app/foo/oat/arm64/base.odex"),
DexContainerFileDexoptStatus.create("/somewhere/app/foo/base.apk",
true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify",
"install", "/somewhere/app/foo/oat/arm/base.odex"),
DexContainerFileDexoptStatus.create("/somewhere/app/foo/split_0.apk",
true /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", "verify",
"vdex", "primary.vdex in /somewhere/app/foo/split_0.dm"),
DexContainerFileDexoptStatus.create("/somewhere/app/foo/split_0.apk",
true /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify",
"vdex", "primary.vdex in /somewhere/app/foo/split_0.dm"),
DexContainerFileDexoptStatus.create("/data/user_de/0/foo/2.apk",
false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a",
"speed-profile", "bg-dexopt", "/data/user_de/0/foo/oat/arm64/2.odex"),
DexContainerFileDexoptStatus.create("/data/user_de/0/foo/2.apk",
false /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a", "verify",
"vdex", "/data/user_de/0/foo/oat/arm/2.vdex"),
DexContainerFileDexoptStatus.create("/data/user_de/0/foo/1.apk",
false /* isPrimaryDex */, false /* isPrimaryAbi */, "armeabi-v7a",
"run-from-apk", "unknown", "unknown")));
lenient()
.when(mArtManagerLocal.getDexoptStatus(any(), eq(PKG_NAME_FOO)))
.thenReturn(status);
// The output should not show "used by other apps:".
lenient()
.when(mDexUseManagerLocal.getPrimaryDexLoaders(
PKG_NAME_FOO, "/somewhere/app/foo/base.apk"))
.thenReturn(Set.of());
// The output should not show "foo" in "used by other apps:".
lenient()
.when(mDexUseManagerLocal.getPrimaryDexLoaders(
PKG_NAME_FOO, "/somewhere/app/foo/split_0.apk"))
.thenReturn(Set.of(DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */),
DexLoader.create(PKG_NAME_BAR, false /* isolatedProcess */)));
var info1 = mock(CheckedSecondaryDexInfo.class);
lenient().when(info1.dexPath()).thenReturn("/data/user_de/0/foo/1.apk");
lenient()
.when(info1.displayClassLoaderContext())
.thenReturn(SecondaryDexInfo.VARYING_CLASS_LOADER_CONTEXTS);
lenient()
.when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO,
"/data/user_de/0/foo/1.apk",
DexLoader.create(PKG_NAME_FOO, true /* isolatedProcess */)))
.thenReturn("CLC1");
lenient()
.when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO,
"/data/user_de/0/foo/1.apk",
DexLoader.create("com.example3.baz", false /* isolatedProcess */)))
.thenReturn("CLC2");
var loaders = new HashSet<DexLoader>();
// The output should show "foo" with "(isolated)" in "used by other apps:".
loaders.add(DexLoader.create(PKG_NAME_FOO, true /* isolatedProcess */));
// The output should show "baz" with "(removed)" in "used by other apps:".
loaders.add(DexLoader.create("com.example3.baz", false /* isolatedProcess */));
lenient().when(info1.loaders()).thenReturn(loaders);
// The output should show the dex path with "(removed)".
lenient().when(info1.fileVisibility()).thenReturn(FileVisibility.NOT_FOUND);
var info2 = mock(CheckedSecondaryDexInfo.class);
lenient().when(info2.dexPath()).thenReturn("/data/user_de/0/foo/2.apk");
lenient().when(info2.displayClassLoaderContext()).thenReturn("PCL[]");
lenient()
.when(mDexUseManagerLocal.getSecondaryClassLoaderContext(PKG_NAME_FOO,
"/data/user_de/0/foo/2.apk",
DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */)))
.thenReturn("PCL[]");
// The output should not show "used by other apps:".
lenient()
.when(info2.loaders())
.thenReturn(Set.of(DexLoader.create(PKG_NAME_FOO, false /* isolatedProcess */)));
lenient().when(info2.fileVisibility()).thenReturn(FileVisibility.OTHER_READABLE);
lenient()
.doReturn(List.of(info1, info2))
.when(mDexUseManagerLocal)
.getCheckedSecondaryDexInfo(
PKG_NAME_FOO, false /* excludeObsoleteDexesAndLoaders */);
}
private void setUpForBar() {
// The order of the ABI should be kept in the output, despite that it's different from the
// order for package "foo".
// The output should not show "known secondary dex files:".
var status = DexoptStatus.create(
List.of(DexContainerFileDexoptStatus.create("/somewhere/app/bar/base.apk",
true /* isPrimaryDex */, true /* isPrimaryAbi */, "armeabi-v7a",
"verify", "install", "/somewhere/app/bar/oat/arm/base.odex"),
DexContainerFileDexoptStatus.create("/somewhere/app/bar/base.apk",
true /* isPrimaryDex */, false /* isPrimaryAbi */, "arm64-v8a",
"verify", "install", "/somewhere/app/bar/oat/arm64/base.odex")));
lenient()
.when(mArtManagerLocal.getDexoptStatus(any(), eq(PKG_NAME_BAR)))
.thenReturn(status);
lenient()
.when(mDexUseManagerLocal.getPrimaryDexLoaders(
PKG_NAME_BAR, "/somewhere/app/bar/base.apk"))
.thenReturn(Set.of());
}
}