260 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			260 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Java
		
	
	
	
/*
 | 
						|
 * Copyright (C) 2021 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.os.classpath;
 | 
						|
 | 
						|
import static android.compat.testing.Classpaths.ClasspathType.BOOTCLASSPATH;
 | 
						|
import static android.compat.testing.Classpaths.ClasspathType.DEX2OATBOOTCLASSPATH;
 | 
						|
import static android.compat.testing.Classpaths.ClasspathType.SYSTEMSERVERCLASSPATH;
 | 
						|
import static android.compat.testing.Classpaths.getJarsOnClasspath;
 | 
						|
import static com.android.os.classpath.ClasspathsTest.ClasspathSubject.assertThat;
 | 
						|
 | 
						|
import static com.google.common.base.Preconditions.checkArgument;
 | 
						|
import static com.google.common.truth.Fact.fact;
 | 
						|
import static com.google.common.truth.Fact.simpleFact;
 | 
						|
import static com.google.common.truth.Truth.assertAbout;
 | 
						|
import static com.google.common.truth.Truth.assertWithMessage;
 | 
						|
 | 
						|
import static org.junit.Assume.assumeTrue;
 | 
						|
 | 
						|
import com.android.modules.utils.build.testing.DeviceSdkLevel;
 | 
						|
import com.android.tradefed.device.ITestDevice;
 | 
						|
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 | 
						|
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 | 
						|
 | 
						|
import com.google.common.collect.ImmutableList;
 | 
						|
import com.google.common.truth.Fact;
 | 
						|
import com.google.common.truth.FailureMetadata;
 | 
						|
import com.google.common.truth.IterableSubject;
 | 
						|
import com.google.common.truth.Ordered;
 | 
						|
 | 
						|
import org.junit.Before;
 | 
						|
import org.junit.Test;
 | 
						|
import org.junit.runner.RunWith;
 | 
						|
 | 
						|
import java.nio.file.Paths;
 | 
						|
 | 
						|
/**
 | 
						|
 * Tests for the contents of *CLASSPATH environ variables on a device.
 | 
						|
 */
 | 
						|
@RunWith(DeviceJUnit4ClassRunner.class)
 | 
						|
