diff options
5 files changed, 387 insertions, 16 deletions
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 8379cbadde..c6ca53c3e1 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 @@ 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 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 @@ public class DeviceState { 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 @@ public class DeviceState { "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 @@ public class DeviceState { /** 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 @@ public class DeviceState { * 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 class DeviceState { } } + 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 @@ public class DeviceState { 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 0000000000..7c7d97b8c1 --- /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 0000000000..d270fab82a --- /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 0000000000..bec0dd8476 --- /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 83cd881fbe..c8d2516ca5 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.android.tradefed.util.CommandResult; 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.Set; 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 class OdsignTestUtils { } } + 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 @@ public class OdsignTestUtils { 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); + } } |