blob: d680477b4c82f56da23811e85ba34ffb806851aa [file] [log] [blame]
/*
* 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.tests.odsign;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import android.cts.install.lib.host.InstallUtilsHost;
import com.android.tradefed.device.ITestDevice.ApexInfo;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
import com.android.tradefed.util.CommandResult;
import org.junit.After;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;
@RunWith(DeviceJUnit4ClassRunner.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
private static final String APEX_FILENAME = "test_com.android.art.apex";
private static final String ART_APEX_DALVIK_CACHE_DIRNAME =
"/data/misc/apexdata/com.android.art/dalvik-cache";
private static final String ODREFRESH_COMPILATION_LOG =
"/data/misc/odrefresh/compilation-log.txt";
private final String[] APP_ARTIFACT_EXTENSIONS = new String[] {".art", ".odex", ".vdex"};
private final String[] BCP_ARTIFACT_EXTENSIONS = new String[] {".art", ".oat", ".vdex"};
private static final String TEST_APP_PACKAGE_NAME = "com.android.tests.odsign";
private static final String TEST_APP_APK = "odsign_e2e_test_app.apk";
private final InstallUtilsHost mInstallUtils = new InstallUtilsHost(this);
private static final Duration BOOT_COMPLETE_TIMEOUT = Duration.ofMinutes(2);
@Before
public void setUp() throws Exception {
assumeTrue("Updating APEX is not supported", mInstallUtils.isApexUpdateSupported());
installPackage(TEST_APP_APK);
mInstallUtils.installApexes(APEX_FILENAME);
removeCompilationLogToAvoidBackoff();
reboot();
}
@After
public void cleanup() throws Exception {
ApexInfo apex = mInstallUtils.getApexInfo(mInstallUtils.getTestFile(APEX_FILENAME));
getDevice().uninstallPackage(apex.name);
removeCompilationLogToAvoidBackoff();
reboot();
}
@Test
public void verifyArtUpgradeSignsFiles() throws Exception {
DeviceTestRunOptions options = new DeviceTestRunOptions(TEST_APP_PACKAGE_NAME);
options.setTestClassName(TEST_APP_PACKAGE_NAME + ".ArtifactsSignedTest");
options.setTestMethodName("testArtArtifactsHaveFsverity");
runDeviceTests(options);
}
@Test
public void verifyArtUpgradeGeneratesRequiredArtifacts() throws Exception {
DeviceTestRunOptions options = new DeviceTestRunOptions(TEST_APP_PACKAGE_NAME);
options.setTestClassName(TEST_APP_PACKAGE_NAME + ".ArtifactsSignedTest");
options.setTestMethodName("testGeneratesRequiredArtArtifacts");
runDeviceTests(options);
}
private Set<String> getMappedArtifacts(String pid, String grepPattern) throws Exception {
final String grepCommand = String.format("grep \"%s\" /proc/%s/maps", grepPattern, pid);
CommandResult result = getDevice().executeShellV2Command(grepCommand);
assertTrue(result.toString(), result.getExitCode() == 0);
Set<String> mappedFiles = new HashSet<>();
for (String line : result.getStdout().split("\\R")) {
int start = line.indexOf(ART_APEX_DALVIK_CACHE_DIRNAME);
if (line.contains("[")) {
continue; // ignore anonymously mapped sections which are quoted in square braces.
}
mappedFiles.add(line.substring(start));
}
return mappedFiles;
}
private String[] getSystemServerClasspath() throws Exception {
String systemServerClasspath =
getDevice().executeShellCommand("echo $SYSTEMSERVERCLASSPATH");
return systemServerClasspath.split(":");
}
private String getSystemServerIsa(String mappedArtifact) {
// Artifact path for system server artifacts has the form:
// ART_APEX_DALVIK_CACHE_DIRNAME + "/<arch>/system@framework@some.jar@classes.odex"
// `mappedArtifacts` may include other artifacts, such as boot-framework.oat that are not
// prefixed by the architecture.
String[] pathComponents = mappedArtifact.split("/");
return pathComponents[pathComponents.length - 2];
}
private void verifySystemServerLoadedArtifacts() throws Exception {
String[] classpathElements = getSystemServerClasspath();
assertTrue("SYSTEMSERVERCLASSPATH is empty", classpathElements.length > 0);
String systemServerPid = getDevice().executeShellCommand("pgrep system_server");
assertTrue(systemServerPid != null);
// system_server artifacts are in the APEX data dalvik cache and names all contain
// the word "@classes". Look for mapped files that match this pattern in the proc map for
// system_server.
final String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + ".*@classes";
final Set<String> mappedArtifacts = getMappedArtifacts(systemServerPid, grepPattern);
assertTrue(
"No mapped artifacts under " + ART_APEX_DALVIK_CACHE_DIRNAME,
mappedArtifacts.size() > 0);
final String isa = getSystemServerIsa(mappedArtifacts.iterator().next());
final String isaCacheDirectory = String.format("%s/%s", ART_APEX_DALVIK_CACHE_DIRNAME, isa);
// Check the non-APEX components in the system_server classpath have mapped artifacts.
for (String element : classpathElements) {
// Skip system_server classpath elements from APEXes as these are not currently
// compiled.
if (element.startsWith("/apex")) {
continue;
}
String escapedPath = element.substring(1).replace('/', '@');
for (String extension : APP_ARTIFACT_EXTENSIONS) {
final String fullArtifactPath =
String.format("%s/%s@classes%s", isaCacheDirectory, escapedPath, extension);
assertTrue(
"Missing " + fullArtifactPath, mappedArtifacts.contains(fullArtifactPath));
}
}
for (String mappedArtifact : mappedArtifacts) {
// Check no APEX JAR artifacts are mapped for system_server since if there
// are, then the policy around not compiling APEX jars for system_server has
// changed and this test needs updating here and in the system_server classpath
// check above.
assertTrue(
"Unexpected mapped artifact: " + mappedArtifact,
mappedArtifact.contains("/apex"));
// Check the mapped artifact has a .art, .odex or .vdex extension.
final boolean knownArtifactKind =
Arrays.stream(APP_ARTIFACT_EXTENSIONS)
.anyMatch(e -> mappedArtifact.endsWith(e));
assertTrue("Unknown artifact kind: " + mappedArtifact, knownArtifactKind);
}
}
private void verifyZygoteLoadedArtifacts(String zygoteName, String zygotePid) throws Exception {
final String bootExtensionName = "boot-framework";
final Set<String> mappedArtifacts = getMappedArtifacts(zygotePid, bootExtensionName);
assertTrue("Expect 3 boot-framework artifacts", mappedArtifacts.size() == 3);
String allArtifacts = mappedArtifacts.stream().collect(Collectors.joining(","));
for (String extension : BCP_ARTIFACT_EXTENSIONS) {
final String artifact = bootExtensionName + extension;
final boolean found = mappedArtifacts.stream().anyMatch(a -> a.endsWith(artifact));
assertTrue(zygoteName + " " + artifact + " not found: '" + allArtifacts + "'", found);
}
}
private void verifyZygotesLoadedArtifacts() throws Exception {
// There are potentially two zygote processes "zygote" and "zygote64". These are
// instances 32-bit and 64-bit unspecialized app_process processes.
// (frameworks/base/cmds/app_process).
int zygoteCount = 0;
for (String zygoteName : new String[] {"zygote", "zygote64"}) {
final CommandResult pgrepResult =
getDevice().executeShellV2Command("pgrep " + zygoteName);
if (pgrepResult.getExitCode() != 0) {
continue;
}
final String zygotePid = pgrepResult.getStdout();
verifyZygoteLoadedArtifacts(zygoteName, zygotePid);
zygoteCount += 1;
}
assertTrue("No zygote processes found", zygoteCount > 0);
}
@Test
public void verifyGeneratedArtifactsLoaded() throws Exception {
// Checking zygote and system_server need the device have adb root to walk process maps.
final boolean adbEnabled = getDevice().enableAdbRoot();
assertTrue("ADB root failed and required to get process maps", adbEnabled);
// Check there is a compilation log, we expect compilation to have occurred.
assertTrue("Compilation log not found", haveCompilationLog());
// Check both zygote and system_server processes to see that they have loaded the
// artifacts compiled and signed by odrefresh and odsign. We check both here rather than
// having a separate test because the device reboots between each @Test method and
// that is an expensive use of time.
verifyZygotesLoadedArtifacts();
// Temporarily disable system_server artifacts test (b/180949581).
if (false) {
verifySystemServerLoadedArtifacts();
}
}
private boolean haveCompilationLog() throws Exception {
CommandResult result =
getDevice().executeShellV2Command("stat " + ODREFRESH_COMPILATION_LOG);
return result.getExitCode() == 0;
}
private void removeCompilationLogToAvoidBackoff() throws Exception {
getDevice().executeShellCommand("rm -f " + ODREFRESH_COMPILATION_LOG);
}
private void reboot() throws Exception {
getDevice().reboot();
boolean success = getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT.toMillis());
assertWithMessage("Device didn't boot in %s", BOOT_COMPLETE_TIMEOUT).that(success).isTrue();
}
}