blob: d9b5e5a555b78455bbad54fc56fc341c8712373c [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.PrimaryDexUtils.DetailedPrimaryDexInfo;
import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.mock;
import android.util.Pair;
import androidx.test.filters.SmallTest;
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 dalvik.system.DelegateLastClassLoader;
import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;
import com.google.common.truth.Correspondence;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
import java.util.ArrayList;
import java.util.List;
@SmallTest
@RunWith(MockitoJUnitRunner.StrictStubs.class)
public class PrimaryDexUtilsTest {
@Before
public void setUp() {}
@Test
public void testGetDexInfo() {
List<PrimaryDexInfo> infos =
PrimaryDexUtils.getDexInfo(createPackage(false /* isIsolatedSplitLoading */));
checkBasicInfo(infos);
}
@Test
public void testGetDetailedDexInfo() {
List<DetailedPrimaryDexInfo> infos = PrimaryDexUtils.getDetailedDexInfo(
createPackageState(), createPackage(false /* isIsolatedSplitLoading */));
checkBasicInfo(infos);
String sharedLibrariesContext = "{"
+ "PCL[library_2.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}#"
+ "PCL[library_3.jar]#"
+ "PCL[library_4.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}"
+ "}";
assertThat(infos)
.comparingElementsUsing(Correspondence.transforming(
(DetailedPrimaryDexInfo info)
-> Pair.create(info.splitName(), info.classLoaderContext()),
"has split name and CLC of"))
.containsExactly(Pair.create(null, "PCL[]" + sharedLibrariesContext),
Pair.create("split_0", "PCL[base.apk]" + sharedLibrariesContext),
Pair.create("split_2",
"PCL[base.apk:split_0.apk:split_1.apk]" + sharedLibrariesContext),
Pair.create("split_3",
"PCL[base.apk:split_0.apk:split_1.apk:split_2.apk]"
+ sharedLibrariesContext),
Pair.create("split_4",
"PCL[base.apk:split_0.apk:split_1.apk:split_2.apk:split_3.apk]"
+ sharedLibrariesContext));
}
@Test
public void testGetDetailedDexInfoIsolated() {
List<DetailedPrimaryDexInfo> infos = PrimaryDexUtils.getDetailedDexInfo(
createPackageState(), createPackage(true /* isIsolatedSplitLoading */));
checkBasicInfo(infos);
String sharedLibrariesContext = "{"
+ "PCL[library_2.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}#"
+ "PCL[library_3.jar]#"
+ "PCL[library_4.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}"
+ "}";
assertThat(infos)
.comparingElementsUsing(Correspondence.transforming(
(DetailedPrimaryDexInfo info)
-> Pair.create(info.splitName(), info.classLoaderContext()),
"has split name and CLC of"))
.containsExactly(Pair.create(null, "PCL[]" + sharedLibrariesContext),
Pair.create("split_0",
"PCL[];DLC[split_2.apk];PCL[base.apk]" + sharedLibrariesContext),
Pair.create("split_2", "DLC[];PCL[base.apk]" + sharedLibrariesContext),
Pair.create("split_3", "PCL[]"),
Pair.create("split_4", "PCL[];PCL[split_3.apk]"));
}
private <T extends PrimaryDexInfo> void checkBasicInfo(List<T> infos) {
assertThat(infos.get(0).dexPath()).isEqualTo("/somewhere/app/foo/base.apk");
assertThat(infos.get(0).hasCode()).isTrue();
assertThat(infos.get(0).splitName()).isNull();
assertThat(infos.get(1).dexPath()).isEqualTo("/somewhere/app/foo/split_0.apk");
assertThat(infos.get(1).hasCode()).isTrue();
assertThat(infos.get(1).splitName()).isEqualTo("split_0");
// split_1 is skipped because it has no code.
assertThat(infos.get(2).dexPath()).isEqualTo("/somewhere/app/foo/split_2.apk");
assertThat(infos.get(2).hasCode()).isTrue();
assertThat(infos.get(2).splitName()).isEqualTo("split_2");
assertThat(infos.get(3).dexPath()).isEqualTo("/somewhere/app/foo/split_3.apk");
assertThat(infos.get(3).hasCode()).isTrue();
assertThat(infos.get(3).splitName()).isEqualTo("split_3");
assertThat(infos.get(4).dexPath()).isEqualTo("/somewhere/app/foo/split_4.apk");
assertThat(infos.get(4).hasCode()).isTrue();
assertThat(infos.get(4).splitName()).isEqualTo("split_4");
}
private AndroidPackage createPackage(boolean isIsolatedSplitLoading) {
AndroidPackage pkg = mock(AndroidPackage.class);
var baseSplit = mock(AndroidPackageSplit.class);
lenient().when(baseSplit.getPath()).thenReturn("/somewhere/app/foo/base.apk");
lenient().when(baseSplit.isHasCode()).thenReturn(true);
lenient().when(baseSplit.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
var split0 = mock(AndroidPackageSplit.class);
lenient().when(split0.getName()).thenReturn("split_0");
lenient().when(split0.getPath()).thenReturn("/somewhere/app/foo/split_0.apk");
lenient().when(split0.isHasCode()).thenReturn(true);
var split1 = mock(AndroidPackageSplit.class);
lenient().when(split1.getName()).thenReturn("split_1");
lenient().when(split1.getPath()).thenReturn("/somewhere/app/foo/split_1.apk");
lenient().when(split1.isHasCode()).thenReturn(false);
var split2 = mock(AndroidPackageSplit.class);
lenient().when(split2.getName()).thenReturn("split_2");
lenient().when(split2.getPath()).thenReturn("/somewhere/app/foo/split_2.apk");
lenient().when(split2.isHasCode()).thenReturn(true);
var split3 = mock(AndroidPackageSplit.class);
lenient().when(split3.getName()).thenReturn("split_3");
lenient().when(split3.getPath()).thenReturn("/somewhere/app/foo/split_3.apk");
lenient().when(split3.isHasCode()).thenReturn(true);
var split4 = mock(AndroidPackageSplit.class);
lenient().when(split4.getName()).thenReturn("split_4");
lenient().when(split4.getPath()).thenReturn("/somewhere/app/foo/split_4.apk");
lenient().when(split4.isHasCode()).thenReturn(true);
var splits = List.of(baseSplit, split0, split1, split2, split3, split4);
lenient().when(pkg.getSplits()).thenReturn(splits);
if (isIsolatedSplitLoading) {
// split_0: PCL(PathClassLoader), depends on split_2.
// split_1: no code.
// split_2: DLC(DelegateLastClassLoader), depends on base.
// split_3: PCL(DexClassLoader), no dependency.
// split_4: PCL(null), depends on split_3.
lenient().when(split0.getClassLoaderName()).thenReturn(PathClassLoader.class.getName());
lenient().when(split1.getClassLoaderName()).thenReturn(null);
lenient()
.when(split2.getClassLoaderName())
.thenReturn(DelegateLastClassLoader.class.getName());
lenient().when(split3.getClassLoaderName()).thenReturn(DexClassLoader.class.getName());
lenient().when(split4.getClassLoaderName()).thenReturn(null);
lenient().when(split0.getDependencies()).thenReturn(List.of(split2));
lenient().when(split2.getDependencies()).thenReturn(List.of(baseSplit));
lenient().when(split4.getDependencies()).thenReturn(List.of(split3));
lenient().when(pkg.isIsolatedSplitLoading()).thenReturn(true);
} else {
lenient().when(pkg.isIsolatedSplitLoading()).thenReturn(false);
}
return pkg;
}
private PackageState createPackageState() {
PackageState pkgState = mock(PackageState.class);
lenient().when(pkgState.getPackageName()).thenReturn("com.example.foo");
// Base depends on library 2, 3, 4.
// Library 2, 4 depends on library 1.
List<SharedLibrary> usesLibraryInfos = new ArrayList<>();
// The native library should not be added to the CLC.
SharedLibrary libraryNative = mock(SharedLibrary.class);
lenient().when(libraryNative.getAllCodePaths()).thenReturn(List.of("library_native.so"));
lenient().when(libraryNative.getDependencies()).thenReturn(null);
lenient().when(libraryNative.isNative()).thenReturn(true);
usesLibraryInfos.add(libraryNative);
SharedLibrary library1 = mock(SharedLibrary.class);
lenient()
.when(library1.getAllCodePaths())
.thenReturn(List.of("library_1_dex_1.jar", "library_1_dex_2.jar"));
lenient().when(library1.getDependencies()).thenReturn(null);
lenient().when(library1.isNative()).thenReturn(false);
SharedLibrary library2 = mock(SharedLibrary.class);
lenient().when(library2.getAllCodePaths()).thenReturn(List.of("library_2.jar"));
lenient().when(library2.getDependencies()).thenReturn(List.of(library1, libraryNative));
lenient().when(library2.isNative()).thenReturn(false);
usesLibraryInfos.add(library2);
SharedLibrary library3 = mock(SharedLibrary.class);
lenient().when(library3.getAllCodePaths()).thenReturn(List.of("library_3.jar"));
lenient().when(library3.getDependencies()).thenReturn(null);
lenient().when(library3.isNative()).thenReturn(false);
usesLibraryInfos.add(library3);
SharedLibrary library4 = mock(SharedLibrary.class);
lenient().when(library4.getAllCodePaths()).thenReturn(List.of("library_4.jar"));
lenient().when(library4.getDependencies()).thenReturn(List.of(library1));
lenient().when(library4.isNative()).thenReturn(false);
usesLibraryInfos.add(library4);
lenient().when(pkgState.getSharedLibraryDependencies()).thenReturn(usesLibraryInfos);
return pkgState;
}
}