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 = "/apex/com.android.compos/bin/compos_verify"; 58 59 private static final String COMPOS_APEXDATA_DIR = "/data/misc/apexdata/com.android.compos"; 60 61 /** Output directory of odrefresh */ 62 private static final String TEST_ARTIFACTS_DIR = "test-artifacts"; 63 64 private static final String ODREFRESH_OUTPUT_DIR = 65 "/data/misc/apexdata/com.android.art/" + TEST_ARTIFACTS_DIR; 66 67 /** Timeout of odrefresh to finish */ 68 private static final int ODREFRESH_TIMEOUT_MS = 10 * 60 * 1000; // 10 minutes 69 70 // ExitCode expanded from art/odrefresh/include/odrefresh/odrefresh.h. 71 private static final int OKAY = 0; 72 private static final int COMPILATION_SUCCESS = 80; 73 74 // Files that define the "test" instance of CompOS 75 private static final String COMPOS_TEST_ROOT = "/data/misc/apexdata/com.android.compos/test/"; 76 77 private static final String SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME = 78 "dalvik.vm.systemservercompilerfilter"; 79 private String mBackupSystemServerCompilerFilter; 80 81 @Rule public TestLogData mTestLogs = new TestLogData(); 82 @Rule public TestName mTestName = new TestName(); 83 84 @Before setUp()85 public void setUp() throws Exception { 86 assumeDeviceIsCapable(getDevice()); 87 // Test takes too long to run on Cuttlefish (b/292824951). 88 assumeFalse("Skipping test on Cuttlefish", isCuttlefish()); 89 // CompOS requires a protected VM 90 assumeTrue(((TestDevice) getDevice()).supportsMicrodroid(/*protectedVm*/ true)); 91 92 String value = getDevice().getProperty(SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME); 93 if (value == null) { 94 mBackupSystemServerCompilerFilter = ""; 95 } else { 96 mBackupSystemServerCompilerFilter = value; 97 } 98 } 99 100 @After tearDown()101 public void tearDown() throws Exception { 102 killVmAndReconnectAdb(); 103 104 CommandRunner android = new CommandRunner(getDevice()); 105 106 // Clear up any CompOS instance files we created 107 android.tryRun("rm", "-rf", COMPOS_TEST_ROOT); 108 109 // And any artifacts generated by odrefresh 110 android.tryRun("rm", "-rf", ODREFRESH_OUTPUT_DIR); 111 112 if (mBackupSystemServerCompilerFilter != null) { 113 CLog.d( 114 "Restore dalvik.vm.systemservercompilerfilter to " 115 + mBackupSystemServerCompilerFilter); 116 getDevice() 117 .setProperty( 118 SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, 119 mBackupSystemServerCompilerFilter); 120 } 121 } 122 123 @Test testOdrefreshSpeed()124 public void testOdrefreshSpeed() throws Exception { 125 setPropertyOrThrow(getDevice(), SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, "speed"); 126 testOdrefresh(); 127 } 128 129 @Test testOdrefreshSpeedProfile()130 public void testOdrefreshSpeedProfile() throws Exception { 131 setPropertyOrThrow(getDevice(), SYSTEM_SERVER_COMPILER_FILTER_PROP_NAME, "speed-profile"); 132 testOdrefresh(); 133 } 134 testOdrefresh()135 private void testOdrefresh() throws Exception { 136 CommandRunner android = new CommandRunner(getDevice()); 137 138 // Prepare the groundtruth. The compilation on Android should finish successfully. 139 { 140 long start = System.currentTimeMillis(); 141 CommandResult result = runOdrefresh(android, "--force-compile"); 142 long elapsed = System.currentTimeMillis() - start; 143 assertThat(result).exitCode().isEqualTo(COMPILATION_SUCCESS); 144 CLog.i("Local compilation took " + elapsed + "ms"); 145 } 146 147 // Save the expected checksum for the output directory. 148 String expectedChecksumSnapshot = 149 checksumDirectoryContentPartial(android, ODREFRESH_OUTPUT_DIR); 150 151 // --check may delete the output. 152 CommandResult result = runOdrefresh(android, "--check"); 153 assertThat(result).exitCode().isEqualTo(OKAY); 154 155 // Expect the compilation in Compilation OS to finish successfully. 156 { 157 long start = System.currentTimeMillis(); 158 result = 159 android.runForResultWithTimeout( 160 ODREFRESH_TIMEOUT_MS, COMPOSD_CMD_BIN, "test-compile"); 161 long elapsed = System.currentTimeMillis() - start; 162 assertThat(result).exitCode().isEqualTo(0); 163 CLog.i("Comp OS compilation took " + elapsed + "ms"); 164 } 165 killVmAndReconnectAdb(); 166 167 // Expect the BCC extracted from the BCC to be well-formed. 168 assertVmBccIsValid(); 169 170 // Save the actual checksum for the output directory. 171 String actualChecksumSnapshot = 172 checksumDirectoryContentPartial(android, ODREFRESH_OUTPUT_DIR); 173 174 // Expect the output of Comp OS to be the same as compiled on Android. 175 assertThat(actualChecksumSnapshot).isEqualTo(expectedChecksumSnapshot); 176 177 // Expect extra files generated by CompOS exist. 178 android.run("test -f " + ODREFRESH_OUTPUT_DIR + "/compos.info"); 179 android.run("test -f " + ODREFRESH_OUTPUT_DIR + "/compos.info.signature"); 180 181 // Expect the CompOS signature to be valid 182 android.run(COMPOS_VERIFY_BIN + " --debug --instance test"); 183 } 184 assertVmBccIsValid()185 private void assertVmBccIsValid() throws Exception { 186 File bcc_file = getDevice().pullFile(COMPOS_APEXDATA_DIR + "/test/bcc"); 187 assertThat(bcc_file).isNotNull(); 188 189 // Add the BCC to test artifacts, in case it is ill-formed or otherwise interesting. 190 mTestLogs.addTestLog( 191 bcc_file.getPath(), LogDataType.UNKNOWN, new FileInputStreamSource(bcc_file)); 192 193 // Find the validator binary - note that it's specified as a dependency in our Android.bp. 194 File validator = 195 getTestInformation().getDependencyFile("hwtrust", /* targetFirst= */ false); 196 197 CommandResult result = 198 new RunUtil() 199 .runTimedCmd( 200 10000, 201 validator.getAbsolutePath(), 202 "dice-chain", 203 "--allow-any-mode", 204 bcc_file.getAbsolutePath()); 205 assertWithMessage("hwtrust failed").about(command_results()).that(result).isSuccess(); 206 } 207 runOdrefresh(CommandRunner android, String command)208 private CommandResult runOdrefresh(CommandRunner android, String command) throws Exception { 209 return android.runForResultWithTimeout( 210 ODREFRESH_TIMEOUT_MS, 211 ODREFRESH_BIN, 212 "--dalvik-cache=" + TEST_ARTIFACTS_DIR, 213 command); 214 } 215 killVmAndReconnectAdb()216 private void killVmAndReconnectAdb() throws Exception { 217 CommandRunner android = new CommandRunner(getDevice()); 218 219 android.tryRun("killall", "crosvm"); 220 android.tryRun("stop", "virtualizationservice"); 221 222 // Delete stale data 223 android.tryRun("rm", "-rf", "/data/misc/virtualizationservice/*"); 224 } 225 checksumDirectoryContentPartial(CommandRunner runner, String path)226 private String checksumDirectoryContentPartial(CommandRunner runner, String path) 227 throws Exception { 228 // Sort by filename (second column) to make comparison easier. Filter out compos.info and 229 // compos.info.signature since it's only generated by CompOS. 230 // TODO(b/211458160): Remove cache-info.xml once we can plumb timestamp and isFactory of 231 // APEXes to the VM. 232 return runner.run( 233 "cd " 234 + path 235 + " && find -type f -exec sha256sum {} \\;" 236 + "| grep -v cache-info.xml | grep -v compos.info" 237 + "| sort -k2"); 238 } 239 } 240