Use a better approach to simulate APEX updates.
Before this change, the tests mutates cache-info.xml to simulate APEX
updates. After this change, the tests bind-mounts a fake
apex-info-list.xml to simulate APEX updates.
This change allows us to add tests later to test the cases where
cache-info.xml doesn't exist. It also allows us to eventually remove
APEX info from cache-info.xml when we use OatFileAssistant in odrefresh.
Bug: 272245228
Test: atest odsign_e2e_tests_full:OdrefreshHostTest
Change-Id: I1f19aa4d50825c4052062732ca9b17bd82ab3ec9
diff --git a/test/odsign/test-src/com/android/tests/odsign/DeviceState.java b/test/odsign/test-src/com/android/tests/odsign/DeviceState.java
new file mode 100644
index 0000000..fb21656
--- /dev/null
+++ b/test/odsign/test-src/com/android/tests/odsign/DeviceState.java
@@ -0,0 +1,141 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.invoker.TestInformation;
+
+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.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+/** 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 final TestInformation mTestInfo;
+ private final OdsignTestUtils mTestUtils;
+
+ private Set<String> mTempFiles = new HashSet<>();
+ private Set<String> mMountPoints = new HashSet<>();
+
+ public DeviceState(TestInformation testInfo) throws Exception {
+ mTestInfo = testInfo;
+ mTestUtils = new OdsignTestUtils(testInfo);
+ }
+
+ /** Restores the device state. */
+ public void restore() throws Exception {
+ for (String mountPoint : mMountPoints) {
+ mTestInfo.getDevice().executeShellV2Command(String.format("umount '%s'", mountPoint));
+ }
+
+ for (String tempFile : mTempFiles) {
+ mTestInfo.getDevice().deleteFile(tempFile);
+ }
+ }
+
+ /** 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");
+ }
+ }
+ }
+ }
+
+ /**
+ * 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.
+ */
+ public void simulateApexUpgrade() 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.wifi")
+ && node.getAttribute("isActive").equals("true")) {
+ node.setAttribute("isFactory", "false");
+ node.setAttribute("lastUpdateMillis", "1");
+ }
+ }
+ }
+ }
+
+ /**
+ * Pushes the file to a temporary location and bind-mount it at the given path. This is useful
+ * when the path is readonly.
+ */
+ private void pushAndBindMount(File localFile, String remotePath) throws Exception {
+ String tempFile = "/data/local/tmp/odsign_e2e_tests_" + UUID.randomUUID() + ".tmp";
+ assertThat(mTestInfo.getDevice().pushFile(localFile, tempFile)).isTrue();
+ mTempFiles.add(tempFile);
+
+ mTestUtils.assertCommandSucceeds(
+ String.format("mount --bind '%s' '%s'", tempFile, remotePath));
+ mMountPoints.add(remotePath);
+ mTestUtils.assertCommandSucceeds(String.format("restorecon '%s'", remotePath));
+ }
+
+ /** A helper class for mutating an XML file. */
+ private class XmlMutator implements AutoCloseable {
+ private final Document mDocument;
+ private final String mRemoteXmlFile;
+ private final File mLocalFile;
+
+ public XmlMutator(String remoteXmlFile) throws Exception {
+ // Load the XML file.
+ mRemoteXmlFile = remoteXmlFile;
+ mLocalFile = mTestInfo.getDevice().pullFile(remoteXmlFile);
+ assertThat(mLocalFile).isNotNull();
+ DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
+ mDocument = builder.parse(mLocalFile);
+ }
+
+ @Override
+ public void close() throws Exception {
+ // Save the XML file.
+ Transformer transformer = TransformerFactory.newInstance().newTransformer();
+ transformer.transform(new DOMSource(mDocument), new StreamResult(mLocalFile));
+ pushAndBindMount(mLocalFile, mRemoteXmlFile);
+ }
+
+ /** Returns a mutable XML document. */
+ public Document getDocument() {
+ return mDocument;
+ }
+ }
+}
diff --git a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
index 60709f1..cf2365e 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OdrefreshHostTest.java
@@ -27,6 +27,7 @@
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.Test;
import org.junit.runner.RunWith;
@@ -54,6 +55,7 @@
private static final String SYSTEM_SERVER_ARTIFACTS_KEY = TAG + ":SYSTEM_SERVER_ARTIFACTS";
private OdsignTestUtils mTestUtils;
+ private DeviceState mDeviceState;
@BeforeClassWithInfo
public static void beforeClassWithDevice(TestInformation testInfo) throws Exception {
@@ -92,6 +94,7 @@
@Before
public void setUp() throws Exception {
mTestUtils = new OdsignTestUtils(getTestInformation());
+ mDeviceState = new DeviceState(getTestInformation());
// Restore the artifacts to ensure a clean initial state.
getDevice().executeShellV2Command(
@@ -101,9 +104,14 @@
OdsignTestUtils.ART_APEX_DALVIK_CACHE_DIRNAME));
}
+ @After
+ public void tearDown() throws Exception {
+ mDeviceState.restore();
+ }
+
@Test
public void verifyArtSamegradeUpdateTriggersCompilation() throws Exception {
- simulateArtApexUpgrade();
+ mDeviceState.simulateArtApexUpgrade();
long timeMs = mTestUtils.getCurrentTimeMs();
runOdrefresh();
@@ -113,7 +121,7 @@
@Test
public void verifyOtherApexSamegradeUpdateTriggersCompilation() throws Exception {
- simulateApexUpgrade();
+ mDeviceState.simulateApexUpgrade();
long timeMs = mTestUtils.getCurrentTimeMs();
runOdrefresh();
@@ -370,7 +378,7 @@
@Test
public void verifyCompilationOsMode() throws Exception {
mTestUtils.removeCompilationLogToAvoidBackoff();
- simulateApexUpgrade();
+ mDeviceState.simulateApexUpgrade();
long timeMs = mTestUtils.getCurrentTimeMs();
runOdrefresh("--compilation-os-mode");
@@ -472,33 +480,6 @@
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 Set<String> simulateMissingArtifacts() throws Exception {
Set<String> missingArtifacts = new HashSet<>();
String sample = getSystemServerArtifacts().iterator().next();