Add tests for cases where APEXes are factory-installed.
Bug: 272245228
Test: atest odsign_e2e_tests_full:OdrefreshFactoryWithCacheInfoHostTest
Test: atest odsign_e2e_tests_full:OdrefreshFactoryWithoutCacheInfoHostTest
Change-Id: I7444f5d2c2beac3c0467dd8eb18d27f757df6033
diff --git a/test/odsign/test-src/com/android/tests/odsign/DeviceState.java b/test/odsign/test-src/com/android/tests/odsign/DeviceState.java
index 8379cba..c6ca53c 100644
--- a/test/odsign/test-src/com/android/tests/odsign/DeviceState.java
+++ b/test/odsign/test-src/com/android/tests/odsign/DeviceState.java
@@ -40,7 +40,6 @@
/** A helper class that can mutate the device state and restore it afterwards. */
public class DeviceState {
- private static final String APEX_INFO_FILE = "/apex/apex-info-list.xml";
private static final String TEST_JAR_RESOURCE_NAME = "/art-gtest-jars-Main.jar";
private static final String PHENOTYPE_FLAG_NAMESPACE = "runtime_native_boot";
private static final String ART_APEX_DALVIK_CACHE_BACKUP_DIRNAME =
@@ -53,6 +52,7 @@
private Set<String> mMountPoints = new HashSet<>();
private Map<String, String> mMutatedProperties = new HashMap<>();
private Set<String> mMutatedPhenotypeFlags = new HashSet<>();
+ private Map<String, String> mDeletedFiles = new HashMap<>();
private boolean mHasArtifactsBackup = false;
public DeviceState(TestInformation testInfo) throws Exception {
@@ -85,6 +85,14 @@
"device_config set_sync_disabled_for_tests none");
}
+ for (var entry : mDeletedFiles.entrySet()) {
+ mTestInfo.getDevice().executeShellV2Command(
+ String.format("cp '%s' '%s'", entry.getValue(), entry.getKey()));
+ mTestInfo.getDevice().executeShellV2Command(String.format("rm '%s'", entry.getValue()));
+ mTestInfo.getDevice().executeShellV2Command(
+ String.format("restorecon '%s'", entry.getKey()));
+ }
+
if (mHasArtifactsBackup) {
mTestInfo.getDevice().executeShellV2Command(
String.format("rm -rf '%s'", OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME));
@@ -96,17 +104,15 @@
/** Simulates that the ART APEX has been upgraded. */
public void simulateArtApexUpgrade() throws Exception {
- try (var xmlMutator = new XmlMutator(APEX_INFO_FILE)) {
- NodeList list = xmlMutator.getDocument().getElementsByTagName("apex-info");
- for (int i = 0; i < list.getLength(); i++) {
- Element node = (Element) list.item(i);
- if (node.getAttribute("moduleName").equals("com.android.art")
- && node.getAttribute("isActive").equals("true")) {
- node.setAttribute("isFactory", "false");
- node.setAttribute("lastUpdateMillis", "1");
- }
- }
- }
+ updateApexInfo("com.android.art", false /* isFactory */);
+ }
+
+ /**
+ * Simulates that the new ART APEX has been uninstalled (i.e., the ART module goes back to the
+ * factory version).
+ */
+ public void simulateArtApexUninstall() throws Exception {
+ updateApexInfo("com.android.art", true /* isFactory */);
}
/**
@@ -114,14 +120,27 @@
* introduce an extra dependency to this test, which we want to avoid.
*/
public void simulateApexUpgrade() throws Exception {
- try (var xmlMutator = new XmlMutator(APEX_INFO_FILE)) {
+ updateApexInfo("com.android.wifi", false /* isFactory */);
+ }
+
+ /**
+ * Simulates that the new APEX has been uninstalled (i.e., the module goes back to the factory
+ * version).
+ */
+ public void simulateApexUninstall() throws Exception {
+ updateApexInfo("com.android.wifi", true /* isFactory */);
+ }
+
+ private void updateApexInfo(String moduleName, boolean isFactory) throws Exception {
+ try (var xmlMutator = new XmlMutator(OdsignTestUtils.APEX_INFO_FILE)) {
NodeList list = xmlMutator.getDocument().getElementsByTagName("apex-info");
for (int i = 0; i < list.getLength(); i++) {
Element node = (Element) list.item(i);
- if (node.getAttribute("moduleName").equals("com.android.wifi")
+ if (node.getAttribute("moduleName").equals(moduleName)
&& node.getAttribute("isActive").equals("true")) {
- node.setAttribute("isFactory", "false");
- node.setAttribute("lastUpdateMillis", "1");
+ node.setAttribute("isFactory", String.valueOf(isFactory));
+ node.setAttribute(
+ "lastUpdateMillis", String.valueOf(System.currentTimeMillis()));
}
}
}
@@ -174,6 +193,14 @@
}
}
+ public void backupAndDeleteFile(String remotePath) throws Exception {
+ String tempFile = "/data/local/tmp/odsign_e2e_tests_" + UUID.randomUUID() + ".tmp";
+ // Backup the file before deleting it.
+ mTestUtils.assertCommandSucceeds(String.format("cp '%s' '%s'", remotePath, tempFile));
+ mTestUtils.assertCommandSucceeds(String.format("rm '%s'", remotePath));
+ mDeletedFiles.put(remotePath, tempFile);
+ }
+
public void backupArtifacts() throws Exception {
mTestInfo.getDevice().executeShellV2Command(
String.format("rm -rf '%s'", ART_APEX_DALVIK_CACHE_BACKUP_DIRNAME));
@@ -192,6 +219,12 @@
assertThat(mTestInfo.getDevice().pushFile(localFile, tempFile)).isTrue();
mTempFiles.add(tempFile);
+ // If the path has already been bind-mounted by this method before, unmount it first.
+ if (mMountPoints.contains(remotePath)) {
+ mTestUtils.assertCommandSucceeds(String.format("umount '%s'", remotePath));
+ mMountPoints.remove(remotePath);
+ }
+
mTestUtils.assertCommandSucceeds(
String.format("mount --bind '%s' '%s'", tempFile, remotePath));
mMountPoints.add(remotePath);
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryHostTestBase.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryHostTestBase.java
new file mode 100644
index 0000000..7c7d97b
--- /dev/null
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryHostTestBase.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2023 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.Assume.assumeTrue;
+
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * This class tests odrefresh for the cases where all the APEXes are initially factory-installed.
+ * Similar to OdrefreshHostTest, it does not involve odsign, fs-verity, and ART runtime.
+ *
+ * The tests are run by derived classes with different conditions: with and without the cache info.
+ */
+@Ignore("See derived classes")
+abstract public class OdrefreshFactoryHostTestBase extends BaseHostJUnit4Test {
+ protected OdsignTestUtils mTestUtils;
+ protected DeviceState mDeviceState;
+
+ @BeforeClassWithInfo
+ public static void beforeClassWithDeviceBase(TestInformation testInfo) throws Exception {
+ OdsignTestUtils testUtils = new OdsignTestUtils(testInfo);
+ assumeTrue(testUtils.areAllApexesFactoryInstalled());
+ testUtils.assertCommandSucceeds("disable-verity");
+ testUtils.removeCompilationLogToAvoidBackoff();
+ testUtils.reboot();
+ testUtils.assertCommandSucceeds("remount");
+ }
+
+ @AfterClassWithInfo
+ public static void afterClassWithDeviceBase(TestInformation testInfo) throws Exception {
+ OdsignTestUtils testUtils = new OdsignTestUtils(testInfo);
+ testUtils.assertCommandSucceeds("enable-verity");
+ testUtils.removeCompilationLogToAvoidBackoff();
+ testUtils.reboot();
+ }
+
+ @Before
+ public void setUpBase() throws Exception {
+ mTestUtils = new OdsignTestUtils(getTestInformation());
+ mDeviceState = new DeviceState(getTestInformation());
+ mDeviceState.backupArtifacts();
+ }
+
+ @After
+ public void tearDownBase() throws Exception {
+ mDeviceState.restore();
+ }
+
+ @Test
+ public void verifyArtSamegradeUpdateTriggersCompilation() throws Exception {
+ mDeviceState.simulateArtApexUpgrade();
+ long timeMs = mTestUtils.getCurrentTimeMs();
+ mTestUtils.runOdrefresh();
+
+ // It should recompile everything.
+ mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+ mTestUtils.assertModifiedAfter(mTestUtils.getZygotesExpectedArtifacts(), timeMs);
+ mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+ mDeviceState.simulateArtApexUninstall();
+ mTestUtils.runOdrefresh();
+
+ // It should delete all compilation artifacts and update the cache info.
+ // TODO(b/272245228): The cache info should be updated.
+ mTestUtils.assertFilesNotExist(mTestUtils.getZygotesExpectedArtifacts());
+ mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+ }
+
+ @Test
+ public void verifyOtherApexSamegradeUpdateTriggersCompilation() throws Exception {
+ mDeviceState.simulateApexUpgrade();
+ long timeMs = mTestUtils.getCurrentTimeMs();
+ mTestUtils.runOdrefresh();
+
+ // It should only recompile system server.
+ mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+ mTestUtils.assertFilesNotExist(mTestUtils.getZygotesExpectedArtifacts());
+ mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+ mDeviceState.simulateApexUninstall();
+ mTestUtils.runOdrefresh();
+
+ // It should delete all compilation artifacts and update the cache info.
+ // TODO(b/272245228): The cache info should be updated.
+ mTestUtils.assertFilesNotExist(mTestUtils.getZygotesExpectedArtifacts());
+ mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+ }
+
+ @Test
+ @Ignore("This test cannot pass. The fix for b/272245228 will also fix this.")
+ public void verifyMissingArtifactTriggersCompilation() throws Exception {
+ // Simulate that an artifact is missing from /system.
+ mDeviceState.backupAndDeleteFile("/system/framework/oat/x86_64/services.odex");
+
+ long timeMs = mTestUtils.getCurrentTimeMs();
+ mTestUtils.runOdrefresh();
+
+ Set<String> expectedArtifacts = OdsignTestUtils.getApexDataDalvikCacheFilenames(
+ "/system/framework/services.jar", mTestUtils.getSystemServerIsa());
+
+ Set<String> nonExpectedArtifacts = new HashSet<>();
+ nonExpectedArtifacts.addAll(mTestUtils.getZygotesExpectedArtifacts());
+ nonExpectedArtifacts.addAll(mTestUtils.getSystemServerExpectedArtifacts());
+ nonExpectedArtifacts.removeAll(expectedArtifacts);
+
+ // It should only generate artifacts that are missing from /system.
+ mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+ mTestUtils.assertFilesNotExist(nonExpectedArtifacts);
+ mTestUtils.assertModifiedAfter(expectedArtifacts, timeMs);
+
+ mDeviceState.simulateArtApexUpgrade();
+ timeMs = mTestUtils.getCurrentTimeMs();
+ mTestUtils.runOdrefresh();
+
+ // It should recompile everything.
+ mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+ mTestUtils.assertModifiedAfter(mTestUtils.getZygotesExpectedArtifacts(), timeMs);
+ mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+ mDeviceState.simulateArtApexUninstall();
+ timeMs = mTestUtils.getCurrentTimeMs();
+ mTestUtils.runOdrefresh();
+
+ // It should only re-generate artifacts that are missing from /system.
+ mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+ mTestUtils.assertFilesNotExist(nonExpectedArtifacts);
+ mTestUtils.assertModifiedAfter(expectedArtifacts, timeMs);
+ }
+
+ @Test
+ public void verifyEnableUffdGcChangeTriggersCompilation() throws Exception {
+ mDeviceState.setPhenotypeFlag("enable_uffd_gc", "true");
+
+ long timeMs = mTestUtils.getCurrentTimeMs();
+ mTestUtils.runOdrefresh();
+
+ // It should recompile everything.
+ mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+ mTestUtils.assertModifiedAfter(mTestUtils.getZygotesExpectedArtifacts(), timeMs);
+ mTestUtils.assertModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+ // Run odrefresh again with the flag unchanged.
+ timeMs = mTestUtils.getCurrentTimeMs();
+ mTestUtils.runOdrefresh();
+
+ // Nothing should change.
+ mTestUtils.assertNotModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+ mTestUtils.assertNotModifiedAfter(mTestUtils.getZygotesExpectedArtifacts(), timeMs);
+ mTestUtils.assertNotModifiedAfter(mTestUtils.getSystemServerExpectedArtifacts(), timeMs);
+
+ mDeviceState.setPhenotypeFlag("enable_uffd_gc", null);
+
+ mTestUtils.runOdrefresh();
+
+ // It should delete all compilation artifacts and update the cache info.
+ // TODO(b/272245228): The cache info should be updated.
+ mTestUtils.assertFilesNotExist(mTestUtils.getZygotesExpectedArtifacts());
+ mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+ }
+}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithCacheInfoHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithCacheInfoHostTest.java
new file mode 100644
index 0000000..d270fab
--- /dev/null
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithCacheInfoHostTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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 com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * This class tests odrefresh for the cases where all the APEXes are initially factory-installed
+ * and the cache info exists, which is the normal case.
+ *
+ * Both the tests in the base class and the tests in this class are run with the setup of this
+ * class.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OdrefreshFactoryWithCacheInfoHostTest extends OdrefreshFactoryHostTestBase {
+ @Test
+ public void verifyNoCompilationWhenSystemIsGood() throws Exception {
+ // Only the cache info should exist.
+ mTestUtils.assertFilesExist(Set.of(OdsignTestUtils.CACHE_INFO_FILE));
+ mTestUtils.assertFilesNotExist(mTestUtils.getZygotesExpectedArtifacts());
+ mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+
+ // Run again.
+ long timeMs = mTestUtils.getCurrentTimeMs();
+ mTestUtils.runOdrefresh();
+
+ // Nothing should change.
+ mTestUtils.assertNotModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+ mTestUtils.assertFilesNotExist(mTestUtils.getZygotesExpectedArtifacts());
+ mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+ }
+}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithoutCacheInfoHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithoutCacheInfoHostTest.java
new file mode 100644
index 0000000..bec0dd8
--- /dev/null
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshFactoryWithoutCacheInfoHostTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 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 com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Set;
+
+/**
+ * This class tests odrefresh for the cases where all the APEXes are initially factory-installed
+ * and the cache info does not exist.
+ *
+ * The cache info can be missing due to various reasons (corrupted files deleted by odsign, odsign
+ * failure, etc.), so this test makes sure that odrefresh doesn't rely on the cache info when
+ * checking artifacts on /system.
+ *
+ * Both the tests in the base class and the tests in this class are run with the setup of this
+ * class.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class OdrefreshFactoryWithoutCacheInfoHostTest extends OdrefreshFactoryHostTestBase {
+ @Before
+ public void setUp() throws Exception {
+ getDevice().deleteFile(OdsignTestUtils.CACHE_INFO_FILE);
+ }
+
+ @Test
+ public void verifyNoCompilationWhenSystemIsGood() throws Exception {
+ long timeMs = mTestUtils.getCurrentTimeMs();
+ mTestUtils.runOdrefresh();
+
+ // It should only generate the missing cache info.
+ mTestUtils.assertModifiedAfter(Set.of(OdsignTestUtils.CACHE_INFO_FILE), timeMs);
+ mTestUtils.assertFilesNotExist(mTestUtils.getZygotesExpectedArtifacts());
+ mTestUtils.assertFilesNotExist(mTestUtils.getSystemServerExpectedArtifacts());
+ }
+}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
index 83cd881..c8d2516 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdsignTestUtils.java
@@ -36,6 +36,11 @@
import com.google.common.io.ByteStreams;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
@@ -53,11 +58,14 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
public class OdsignTestUtils {
public static final String ART_APEX_DALVIK_CACHE_DIRNAME =
"/data/misc/apexdata/com.android.art/dalvik-cache";
public static final String CACHE_INFO_FILE = ART_APEX_DALVIK_CACHE_DIRNAME + "/cache-info.xml";
+ public static final String APEX_INFO_FILE = "/apex/apex-info-list.xml";
private static final String ODREFRESH_BIN = "odrefresh";
@@ -413,6 +421,24 @@
}
}
+ public void assertFilesExist(Set<String> files) throws Exception {
+ assertThat(getExistingFiles(files)).containsExactlyElementsIn(files);
+ }
+
+ public void assertFilesNotExist(Set<String> files) throws Exception {
+ assertThat(getExistingFiles(files)).isEmpty();
+ }
+
+ private Set<String> getExistingFiles(Set<String> files) throws Exception {
+ Set<String> existingFiles = new HashSet<>();
+ for (String file : files) {
+ if (mTestInfo.getDevice().doesFileExist(file)) {
+ existingFiles.add(file);
+ }
+ }
+ return existingFiles;
+ }
+
public static String replaceExtension(String filename, String extension) throws Exception {
int index = filename.lastIndexOf(".");
assertTrue("Extension not found in filename: " + filename, index != -1);
@@ -428,4 +454,24 @@
mTestInfo.getDevice().executeShellV2Command(
ODREFRESH_BIN + " --partial-compilation --no-refresh " + extraArgs + " --compile");
}
+
+ public boolean areAllApexesFactoryInstalled() throws Exception {
+ Document doc = loadXml(APEX_INFO_FILE);
+ NodeList list = doc.getElementsByTagName("apex-info");
+ for (int i = 0; i < list.getLength(); i++) {
+ Element node = (Element) list.item(i);
+ if (node.getAttribute("isActive").equals("true")
+ && node.getAttribute("isFactory").equals("false")) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private Document loadXml(String remoteXmlFile) throws Exception {
+ File localFile = mTestInfo.getDevice().pullFile(remoteXmlFile);
+ assertThat(localFile).isNotNull();
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ return builder.parse(localFile);
+ }
}