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