1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.os.classpath; 18 19 import static android.compat.testing.Classpaths.ClasspathType.BOOTCLASSPATH; 20 import static android.compat.testing.Classpaths.ClasspathType.DEX2OATBOOTCLASSPATH; 21 import static android.compat.testing.Classpaths.ClasspathType.SYSTEMSERVERCLASSPATH; 22 import static android.compat.testing.Classpaths.getJarsOnClasspath; 23 24 import static com.android.os.classpath.ClasspathsTest.ClasspathSubject.assertThat; 25 26 import static com.google.common.base.Preconditions.checkArgument; 27 import static com.google.common.truth.Fact.fact; 28 import static com.google.common.truth.Fact.simpleFact; 29 import static com.google.common.truth.Truth.assertAbout; 30 import static com.google.common.truth.Truth.assertWithMessage; 31 32 import static org.junit.Assume.assumeTrue; 33 34 import com.android.modules.utils.build.testing.DeviceSdkLevel; 35 import com.android.tradefed.device.ITestDevice; 36 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 37 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 38 39 import com.google.common.collect.ImmutableList; 40 import com.google.common.truth.Fact; 41 import com.google.common.truth.FailureMetadata; 42 import com.google.common.truth.IterableSubject; 43 import com.google.common.truth.Ordered; 44 45 import org.junit.Before; 46 import org.junit.Test; 47 import org.junit.runner.RunWith; 48 49 import java.nio.file.Paths; 50 51 /** Tests for the contents of *CLASSPATH environ variables on a device. */ 52 @RunWith(DeviceJUnit4ClassRunner.class) 53 public class ClasspathsTest extends BaseHostJUnit4Test { 54 55 // A selection of jars on *CLASSPATH that cover all categories: 56 // - ART apex jar 57 // - Non-updatable apex jar 58 // - Updatable apex jar 59 // - System jar on BOOTCLASSPATH 60 // - System jar on SYSTEMSERVERCLASSPATH 61 private static final String FRAMEWORK_JAR = "/system/framework/framework.jar"; 62 private static final String ICU4J_JAR = "/apex/com.android.i18n/javalib/core-icu4j.jar"; 63 private static final String LIBART_JAR = "/apex/com.android.art/javalib/core-libart.jar"; 64 private static final String SDKEXTENSIONS_JAR = 65 "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar"; 66 private static final String SERVICES_JAR = "/system/framework/services.jar"; 67 68 @Before before()69 public void before() throws Exception { 70 ITestDevice device = getDevice(); 71 DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(device); 72 assumeTrue(deviceSdkLevel.isDeviceAtLeastS()); 73 } 74 75 @Test testBootclasspath()76 public void testBootclasspath() throws Exception { 77 ImmutableList<String> jars = getJarsOnClasspath(getDevice(), BOOTCLASSPATH); 78 79 assertThat(jars).containsNoDuplicates(); 80 81 assertThat(jars) 82 .containsAtLeast(LIBART_JAR, FRAMEWORK_JAR, ICU4J_JAR, SDKEXTENSIONS_JAR) 83 .inOrder(); 84 assertThat(jars).doesNotContain(SERVICES_JAR); 85 86 ImmutableList<String> expectedPrefixes = 87 ImmutableList.of( 88 "/apex/com.android.art/", 89 "/system/", 90 "/system_ext/", 91 "/apex/com.android.i18n/", 92 "/apex/"); 93 assertThat(jars).prefixesMatch(expectedPrefixes).inOrder(); 94 95 assertThat(getUpdatableApexes(jars)).isInOrder(); 96 } 97 98 @Test testDex2oatBootclasspath()99 public void testDex2oatBootclasspath() throws Exception { 100 ImmutableList<String> jars = getJarsOnClasspath(getDevice(), DEX2OATBOOTCLASSPATH); 101 102 assertThat(jars).containsNoDuplicates(); 103 104 // DEX2OATBOOTCLASSPATH must only contain ART, core-icu4j, and platform system jars 105 assertThat(jars).containsAtLeast(LIBART_JAR, FRAMEWORK_JAR, ICU4J_JAR).inOrder(); 106 assertThat(jars).containsNoneOf(SDKEXTENSIONS_JAR, SERVICES_JAR); 107 108 // DEX2OATBOOTCLASSPATH must be a subset of BOOTCLASSPATH 109 ImmutableList<String> bootJars = getJarsOnClasspath(getDevice(), BOOTCLASSPATH); 110 assertThat(bootJars).containsAtLeastElementsIn(jars); 111 112 ImmutableList<String> expectedPrefixes = 113 ImmutableList.of( 114 "/apex/com.android.art/", 115 "/system/", 116 "/system_ext/", 117 "/apex/com.android.i18n/"); 118 assertThat(jars).prefixesMatch(expectedPrefixes).inOrder(); 119 120 // No updatable jars on DEX2OATBOOTCLASSPATH 121 assertThat(getUpdatableApexes(jars)).isEmpty(); 122 } 123 124 @Test testSystemServerClasspath()125 public void testSystemServerClasspath() throws Exception { 126 ImmutableList<String> jars = getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH); 127 128 assertThat(jars).containsNoDuplicates(); 129 130 assertThat(jars).containsNoneOf(LIBART_JAR, FRAMEWORK_JAR, ICU4J_JAR, SDKEXTENSIONS_JAR); 131 assertThat(jars).contains(SERVICES_JAR); 132 133 ImmutableList<String> expectedPrefixes = 134 ImmutableList.of("/system/", "/system_ext/", "/apex/"); 135 assertThat(jars).prefixesMatch(expectedPrefixes).inOrder(); 136 137 assertThat(getUpdatableApexes(jars)).isInOrder(); 138 } 139 140 @Test testDex2oatJarsAreFirstOnBootclasspath()141 public void testDex2oatJarsAreFirstOnBootclasspath() throws Exception { 142 ImmutableList<String> bootJars = getJarsOnClasspath(getDevice(), BOOTCLASSPATH); 143 ImmutableList<String> dex2oatJars = getJarsOnClasspath(getDevice(), DEX2OATBOOTCLASSPATH); 144 145 // All preopt jars on BOOTCLASSPATH must come before updatable jars. 146 assertThat(bootJars).startsWith(dex2oatJars); 147 } 148 149 /** 150 * Returns a derived subject with names of the updatable APEXes preserving the original order. 151 */ getUpdatableApexes(ImmutableList<String> jars)152 private static ImmutableList<String> getUpdatableApexes(ImmutableList<String> jars) { 153 return jars.stream() 154 .filter(jar -> jar.startsWith("/apex")) 155 // ICU4J_JAR is the last non-updatable APEX jar, i.e. everything after is 156 // considered to be an updatable APEX jar 157 .dropWhile(jar -> !jar.equals(ICU4J_JAR)) 158 .skip(1) 159 // Map to APEX name from "/apex/<name>/javalibs/foo.jar" 160 .map(jar -> Paths.get(jar).getName(1).toString()) 161 .collect(ImmutableList.toImmutableList()); 162 } 163 164 static final class ClasspathSubject extends IterableSubject { 165 166 private static final Ordered EMPTY_ORDERED = () -> {}; 167 168 private final ImmutableList<String> actual; 169 ClasspathSubject(FailureMetadata metadata, ImmutableList<String> iterable)170 protected ClasspathSubject(FailureMetadata metadata, ImmutableList<String> iterable) { 171 super(metadata, iterable); 172 actual = iterable; 173 } 174 assertThat(ImmutableList<String> jars)175 public static ClasspathSubject assertThat(ImmutableList<String> jars) { 176 return assertAbout(ClasspathSubject::new).that(jars); 177 } 178 179 /** 180 * Checks that the actual iterable contains only jars that start with the expected prefixes 181 * or fails. 182 * 183 * <p>To also test that the prefixes appear in the given order, make a call to {@code 184 * inOrder} on the object returned by this method. The expected elements must appear in the 185 * given order within the actual elements. 186 */ prefixesMatch(ImmutableList<String> expected)187 public Ordered prefixesMatch(ImmutableList<String> expected) { 188 checkArgument( 189 expected.stream().distinct().count() == expected.size(), 190 "No duplicates are allowed in expected values."); 191 192 ImmutableList.Builder<String> unexpectedJars = ImmutableList.builder(); 193 boolean ordered = true; 194 int currentPrefixIndex = expected.isEmpty() ? -1 : 0; 195 for (String jar : actual) { 196 int prefixIndex = findFirstMatchingPrefix(jar, expected); 197 if (prefixIndex == -1) { 198 unexpectedJars.add(jar); 199 continue; 200 } 201 if (prefixIndex != currentPrefixIndex) { 202 if (prefixIndex < currentPrefixIndex) { 203 ordered = false; 204 } 205 currentPrefixIndex = prefixIndex; 206 } 207 } 208 209 ImmutableList<String> unexpected = unexpectedJars.build(); 210 if (!unexpected.isEmpty()) { 211 Fact expectedOrder = 212 fact("expected jar filepaths to be prefixes with one of", expected); 213 ImmutableList.Builder<Fact> facts = ImmutableList.builder(); 214 for (String e : unexpected) { 215 facts.add(fact("unexpected", e)); 216 } 217 facts.add(simpleFact("---")); 218 facts.add(simpleFact("")); 219 failWithoutActual(expectedOrder, facts.build().toArray(new Fact[0])); 220 return EMPTY_ORDERED; 221 } 222 223 if (ordered) { 224 return EMPTY_ORDERED; 225 } 226 227 return () -> 228 failWithActual( 229 simpleFact("all jars have valid partitions, but the order was wrong"), 230 fact("expected order", expected)); 231 } 232 233 /** Checks that the actual iterable starts with expected elements. */ startsWith(ImmutableList<String> expected)234 public void startsWith(ImmutableList<String> expected) { 235 if (actual.size() < expected.size()) { 236 failWithActual("expected at least number of elements", expected.size()); 237 return; 238 } 239 assertWithMessage("Unexpected initial elements of the list") 240 .that(actual.subList(0, expected.size())) 241 .isEqualTo(expected); 242 } 243 findFirstMatchingPrefix(String value, ImmutableList<String> prefixes)244 private static int findFirstMatchingPrefix(String value, ImmutableList<String> prefixes) { 245 for (int i = 0; i < prefixes.size(); i++) { 246 if (value.startsWith(prefixes.get(i))) { 247 return i; 248 } 249 } 250 return -1; 251 } 252 } 253 } 254