Split odsign_e2e_tests. am: 59064ebccc
Original change: https://android-review.googlesource.com/c/platform/art/+/1833296
Change-Id: I97da3b5c01ed136096c0d618bdb4c3ccb88cc22f
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
new file mode 100644
index 0000000..a6e9e73
--- /dev/null
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
@@ -0,0 +1,248 @@
+/*
+ * 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 org.junit.Assert.assertTrue;
+
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.regex.Matcher;
+
+/**
+ * Test to check end-to-end odrefresh invocations, but without odsign, fs-verity, and ART runtime
+ * involved.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OdrefreshHostTest extends BaseHostJUnit4Test {
+ private static final String CACHE_INFO_FILE =
+ OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME + "/cache-info.xml";
+
+ private static OdsignTestUtils sTestUtils;
+
+ private static Set<String> sZygoteArtifacts;
+ private static Set<String> sSystemServerArtifacts;
+
+ @BeforeClassWithInfo
+ public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
+ sTestUtils = new OdsignTestUtils(testInfo);
+ sTestUtils.installTestApex();
+
+ sZygoteArtifacts = new HashSet<>();
+ for (String zygoteName : sTestUtils.ZYGOTE_NAMES) {
+ sZygoteArtifacts.addAll(
+ sTestUtils.getZygoteLoadedArtifacts(zygoteName).orElse(new HashSet<>()));
+ }
+ sSystemServerArtifacts = sTestUtils.getSystemServerLoadedArtifacts();
+ }
+
+ @AfterClassWithInfo
+ public static void afterClassWithDevice(TestInformation testInfo) throws Exception {
+ sTestUtils.uninstallTestApex();
+ }
+
+ @Test
+ public void verifyArtSamegradeUpdateTriggersCompilation() throws Exception {
+ simulateArtApexUpgrade();
+ sTestUtils.removeCompilationLogToAvoidBackoff();
+ long timeMs = getCurrentTimeMs();
+ getDevice().executeShellV2Command("odrefresh --compile");
+
+ assertArtifactsModifiedAfter(sZygoteArtifacts, timeMs);
+ assertArtifactsModifiedAfter(sSystemServerArtifacts, timeMs);
+ }
+
+ @Test
+ public void verifyOtherApexSamegradeUpdateTriggersCompilation() throws Exception {
+ simulateApexUpgrade();
+ sTestUtils.removeCompilationLogToAvoidBackoff();
+ long timeMs = getCurrentTimeMs();
+ getDevice().executeShellV2Command("odrefresh --compile");
+
+ assertArtifactsNotModifiedAfter(sZygoteArtifacts, timeMs);
+ assertArtifactsModifiedAfter(sSystemServerArtifacts, timeMs);
+ }
+
+ @Test
+ public void verifyBootClasspathOtaTriggersCompilation() throws Exception {
+ simulateBootClasspathOta();
+ sTestUtils.removeCompilationLogToAvoidBackoff();
+ long timeMs = getCurrentTimeMs();
+ getDevice().executeShellV2Command("odrefresh --compile");
+
+ assertArtifactsModifiedAfter(sZygoteArtifacts, timeMs);
+ assertArtifactsModifiedAfter(sSystemServerArtifacts, timeMs);
+ }
+
+ @Test
+ public void verifySystemServerOtaTriggersCompilation() throws Exception {
+ simulateSystemServerOta();
+ sTestUtils.removeCompilationLogToAvoidBackoff();
+ long timeMs = getCurrentTimeMs();
+ getDevice().executeShellV2Command("odrefresh --compile");
+
+ assertArtifactsNotModifiedAfter(sZygoteArtifacts, timeMs);
+ assertArtifactsModifiedAfter(sSystemServerArtifacts, timeMs);
+ }
+
+ @Test
+ public void verifyNoCompilationWhenCacheIsGood() throws Exception {
+ sTestUtils.removeCompilationLogToAvoidBackoff();
+ long timeMs = getCurrentTimeMs();
+ getDevice().executeShellV2Command("odrefresh --compile");
+
+ assertArtifactsNotModifiedAfter(sZygoteArtifacts, timeMs);
+ assertArtifactsNotModifiedAfter(sSystemServerArtifacts, timeMs);
+ }
+
+ /**
+ * Checks the input line by line and replaces all lines that match the regex with the given
+ * replacement.
+ */
+ private String replaceLine(String input, String regex, String replacement) {
+ StringBuffer output = new StringBuffer();
+ Pattern p = Pattern.compile(regex);
+ for (String line : input.split("\n")) {
+ Matcher m = p.matcher(line);
+ if (m.matches()) {
+ m.appendReplacement(output, replacement);
+ output.append("\n");
+ } else {
+ output.append(line + "\n");
+ }
+ }
+ return output.toString();
+ }
+
+ /**
+ * Simulates that there is an OTA that updates a boot classpath jar.
+ */
+ private void simulateBootClasspathOta() throws Exception {
+ String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ // Replace the cached checksum of /system/framework/framework.jar with "aaaaaaaa".
+ cacheInfo = replaceLine(
+ cacheInfo,
+ "(.*/system/framework/framework\\.jar.*checksums=\").*?(\".*)",
+ "$1aaaaaaaa$2");
+ getDevice().pushString(cacheInfo, CACHE_INFO_FILE);
+ }
+
+ /**
+ * Simulates that there is an OTA that updates a system server jar.
+ */
+ private void simulateSystemServerOta() throws Exception {
+ String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ // Replace the cached checksum of /system/framework/services.jar with "aaaaaaaa".
+ cacheInfo = replaceLine(
+ cacheInfo,
+ "(.*/system/framework/services\\.jar.*checksums=\").*?(\".*)",
+ "$1aaaaaaaa$2");
+ getDevice().pushString(cacheInfo, CACHE_INFO_FILE);
+ }
+
+ /**
+ * Simulates that an ART APEX has been upgraded.
+ */
+ private void simulateArtApexUpgrade() throws Exception {
+ String apexInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ // Replace the lastUpdateMillis of com.android.art with "1".
+ apexInfo = replaceLine(
+ apexInfo,
+ "(.*com\\.android\\.art.*lastUpdateMillis=\").*?(\".*)",
+ "$11$2");
+ getDevice().pushString(apexInfo, CACHE_INFO_FILE);
+ }
+
+ /**
+ * Simulates that an APEX has been upgraded. We could install a real APEX, but that would
+ * introduce an extra dependency to this test, which we want to avoid.
+ */
+ private void simulateApexUpgrade() throws Exception {
+ String apexInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
+ // Replace the lastUpdateMillis of com.android.wifi with "1".
+ apexInfo = replaceLine(
+ apexInfo,
+ "(.*com\\.android\\.wifi.*lastUpdateMillis=\").*?(\".*)",
+ "$11$2");
+ getDevice().pushString(apexInfo, CACHE_INFO_FILE);
+ }
+
+ private long parseFormattedDateTime(String dateTimeStr) throws Exception {
+ DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
+ "yyyy-MM-dd HH:mm:ss.nnnnnnnnn Z");
+ ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeStr, formatter);
+ return zonedDateTime.toInstant().toEpochMilli();
+ }
+
+ private long getModifiedTimeMs(String filename) throws Exception {
+ // We can't use the "-c '%.3Y'" flag when to get the timestamp because the Toybox's `stat`
+ // implementation truncates the timestamp to seconds, which is not accurate enough, so we
+ // use "-c '%%y'" and parse the time ourselves.
+ String dateTimeStr = getDevice()
+ .executeShellCommand(String.format("stat -c '%%y' '%s'", filename))
+ .trim();
+ return parseFormattedDateTime(dateTimeStr);
+ }
+
+ private long getCurrentTimeMs() throws Exception {
+ // We can't use getDevice().getDeviceDate() because it truncates the timestamp to seconds,
+ // which is not accurate enough.
+ String dateTimeStr = getDevice()
+ .executeShellCommand("date +'%Y-%m-%d %H:%M:%S.%N %z'")
+ .trim();
+ return parseFormattedDateTime(dateTimeStr);
+ }
+
+ private void assertArtifactsModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
+ for (String artifact : artifacts) {
+ long modifiedTime = getModifiedTimeMs(artifact);
+ assertTrue(
+ String.format(
+ "Artifact %s is not re-compiled. Modified time: %d, Reference time: %d",
+ artifact,
+ modifiedTime,
+ timeMs),
+ modifiedTime > timeMs);
+ }
+ }
+
+ private void assertArtifactsNotModifiedAfter(Set<String> artifacts, long timeMs)
+ throws Exception {
+ for (String artifact : artifacts) {
+ long modifiedTime = getModifiedTimeMs(artifact);
+ assertTrue(
+ String.format(
+ "Artifact %s is unexpectedly re-compiled. " +
+ "Modified time: %d, Reference time: %d",
+ artifact,
+ modifiedTime,
+ timeMs),
+ modifiedTime < timeMs);
+ }
+ }
+}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
new file mode 100644
index 0000000..c51b0c6
--- /dev/null
+++ b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
@@ -0,0 +1,143 @@
+/*
+ * 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.assertNotNull;
+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.invoker.TestInformation;
+import com.android.tradefed.util.CommandResult;
+
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+
+public class OdsignTestUtils {
+ public static final String ART_APEX_DALVIK_CACHE_DIRNAME =
+ "/data/misc/apexdata/com.android.art/dalvik-cache";
+
+ public static final List<String> ZYGOTE_NAMES = List.of("zygote", "zygote64");
+
+ private static final String APEX_FILENAME = "test_com.android.art.apex";
+
+ private static final String ODREFRESH_COMPILATION_LOG =
+ "/data/misc/odrefresh/compilation-log.txt";
+
+ private static final Duration BOOT_COMPLETE_TIMEOUT = Duration.ofMinutes(2);
+
+ private final InstallUtilsHost mInstallUtils;
+ private final TestInformation mTestInfo;
+
+ public OdsignTestUtils(TestInformation testInfo) throws Exception {
+ assertNotNull(testInfo.getDevice());
+ mInstallUtils = new InstallUtilsHost(testInfo);
+ mTestInfo = testInfo;
+ }
+
+ public void installTestApex() throws Exception {
+ assumeTrue("Updating APEX is not supported", mInstallUtils.isApexUpdateSupported());
+ mInstallUtils.installApexes(APEX_FILENAME);
+ removeCompilationLogToAvoidBackoff();
+ reboot();
+ }
+
+ public void uninstallTestApex() throws Exception {
+ ApexInfo apex = mInstallUtils.getApexInfo(mInstallUtils.getTestFile(APEX_FILENAME));
+ mTestInfo.getDevice().uninstallPackage(apex.name);
+ removeCompilationLogToAvoidBackoff();
+ reboot();
+ }
+
+ public Set<String> getMappedArtifacts(String pid, String grepPattern) throws Exception {
+ final String grepCommand = String.format("grep \"%s\" /proc/%s/maps", grepPattern, pid);
+ CommandResult result = mTestInfo.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;
+ }
+
+ /**
+ * Returns the mapped artifacts of the Zygote process, or {@code Optional.empty()} if the
+ * process does not exist.
+ */
+ public Optional<Set<String>> getZygoteLoadedArtifacts(String zygoteName) throws Exception {
+ final CommandResult result =
+ mTestInfo.getDevice().executeShellV2Command("pidof " + zygoteName);
+ if (result.getExitCode() != 0) {
+ return Optional.empty();
+ }
+ // There may be multiple Zygote processes when Zygote just forks and has not executed any
+ // app binary. We can take any of the pids.
+ // We can't use the "-s" flag when calling `pidof` because the Toybox's `pidof`
+ // implementation is wrong and it outputs multiple pids regardless of the "-s" flag, so we
+ // split the output and take the first pid ourselves.
+ final String zygotePid = result.getStdout().trim().split("\\s+")[0];
+ assertTrue(!zygotePid.isEmpty());
+
+ final String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + ".*boot-framework";
+ return Optional.of(getMappedArtifacts(zygotePid, grepPattern));
+ }
+
+ public Set<String> getSystemServerLoadedArtifacts() throws Exception {
+ final CommandResult result =
+ mTestInfo.getDevice().executeShellV2Command("pidof system_server");
+ assertTrue(result.toString(), result.getExitCode() == 0);
+ final String systemServerPid = result.getStdout().trim();
+ assertTrue(!systemServerPid.isEmpty());
+ assertTrue(
+ "There should be exactly one `system_server` process",
+ systemServerPid.matches("\\d+"));
+
+ // 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";
+ return getMappedArtifacts(systemServerPid, grepPattern);
+ }
+
+ public boolean haveCompilationLog() throws Exception {
+ CommandResult result =
+ mTestInfo.getDevice().executeShellV2Command("stat " + ODREFRESH_COMPILATION_LOG);
+ return result.getExitCode() == 0;
+ }
+
+ public void removeCompilationLogToAvoidBackoff() throws Exception {
+ mTestInfo.getDevice().executeShellCommand("rm -f " + ODREFRESH_COMPILATION_LOG);
+ }
+
+ public void reboot() throws Exception {
+ mTestInfo.getDevice().reboot();
+ boolean success =
+ mTestInfo.getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT.toMillis());
+ assertWithMessage("Device didn't boot in %s", BOOT_COMPLETE_TIMEOUT).that(success).isTrue();
+ }
+}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
index 2e20591..ca53188 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
@@ -16,101 +16,50 @@
package com.android.tests.odsign;
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertNotNull;
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.invoker.TestInformation;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
-import com.android.tradefed.util.CommandResult;
-import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.MethodSorters;
-import java.time.Duration;
-import java.time.ZonedDateTime;
-import java.time.format.DateTimeFormatter;
-import java.util.Arrays;
-import java.util.HashSet;
+import java.util.List;
import java.util.Optional;
import java.util.Set;
-import java.util.regex.Pattern;
-import java.util.regex.Matcher;
import java.util.stream.Collectors;
+/**
+ * Test to check if odrefresh, odsign, fs-verity, and ART runtime work together properly.
+ */
@RunWith(DeviceJUnit4ClassRunner.class)
-@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class OnDeviceSigningHostTest extends BaseHostJUnit4Test {
+ private static final List<String> APP_ARTIFACT_EXTENSIONS = List.of(".art", ".odex", ".vdex");
- 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 static final String CACHE_INFO_FILE = ART_APEX_DALVIK_CACHE_DIRNAME + "/cache-info.xml";
-
- 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 List<String> BCP_ARTIFACT_EXTENSIONS = List.of(".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 static final Duration BOOT_COMPLETE_TIMEOUT = Duration.ofMinutes(2);
-
- private static final String[] ZYGOTE_NAMES = new String[] {"zygote", "zygote64"};
-
- private static InstallUtilsHost sInstallUtils;
- private static TestInformation sTestInfo;
-
- private static Set<String> sZygoteArtifacts;
- private static Set<String> sSystemServerArtifacts;
+ private static OdsignTestUtils sTestUtils;
@BeforeClassWithInfo
public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
- assertNotNull(testInfo.getDevice());
- sInstallUtils = new InstallUtilsHost(testInfo);
- sTestInfo = testInfo;
-
- assumeTrue("Updating APEX is not supported", sInstallUtils.isApexUpdateSupported());
- sInstallUtils.installApexes(APEX_FILENAME);
- removeCompilationLogToAvoidBackoff();
- reboot();
-
- sZygoteArtifacts = new HashSet<>();
- for (String zygoteName : ZYGOTE_NAMES) {
- sZygoteArtifacts.addAll(getZygoteLoadedArtifacts(zygoteName).orElse(new HashSet<>()));
- }
- sSystemServerArtifacts = getSystemServerLoadedArtifacts();
+ sTestUtils = new OdsignTestUtils(testInfo);
+ sTestUtils.installTestApex();;
}
@AfterClassWithInfo
public static void afterClassWithDevice(TestInformation testInfo) throws Exception {
- ApexInfo apex = sInstallUtils.getApexInfo(sInstallUtils.getTestFile(APEX_FILENAME));
- testInfo.getDevice().uninstallPackage(apex.name);
- removeCompilationLogToAvoidBackoff();
- reboot();
+ sTestUtils.uninstallTestApex();
}
- // Test cases starts with `testA` check if odrefresh, odsign, fs-verity, and ART runtime work
- // together properly.
-
@Test
- public void testAVerifyArtUpgradeSignsFiles() throws Exception {
+ public void verifyArtUpgradeSignsFiles() throws Exception {
installPackage(TEST_APP_APK);
DeviceTestRunOptions options = new DeviceTestRunOptions(TEST_APP_PACKAGE_NAME);
options.setTestClassName(TEST_APP_PACKAGE_NAME + ".ArtifactsSignedTest");
@@ -119,7 +68,7 @@
}
@Test
- public void testAVerifyArtUpgradeGeneratesRequiredArtifacts() throws Exception {
+ public void verifyArtUpgradeGeneratesRequiredArtifacts() throws Exception {
installPackage(TEST_APP_APK);
DeviceTestRunOptions options = new DeviceTestRunOptions(TEST_APP_PACKAGE_NAME);
options.setTestClassName(TEST_APP_PACKAGE_NAME + ".ArtifactsSignedTest");
@@ -127,59 +76,27 @@
runDeviceTests(options);
}
- private static Set<String> getMappedArtifacts(String pid, String grepPattern) throws Exception {
- final String grepCommand = String.format("grep \"%s\" /proc/%s/maps", grepPattern, pid);
- CommandResult result = sTestInfo.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;
+ @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", sTestUtils.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();
+ verifySystemServerLoadedArtifacts();
}
- /**
- * Returns the mapped artifacts of the Zygote process, or {@code Optional.empty()} if the
- * process does not exist.
- */
- private static Optional<Set<String>> getZygoteLoadedArtifacts(String zygoteName)
- throws Exception {
- final CommandResult result =
- sTestInfo.getDevice().executeShellV2Command("pidof " + zygoteName);
- if (result.getExitCode() != 0) {
- return Optional.empty();
- }
- // There may be multiple Zygote processes when Zygote just forks and has not executed any
- // app binary. We can take any of the pids.
- // We can't use the "-s" flag when calling `pidof` because the Toybox's `pidof`
- // implementation is wrong and it outputs multiple pids regardless of the "-s" flag, so we
- // split the output and take the first pid ourselves.
- final String zygotePid = result.getStdout().trim().split("\\s+")[0];
- assertTrue(!zygotePid.isEmpty());
-
- final String grepPattern = ART_APEX_DALVIK_CACHE_DIRNAME + ".*boot-framework";
- return Optional.of(getMappedArtifacts(zygotePid, grepPattern));
- }
-
- private static Set<String> getSystemServerLoadedArtifacts() throws Exception {
- final CommandResult result =
- sTestInfo.getDevice().executeShellV2Command("pidof system_server");
- assertTrue(result.toString(), result.getExitCode() == 0);
- final String systemServerPid = result.getStdout().trim();
- assertTrue(!systemServerPid.isEmpty());
- assertTrue(
- "There should be exactly one `system_server` process",
- systemServerPid.matches("\\d+"));
-
- // 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";
- return getMappedArtifacts(systemServerPid, grepPattern);
+ @Test
+ public void verifyGeneratedArtifactsLoadedAfterReboot() throws Exception {
+ sTestUtils.reboot();
+ verifyGeneratedArtifactsLoaded();
}
private String[] getSystemServerClasspath() throws Exception {
@@ -201,12 +118,13 @@
String[] classpathElements = getSystemServerClasspath();
assertTrue("SYSTEMSERVERCLASSPATH is empty", classpathElements.length > 0);
- final Set<String> mappedArtifacts = getSystemServerLoadedArtifacts();
+ final Set<String> mappedArtifacts = sTestUtils.getSystemServerLoadedArtifacts();
assertTrue(
- "No mapped artifacts under " + ART_APEX_DALVIK_CACHE_DIRNAME,
+ "No mapped artifacts under " + OdsignTestUtils.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);
+ final String isaCacheDirectory =
+ String.format("%s/%s", OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME, isa);
// Check components in the system_server classpath have mapped artifacts.
for (String element : classpathElements) {
@@ -221,7 +139,7 @@
for (String mappedArtifact : mappedArtifacts) {
// Check the mapped artifact has a .art, .odex or .vdex extension.
final boolean knownArtifactKind =
- Arrays.stream(APP_ARTIFACT_EXTENSIONS).anyMatch(e -> mappedArtifact.endsWith(e));
+ APP_ARTIFACT_EXTENSIONS.stream().anyMatch(e -> mappedArtifact.endsWith(e));
assertTrue("Unknown artifact kind: " + mappedArtifact, knownArtifactKind);
}
}
@@ -245,8 +163,9 @@
// instances 32-bit and 64-bit unspecialized app_process processes.
// (frameworks/base/cmds/app_process).
int zygoteCount = 0;
- for (String zygoteName : ZYGOTE_NAMES) {
- final Optional<Set<String>> mappedArtifacts = getZygoteLoadedArtifacts(zygoteName);
+ for (String zygoteName : OdsignTestUtils.ZYGOTE_NAMES) {
+ final Optional<Set<String>> mappedArtifacts =
+ sTestUtils.getZygoteLoadedArtifacts(zygoteName);
if (!mappedArtifacts.isPresent()) {
continue;
}
@@ -255,227 +174,4 @@
}
assertTrue("No zygote processes found", zygoteCount > 0);
}
-
- @Test
- public void testAVerifyGeneratedArtifactsLoaded() 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();
- verifySystemServerLoadedArtifacts();
- }
-
- @Test
- public void testAVerifyGeneratedArtifactsLoadedAfterReboot() throws Exception {
- reboot();
- testAVerifyGeneratedArtifactsLoaded();
- }
-
- /**
- * Checks the input line by line and replaces all lines that match the regex with the given
- * replacement.
- */
- String replaceLine(String input, String regex, String replacement) {
- StringBuffer output = new StringBuffer();
- Pattern p = Pattern.compile(regex);
- for (String line : input.split("\n")) {
- Matcher m = p.matcher(line);
- if (m.matches()) {
- m.appendReplacement(output, replacement);
- output.append("\n");
- } else {
- output.append(line + "\n");
- }
- }
- return output.toString();
- }
-
- /**
- * Simulates that there is an OTA that updates a boot classpath jar.
- */
- void simulateBootClasspathOta() throws Exception {
- String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
- // Replace the cached checksum of /system/framework/framework.jar with "aaaaaaaa".
- cacheInfo = replaceLine(
- cacheInfo,
- "(.*/system/framework/framework\\.jar.*checksums=\").*?(\".*)",
- "$1aaaaaaaa$2");
- getDevice().pushString(cacheInfo, CACHE_INFO_FILE);
- }
-
- /**
- * Simulates that there is an OTA that updates a system server jar.
- */
- void simulateSystemServerOta() throws Exception {
- String cacheInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
- // Replace the cached checksum of /system/framework/services.jar with "aaaaaaaa".
- cacheInfo = replaceLine(
- cacheInfo,
- "(.*/system/framework/services\\.jar.*checksums=\").*?(\".*)",
- "$1aaaaaaaa$2");
- getDevice().pushString(cacheInfo, CACHE_INFO_FILE);
- }
-
- /**
- * Simulates that an ART APEX has been upgraded.
- */
- void simulateArtApexUpgrade() throws Exception {
- String apexInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
- // Replace the lastUpdateMillis of com.android.art with "1".
- apexInfo = replaceLine(
- apexInfo,
- "(.*com\\.android\\.art.*lastUpdateMillis=\").*?(\".*)",
- "$11$2");
- getDevice().pushString(apexInfo, CACHE_INFO_FILE);
- }
-
- /**
- * Simulates that an APEX has been upgraded. We could install a real APEX, but that would
- * introduce an extra dependency to this test, which we want to avoid.
- */
- void simulateApexUpgrade() throws Exception {
- String apexInfo = getDevice().pullFileContents(CACHE_INFO_FILE);
- // Replace the lastUpdateMillis of com.android.wifi with "1".
- apexInfo = replaceLine(
- apexInfo,
- "(.*com\\.android\\.wifi.*lastUpdateMillis=\").*?(\".*)",
- "$11$2");
- getDevice().pushString(apexInfo, CACHE_INFO_FILE);
- }
-
- long parseFormattedDateTime(String dateTimeStr) throws Exception {
- DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
- "yyyy-MM-dd HH:mm:ss.nnnnnnnnn Z");
- ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeStr, formatter);
- return zonedDateTime.toInstant().toEpochMilli();
- }
-
- long getModifiedTimeMs(String filename) throws Exception {
- // We can't use the "-c '%.3Y'" flag when to get the timestamp because the Toybox's `stat`
- // implementation truncates the timestamp to seconds, which is not accurate enough, so we
- // use "-c '%%y'" and parse the time ourselves.
- String dateTimeStr = getDevice()
- .executeShellCommand(String.format("stat -c '%%y' '%s'", filename))
- .trim();
- return parseFormattedDateTime(dateTimeStr);
- }
-
- long getCurrentTimeMs() throws Exception {
- // We can't use getDevice().getDeviceDate() because it truncates the timestamp to seconds,
- // which is not accurate enough.
- String dateTimeStr = getDevice()
- .executeShellCommand("date +'%Y-%m-%d %H:%M:%S.%N %z'")
- .trim();
- return parseFormattedDateTime(dateTimeStr);
- }
-
- void assertArtifactsModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
- for (String artifact : artifacts) {
- long modifiedTime = getModifiedTimeMs(artifact);
- assertTrue(
- String.format(
- "Artifact %s is not re-compiled. Modified time: %d, Reference time: %d",
- artifact,
- modifiedTime,
- timeMs),
- modifiedTime > timeMs);
- }
- }
-
- void assertArtifactsNotModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
- for (String artifact : artifacts) {
- long modifiedTime = getModifiedTimeMs(artifact);
- assertTrue(
- String.format(
- "Artifact %s is unexpectedly re-compiled. " +
- "Modified time: %d, Reference time: %d",
- artifact,
- modifiedTime,
- timeMs),
- modifiedTime < timeMs);
- }
- }
-
- // Test cases starts with `testB` check end-to-end odrefresh invocations, but without odsign,
- // fs-verity, and ART runtime involved. Do not add tests after `testB*` cases that check
- // fs-verity or runtime behaviors.
-
- @Test
- public void testBVerifyArtSamegradeUpdateTriggersCompilation() throws Exception {
- simulateArtApexUpgrade();
- removeCompilationLogToAvoidBackoff();
- long timeMs = getCurrentTimeMs();
- getDevice().executeShellV2Command("odrefresh --compile");
-
- assertArtifactsModifiedAfter(sZygoteArtifacts, timeMs);
- assertArtifactsModifiedAfter(sSystemServerArtifacts, timeMs);
- }
-
- @Test
- public void testBVerifyOtherApexSamegradeUpdateTriggersCompilation() throws Exception {
- simulateApexUpgrade();
- removeCompilationLogToAvoidBackoff();
- long timeMs = getCurrentTimeMs();
- getDevice().executeShellV2Command("odrefresh --compile");
-
- assertArtifactsNotModifiedAfter(sZygoteArtifacts, timeMs);
- assertArtifactsModifiedAfter(sSystemServerArtifacts, timeMs);
- }
-
- @Test
- public void testBVerifyBootClasspathOtaTriggersCompilation() throws Exception {
- simulateBootClasspathOta();
- removeCompilationLogToAvoidBackoff();
- long timeMs = getCurrentTimeMs();
- getDevice().executeShellV2Command("odrefresh --compile");
-
- assertArtifactsModifiedAfter(sZygoteArtifacts, timeMs);
- assertArtifactsModifiedAfter(sSystemServerArtifacts, timeMs);
- }
-
- @Test
- public void testBVerifySystemServerOtaTriggersCompilation() throws Exception {
- simulateSystemServerOta();
- removeCompilationLogToAvoidBackoff();
- long timeMs = getCurrentTimeMs();
- getDevice().executeShellV2Command("odrefresh --compile");
-
- assertArtifactsNotModifiedAfter(sZygoteArtifacts, timeMs);
- assertArtifactsModifiedAfter(sSystemServerArtifacts, timeMs);
- }
-
- @Test
- public void testBVerifyNoCompilationWhenCacheIsGood() throws Exception {
- removeCompilationLogToAvoidBackoff();
- long timeMs = getCurrentTimeMs();
- getDevice().executeShellV2Command("odrefresh --compile");
-
- assertArtifactsNotModifiedAfter(sZygoteArtifacts, timeMs);
- assertArtifactsNotModifiedAfter(sSystemServerArtifacts, timeMs);
- }
-
- private boolean haveCompilationLog() throws Exception {
- CommandResult result =
- getDevice().executeShellV2Command("stat " + ODREFRESH_COMPILATION_LOG);
- return result.getExitCode() == 0;
- }
-
- private static void removeCompilationLogToAvoidBackoff() throws Exception {
- sTestInfo.getDevice().executeShellCommand("rm -f " + ODREFRESH_COMPILATION_LOG);
- }
-
- private static void reboot() throws Exception {
- sTestInfo.getDevice().reboot();
- boolean success =
- sTestInfo.getDevice().waitForBootComplete(BOOT_COMPLETE_TIMEOUT.toMillis());
- assertWithMessage("Device didn't boot in %s", BOOT_COMPLETE_TIMEOUT).that(success).isTrue();
- }
}