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.virt.test; 18 19 import static org.hamcrest.CoreMatchers.is; 20 import static org.hamcrest.CoreMatchers.not; 21 import static org.junit.Assert.assertThat; 22 23 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 24 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 25 import com.android.tradefed.util.CommandResult; 26 import com.android.tradefed.util.FileUtil; 27 import com.android.tradefed.util.RunUtil; 28 29 import org.junit.After; 30 import org.junit.Before; 31 import org.junit.Test; 32 import org.junit.runner.RunWith; 33 34 import java.io.File; 35 import java.io.FileWriter; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.List; 39 import java.util.concurrent.ExecutorService; 40 import java.util.concurrent.Executors; 41 42 @RunWith(DeviceJUnit4ClassRunner.class) 43 public class MicrodroidTestCase extends BaseHostJUnit4Test { 44 private static final String TEST_ROOT = "/data/local/tmp/virt/"; 45 private static final String VIRT_APEX = "/apex/com.android.virt/"; 46 private static final int TEST_VM_CID = 5; 47 private static final int TEST_VM_ADB_PORT = 8000; 48 private static final String MICRODROID_SERIAL = "localhost:" + TEST_VM_ADB_PORT; 49 private static final long MICRODROID_BOOT_TIMEOUT_MILLIS = 15000; 50 executeCommand(String cmd)51 private String executeCommand(String cmd) { 52 final long defaultCommandTimeoutMillis = 1000; // 1 sec 53 return executeCommand(defaultCommandTimeoutMillis, cmd); 54 } 55 executeCommand(long timeout, String cmd)56 private String executeCommand(long timeout, String cmd) { 57 CommandResult result = RunUtil.getDefault().runTimedCmd(timeout, cmd.split(" ")); 58 return result.getStdout().trim(); // remove the trailing whitespace including newline 59 } 60 61 @Test testMicrodroidBoots()62 public void testMicrodroidBoots() throws Exception { 63 // Prepare input files 64 String prepareImagesCmd = 65 String.format( 66 "mkdir -p %s; cd %s; " 67 + "cp %setc/microdroid_bootloader bootloader && " 68 + "cp %setc/fs/*.img . && " 69 + "cp %setc/uboot_env.img . && " 70 + "dd if=/dev/zero of=misc.img bs=4k count=256 && " 71 + "dd if=/dev/zero of=userdata.img bs=1 count=0 seek=4G && " 72 + "mkfs.ext4 userdata.img", 73 TEST_ROOT, TEST_ROOT, VIRT_APEX, VIRT_APEX, VIRT_APEX); 74 getDevice().executeShellCommand(prepareImagesCmd); 75 76 // Create os_composite.img, env_composite.img, userdata.img, and payload.img 77 String makeOsCompositeCmd = 78 String.format( 79 "cd %s; %sbin/mk_cdisk %setc/microdroid_cdisk.json os_composite.img", 80 TEST_ROOT, VIRT_APEX, VIRT_APEX); 81 getDevice().executeShellCommand(makeOsCompositeCmd); 82 String makeEnvCompositeCmd = 83 String.format( 84 "cd %s; %sbin/mk_cdisk %setc/microdroid_cdisk_env.json env_composite.img", 85 TEST_ROOT, VIRT_APEX, VIRT_APEX); 86 getDevice().executeShellCommand(makeEnvCompositeCmd); 87 String makeDataCompositeCmd = 88 String.format( 89 "cd %s; %sbin/mk_cdisk %setc/microdroid_cdisk_userdata.json" 90 + " userdata_composite.img", 91 TEST_ROOT, VIRT_APEX, VIRT_APEX); 92 getDevice().executeShellCommand(makeDataCompositeCmd); 93 String makeDataCompositeQcow2Cmd = 94 String.format( 95 "cd %s; %sbin/crosvm create_qcow2 --backing_file=userdata_composite.img" 96 + " userdata_composite.qcow2", 97 TEST_ROOT, VIRT_APEX); 98 getDevice().executeShellCommand(makeDataCompositeQcow2Cmd); 99 String makePayloadCompositeCmd = 100 String.format( 101 "cd %s; %sbin/mk_payload %setc/microdroid_payload.json payload.img", 102 TEST_ROOT, VIRT_APEX, VIRT_APEX); 103 getDevice().executeShellCommand(makePayloadCompositeCmd); 104 105 // Make sure that the composite images are created 106 final List<String> compositeImages = 107 new ArrayList<>( 108 Arrays.asList( 109 TEST_ROOT + "/os_composite.img", 110 TEST_ROOT + "/env_composite.img", 111 TEST_ROOT + "/userdata_composite.qcow2", 112 TEST_ROOT + "/payload.img")); 113 CommandResult result = 114 getDevice().executeShellV2Command("du -b " + String.join(" ", compositeImages)); 115 assertThat(result.getExitCode(), is(0)); 116 assertThat(result.getStdout(), is(not(""))); 117 118 // Start microdroid using crosvm 119 ExecutorService executor = Executors.newFixedThreadPool(1); 120 String runMicrodroidCmd = 121 String.format( 122 "cd %s; %sbin/crosvm run --cid=%d --disable-sandbox --bios=bootloader" 123 + " --serial=type=syslog --disk=os_composite.img" 124 + " --disk=env_composite.img --disk=payload.img" 125 + " --rwdisk=userdata_composite.qcow2", 126 TEST_ROOT, VIRT_APEX, TEST_VM_CID); 127 executor.execute( 128 () -> { 129 try { 130 getDevice().executeShellV2Command(runMicrodroidCmd); 131 } catch (Exception e) { 132 throw new RuntimeException(e); 133 } 134 }); 135 // .. and wait for microdroid to boot 136 // TODO(jiyong): don't wait too long. We can wait less by monitoring log from microdroid 137 Thread.sleep(MICRODROID_BOOT_TIMEOUT_MILLIS); 138 139 // Connect to microdroid and read a system property from there 140 executeCommand( 141 "adb -s " 142 + getDevice().getSerialNumber() 143 + " forward tcp:" 144 + TEST_VM_ADB_PORT 145 + " vsock:" 146 + TEST_VM_CID 147 + ":5555"); 148 executeCommand("adb connect " + MICRODROID_SERIAL); 149 String prop = executeCommand("adb -s " + MICRODROID_SERIAL + " shell getprop ro.hardware"); 150 assertThat(prop, is("microdroid")); 151 152 // Test writing to /data partition 153 File tmpFile = FileUtil.createTempFile("test", ".txt"); 154 tmpFile.deleteOnExit(); 155 FileWriter writer = new FileWriter(tmpFile); 156 writer.write("MicrodroidTest"); 157 writer.close(); 158 159 executeCommand( 160 "adb -s " 161 + MICRODROID_SERIAL 162 + " push " 163 + tmpFile.getPath() 164 + " /data/local/tmp/test.txt"); 165 String catResult = 166 executeCommand( 167 "adb -s " + MICRODROID_SERIAL + " shell cat /data/local/tmp/test.txt"); 168 assertThat(catResult, is("MicrodroidTest")); 169 170 // Shutdown microdroid 171 executeCommand("adb -s localhost:" + TEST_VM_ADB_PORT + " shell reboot"); 172 } 173 174 @Before setUp()175 public void setUp() throws Exception { 176 // delete the test root 177 getDevice().executeShellCommand("rm -rf " + TEST_ROOT); 178 179 // disconnect from microdroid 180 executeCommand("adb disconnect " + MICRODROID_SERIAL); 181 } 182 183 @After shutdown()184 public void shutdown() throws Exception { 185 // disconnect from microdroid 186 executeCommand("adb disconnect " + MICRODROID_SERIAL); 187 188 // kill stale crosvm processes 189 getDevice().executeShellV2Command("killall crosvm"); 190 } 191 } 192