public class ClasspathsTest extends BaseHostJUnit4Test {
 | 
						|
 | 
						|
    // A selection of jars on *CLASSPATH that cover all categories:
 | 
						|
    // - ART apex jar
 | 
						|
    // - Non-updatable apex jar
 | 
						|
    // - Updatable apex jar
 | 
						|
    // - System jar on BOOTCLASSPATH
 | 
						|
    // - System jar on SYSTEMSERVERCLASSPATH
 | 
						|
    private static final String FRAMEWORK_JAR = "/system/framework/framework.jar";
 | 
						|
    private static final String ICU4J_JAR = "/apex/com.android.i18n/javalib/core-icu4j.jar";
 | 
						|
    private static final String LIBART_JAR = "/apex/com.android.art/javalib/core-libart.jar";
 | 
						|
    private static final String SDKEXTENSIONS_JAR =
 | 
						|
            "/apex/com.android.sdkext/javalib/framework-sdkextensions.jar";
 | 
						|
    private static final String SERVICES_JAR = "/system/framework/services.jar";
 | 
						|
 | 
						|
    @Before
 | 
						|
    public void before() throws Exception {
 | 
						|
        ITestDevice device = getDevice();
 | 
						|
        DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(device);
 | 
						|
        assumeTrue(deviceSdkLevel.isDeviceAtLeastS());
 | 
						|
    }
 | 
						|
 | 
						|
    @Test
 | 
						|
    public void testBootclasspath() throws Exception {
 | 
						|
        ImmutableList<String> jars = getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
 | 
						|
 | 
						|
        assertThat(jars).containsNoDuplicates();
 | 
						|
 | 
						|
        assertThat(jars)
 | 
						|
                .containsAtLeast(LIBART_JAR, FRAMEWORK_JAR, ICU4J_JAR, SDKEXTENSIONS_JAR)
 | 
						|
                .inOrder();
 | 
						|
        assertThat(jars)
 | 
						|
                .doesNotContain(SERVICES_JAR);
 | 
						|
 | 
						|
        ImmutableList<String> expectedPrefixes = ImmutableList.of(
 | 
						|
                "/apex/com.android.art/",
 | 
						|
                "/system/",
 | 
						|
                "/system_ext/",
 | 
						|
                "/apex/com.android.i18n/",
 | 
						|
                "/apex/");
 | 
						|
        assertThat(jars)
 | 
						|
                .prefixesMatch(expectedPrefixes)
 | 
						|
                .inOrder();
 | 
						|
 | 
						|
        assertThat(getUpdatableApexes(jars)).isInOrder();
 | 
						|
    }
 | 
						|
 | 
						|
    @Test
 | 
						|
    public void testDex2oatBootclasspath() throws Exception {
 | 
						|
        ImmutableList<String> jars = getJarsOnClasspath(getDevice(), DEX2OATBOOTCLASSPATH);
 | 
						|
 | 
						|
        assertThat(jars).containsNoDuplicates();
 | 
						|
 | 
						|
        // DEX2OATBOOTCLASSPATH must only contain ART, core-icu4j, and platform system jars
 | 
						|
        assertThat(jars)
 | 
						|
                .containsAtLeast(LIBART_JAR, FRAMEWORK_JAR, ICU4J_JAR)
 | 
						|
                .inOrder();
 | 
						|
        assertThat(jars)
 | 
						|
                .containsNoneOf(SDKEXTENSIONS_JAR, SERVICES_JAR);
 | 
						|
 | 
						|
        // DEX2OATBOOTCLASSPATH must be a subset of BOOTCLASSPATH
 | 
						|
        ImmutableList<String> bootJars = getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
 | 
						|
        assertThat(bootJars).containsAtLeastElementsIn(jars);
 | 
						|
 | 
						|
        ImmutableList<String> expectedPrefixes = ImmutableList.of(
 | 
						|
                "/apex/com.android.art/", "/system/", "/system_ext/", "/apex/com.android.i18n/");
 | 
						|
        assertThat(jars)
 | 
						|
                .prefixesMatch(expectedPrefixes)
 | 
						|
                .inOrder();
 | 
						|
 | 
						|
        // No updatable jars on DEX2OATBOOTCLASSPATH
 | 
						|
        assertThat(getUpdatableApexes(jars)).isEmpty();
 | 
						|
    }
 | 
						|
 | 
						|
    @Test
 | 
						|
    public void testSystemServerClasspath() throws Exception {
 | 
						|
        ImmutableList<String> jars = getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
 | 
						|
 | 
						|
        assertThat(jars).containsNoDuplicates();
 | 
						|
 | 
						|
        assertThat(jars).containsNoneOf(LIBART_JAR, FRAMEWORK_JAR, ICU4J_JAR, SDKEXTENSIONS_JAR);
 | 
						|
        assertThat(jars).contains(SERVICES_JAR);
 | 
						|
 | 
						|
        ImmutableList<String> expectedPrefixes = ImmutableList.of(
 | 
						|
                "/system/", "/system_ext/", "/apex/");
 | 
						|
        assertThat(jars)
 | 
						|
                .prefixesMatch(expectedPrefixes)
 | 
						|
                .inOrder();
 | 
						|
 | 
						|
        assertThat(getUpdatableApexes(jars)).isInOrder();
 | 
						|
    }
 | 
						|
 | 
						|
    @Test
 | 
						|
    public void testDex2oatJarsAreFirstOnBootclasspath() throws Exception {
 | 
						|
        ImmutableList<String> bootJars = getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
 | 
						|
        ImmutableList<String> dex2oatJars = getJarsOnClasspath(getDevice(), DEX2OATBOOTCLASSPATH);
 | 
						|
 | 
						|
        // All preopt jars on BOOTCLASSPATH must come before updatable jars.
 | 
						|
        assertThat(bootJars).startsWith(dex2oatJars);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Returns a derived subject with names of the updatable APEXes preserving the original
 | 
						|
     * order.
 | 
						|
     */
 | 
						|
    private static ImmutableList<String> getUpdatableApexes(ImmutableList<String> jars) {
 | 
						|
        return jars.stream()
 | 
						|
                .filter(jar -> jar.startsWith("/apex"))
 | 
						|
                // ICU4J_JAR is the last non-updatable APEX jar, i.e. everything after is
 | 
						|
                // considered to be an updatable APEX jar
 | 
						|
                .dropWhile(jar -> !jar.equals(ICU4J_JAR))
 | 
						|
                .skip(1)
 | 
						|
                // Map to APEX name from "/apex/<name>/javalibs/foo.jar"
 | 
						|
                .map(jar -> Paths.get(jar).getName(1).toString())
 | 
						|
                .collect(ImmutableList.toImmutableList());
 | 
						|
    }
 | 
						|
 | 
						|
    final static class ClasspathSubject extends IterableSubject {
 | 
						|
 | 
						|
        private static final Ordered EMPTY_ORDERED = () -> {
 | 
						|
        };
 | 
						|
 | 
						|
        private final ImmutableList<String> actual;
 | 
						|
 | 
						|
        protected ClasspathSubject(FailureMetadata metadata, ImmutableList<String> iterable) {
 | 
						|
            super(metadata, iterable);
 | 
						|
            actual = iterable;
 | 
						|
        }
 | 
						|
 | 
						|
        public static ClasspathSubject assertThat(ImmutableList<String> jars) {
 | 
						|
            return assertAbout(ClasspathSubject::new).that(jars);
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Checks that the actual iterable contains only jars that start with the expected prefixes
 | 
						|
         * or fails.
 | 
						|
         *
 | 
						|
         * <p>To also test that the prefixes appear in the given order, make a call to {@code
 | 
						|
         * inOrder}
 | 
						|
         * on the object returned by this method. The expected elements must appear in the given
 | 
						|
         * order within the actual elements.
 | 
						|
         */
 | 
						|
        public Ordered prefixesMatch(ImmutableList<String> expected) {
 | 
						|
            checkArgument(expected.stream().distinct().count() == expected.size(),
 | 
						|
                    "No duplicates are allowed in expected values.");
 | 
						|
 | 
						|
            ImmutableList.Builder<String> unexpectedJars = ImmutableList.builder();
 | 
						|
            boolean ordered = true;
 | 
						|
            int currentPrefixIndex = expected.isEmpty() ? -1 : 0;
 | 
						|
            for (String jar : actual) {
 | 
						|
                int prefixIndex = findFirstMatchingPrefix(jar, expected);
 | 
						|
                if (prefixIndex == -1) {
 | 
						|
                    unexpectedJars.add(jar);
 | 
						|
                    continue;
 | 
						|
                }
 | 
						|
                if (prefixIndex != currentPrefixIndex) {
 | 
						|
                    if (prefixIndex < currentPrefixIndex) {
 | 
						|
                        ordered = false;
 | 
						|
                    }
 | 
						|
                    currentPrefixIndex = prefixIndex;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            ImmutableList<String> unexpected = unexpectedJars.build();
 | 
						|
            if (!unexpected.isEmpty()) {
 | 
						|
                Fact expectedOrder = fact("expected jar filepaths to be prefixes with one of",
 | 
						|
                        expected);
 | 
						|
                ImmutableList.Builder<Fact> facts = ImmutableList.builder();
 | 
						|
                for (String e : unexpected) {
 | 
						|
                    facts.add(fact("unexpected", e));
 | 
						|
                }
 | 
						|
                facts.add(simpleFact("---"));
 | 
						|
                facts.add(simpleFact(""));
 | 
						|
                failWithoutActual(expectedOrder, facts.build().toArray(new Fact[0]));
 | 
						|
                return EMPTY_ORDERED;
 | 
						|
            }
 | 
						|
 | 
						|
            return ordered ? EMPTY_ORDERED : () -> failWithActual(
 | 
						|
                    simpleFact("all jars have valid partitions, but the order was wrong"),
 | 
						|
                    fact("expected order", expected)
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Checks that the actual iterable starts with expected elements.
 | 
						|
         */
 | 
						|
        public void startsWith(ImmutableList<String> expected) {
 | 
						|
            if (actual.size() < expected.size()) {
 | 
						|
                failWithActual("expected at least number of elements", expected.size());
 | 
						|
                return;
 | 
						|
            }
 | 
						|
            assertWithMessage("Unexpected initial elements of the list")
 | 
						|
                    .that(actual.subList(0, expected.size())).isEqualTo(expected);
 | 
						|
        }
 | 
						|
 | 
						|
        private static int findFirstMatchingPrefix(String value, ImmutableList<String> prefixes) {
 | 
						|
            for (int i = 0; i < prefixes.size(); i++) {
 | 
						|
                if (value.startsWith(prefixes.get(i))) {
 | 
						|
                    return i;
 | 
						|
                }
 | 
						|
            }
 | 
						|
            return -1;
 | 
						|
        }
 | 
						|
 | 
						|
    }
 | 
						|
}
 |