1 /* 2 * Copyright (C) 2021 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.compos.test; 18 19 import static com.android.microdroid.test.host.CommandResultSubject.assertThat; 20 import static com.android.microdroid.test.host.CommandResultSubject.command_results; 21 import static com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData; 22 23 import static com.google.common.truth.Truth.assertThat; 24 import static com.google.common.truth.Truth.assertWithMessage; 25 26 import static org.junit.Assume.assumeFalse; 27 import static org.junit.Assume.assumeTrue; 28 29 import android.platform.test.annotations.RootPermissionTest; 30 31 import com.android.microdroid.test.host.CommandRunner; 32 import com.android.microdroid.test.host.MicrodroidHostTestCaseBase; 33 import com.android.tradefed.device.TestDevice; 34 import com.android.tradefed.log.LogUtil.CLog; 35 import com.android.tradefed.result.FileInputStreamSource; 36 import com.android.tradefed.result.LogDataType; 37 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 38 import com.android.tradefed.util.CommandResult; 39 import com.android.tradefed.util.RunUtil; 40 41 import org.junit.After; 42 import org.junit.Before; 43 import org.junit.Rule; 44 import org.junit.Test; 45 import org.junit.rules.TestName; 46 import org.junit.runner.RunWith; 47 48 import java.io.File; 49 50 @RootPermissionTest 51 @RunWith(DeviceJUnit4ClassRunner.class) 52 public final class ComposTestCase extends MicrodroidHostTestCaseBase { 53 54 // Binaries used in test. (These paths are valid both in host and Microdroid.) 55 private static final String ODREFRESH_BIN = "/apex/com.android.art/bin/odrefresh"; 56 private static final String COMPOSD_CMD_BIN = "/apex/com.android.compos/bin/composd_cmd"; 57 private static final String COMPOS_VERIFY_BIN = 58 "/apex/com.android.compos/bin/compos_verify"; 59 60 private static final String COMPOS_APEXDATA_DIR = "/data/misc/apexdata/com.android.compos"; 61 62 /** Output directory of odrefresh */ 63 private static final String TEST_ARTIFACTS_DIR = "test-artifacts"; 64 65 private static final String ODREFRESH_OUTPUT_DIR = 66 "/data/misc/apexdata/com.android.art/" + TEST_ARTIFACTS_DIR; 67 68 /** Timeout of odrefresh to finish */ 69 private static final int ODREFRESH_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes 70 71 // ExitCode expanded from art/odrefresh/include/odrefresh/odrefresh.h. 72 private static final int OKAY = 0; 73 private static final int COMPILATION_SUCCESS = 80; 74 75 // Files that define the "test" instance of CompOS 76 private static final String COMPOS_TEST_ROOT = "/data/misc/apexdata/com.android.compos/test/"; 77 78 private static final String SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME = 79 "dalvik.vm.systemservercompilerfilter"; 80 private String mBackupSystemServerCompilerFilter; 81 82 @Rule public TestLogData mTestLogs = new TestLogData(); 83 @Rule public TestName mTestName = new TestName(); 84 85 @Before setUp()86 public void setUp() throws Exception { 87 assumeDeviceIsCapable(getDevice()); 88 // Test takes too long to run on Cuttlefish (b/292824951). 89 assumeFalse("Skipping test on Cuttlefish", isCuttlefish()); 90 // CompOS requires a protected VM 91 assumeTrue(((TestDevice) getDevice()).supportsMicrodroid(/*protectedVm*/ true)); 92 93 String value = getDevice().getProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME); 94 if (value == null) { 95 mBackupSystemServerCompilerFilter = ""; 96 } else { 97 mBackupSystemServerCompilerFilter = value; 98 } 99 } 100 101 @After tearDown()102 public void tearDown() throws Exception { 103 killVmAndReconnectAdb(); 104 105 CommandRunner android = new CommandRunner(getDevice()); 106 107 // Clear up any CompOS instance files we created 108 android.tryRun("rm", "-rf", COMPOS_TEST_ROOT); 109 110 // And any artifacts generated by odrefresh 111 android.tryRun("rm", "-rf", ODREFRESH_OUTPUT_DIR); 112 113 if (mBackupSystemServerCompilerFilter != null) { 114 CLog.d("Restore dalvik.vm.systemservercompilerfilter to " 115 + mBackupSystemServerCompilerFilter); 116 getDevice().setProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, 117 mBackupSystemServerCompilerFilter); 118 } 119 } 120 121 @Test testOdrefreshSpeed()122 public void testOdrefreshSpeed() throws Exception { 123 setPropertyOrThrow(getDevice(), SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, "speed"); 124 testOdrefresh(); 125 } 126 127 @Test testOdrefreshSpeedProfile()128 public void testOdrefreshSpeedProfile() throws Exception { 129 setPropertyOrThrow(getDevice(), SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, "speed-profile"); 130 testOdrefresh(); 131 } 132 testOdrefresh()133 private void testOdrefresh() throws Exception { 134 CommandRunner android = new CommandRunner(getDevice()); 135 136 // Prepare the groundtruth. The compilation on Android should finish successfully. 137 { 138 long start = System.currentTimeMillis(); 139 CommandResult result = runOdrefresh(android, "--force-compile"); 140 long elapsed = System.currentTimeMillis() - start; 141 assertThat(result).exitCode().isEqualTo(COMPILATION_SUCCESS); 142 CLog.i("Local compilation took " + elapsed + "ms"); 143 } 144 145 // Save the expected checksum for the output directory. 146 String expectedChecksumSnapshot = checksumDirectoryContentPartial(android, 147 ODREFRESH_OUTPUT_DIR); 148 149 // --check may delete the output. 150 CommandResult result = runOdrefresh(android, "--check"); 151 assertThat(result).exitCode().isEqualTo(OKAY); 152 153 // Expect the compilation in Compilation OS to finish successfully. 154 { 155 long start = System.currentTimeMillis(); 156 result = 157 android.runForResultWithTimeout( 158 ODREFRESH_TIMEOUT_MS, COMPOSD_CMD_BIN, "test-compile"); 159 long elapsed = System.currentTimeMillis() - start; 160 assertThat(result).exitCode().isEqualTo(0); 161 CLog.i("Comp OS compilation took " + elapsed + "ms"); 162 } 163 killVmAndReconnectAdb(); 164 165 // Expect the BCC extracted from the BCC to be well-formed. 166 assertVmBccIsValid(); 167 168 // Save the actual checksum for the output directory. 169 String actualChecksumSnapshot = checksumDirectoryContentPartial(android, 170 ODREFRESH_OUTPUT_DIR); 171 172 // Expect the output of Comp OS to be the same as compiled on Android. 173 assertThat(actualChecksumSnapshot).isEqualTo(expectedChecksumSnapshot); 174 175 // Expect extra files generated by CompOS exist. 176 android.run("test -f " + ODREFRESH_OUTPUT_DIR + "/compos.info"); 177 android.run("test -f " + ODREFRESH_OUTPUT_DIR + "/compos.info.signature"); 178 179 // Expect the CompOS signature to be valid 180 android.run(COMPOS_VERIFY_BIN + " --debug --instance test"); 181 } 182 assertVmBccIsValid()183 private void assertVmBccIsValid() throws Exception { 184 File bcc_file = getDevice().pullFile(COMPOS_APEXDATA_DIR + "/test/bcc"); 185 assertThat(bcc_file).isNotNull(); 186 187 // Add the BCC to test artifacts, in case it is ill-formed or otherwise interesting. 188 mTestLogs.addTestLog(bcc_file.getPath(), LogDataType.UNKNOWN, 189 new FileInputStreamSource(bcc_file)); 190 191 // Find the validator binary - note that it's specified as a dependency in our Android.bp. 192 File validator = getTestInformation().getDependencyFile("hwtrust", /*targetFirst=*/ false); 193 194 CommandResult result = 195 new RunUtil() 196 .runTimedCmd( 197 10000, 198 validator.getAbsolutePath(), 199 "dice-chain", 200 bcc_file.getAbsolutePath()); 201 assertWithMessage("hwtrust failed").about(command_results()).that(result).isSuccess(); 202 } 203 runOdrefresh(CommandRunner android, String command)204 private CommandResult runOdrefresh(CommandRunner android, String command) throws Exception { 205 return android.runForResultWithTimeout( 206 ODREFRESH_TIMEOUT_MS, 207 ODREFRESH_BIN, 208 "--dalvik-cache=" + TEST_ARTIFACTS_DIR, 209 command); 210 } 211 killVmAndReconnectAdb()212 private void killVmAndReconnectAdb() throws Exception { 213 CommandRunner android = new CommandRunner(getDevice()); 214 215 android.tryRun("killall", "crosvm"); 216 android.tryRun("stop", "virtualizationservice"); 217 218 // Delete stale data 219 android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*"); 220 } 221 checksumDirectoryContentPartial(CommandRunner runner, String path)222 private String checksumDirectoryContentPartial(CommandRunner runner, String path) 223 throws Exception { 224 // Sort by filename (second column) to make comparison easier. Filter out compos.info and 225 // compos.info.signature since it's only generated by CompOS. 226 // TODO(b/211458160): Remove cache-info.xml once we can plumb timestamp and isFactory of 227 // APEXes to the VM. 228 return runner.run( 229 "cd " 230 + path 231 + " && find -type f -exec sha256sum {} \\;" 232 + "| grep -v cache-info.xml | grep -v compos.info" 233 + "| sort -k2"); 234 } 235 } 236