1 /* 2 * Copyright (C) 2019 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 com.android.tests.tombstoneTransmit; 18 19 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner; 20 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test; 21 import com.android.tradefed.device.ITestDevice; 22 import com.android.tradefed.device.DeviceNotAvailableException; 23 import com.android.tradefed.device.TestDeviceOptions; 24 import com.android.tradefed.device.TestDeviceOptions.InstanceType; 25 import com.android.tradefed.util.CommandResult; 26 import com.android.tradefed.util.CommandStatus; 27 import com.android.tradefed.util.FileUtil; 28 import com.android.tradefed.util.StreamUtil; 29 import com.android.tradefed.log.LogUtil.CLog; 30 import org.junit.After; 31 import org.junit.Assert; 32 import org.junit.Before; 33 import org.junit.Test; 34 import org.junit.runner.RunWith; 35 import java.io.IOException; 36 import java.io.BufferedReader; 37 import java.io.File; 38 import java.io.FileInputStream; 39 import java.io.InputStreamReader; 40 import java.util.ArrayList; 41 import java.util.List; 42 43 /** 44 * Tests the tombstone transfer feature available on cuttlefish devices. This 45 * feature is used to transfer tombstones off the guest as they are created. 46 */ 47 @RunWith(DeviceJUnit4ClassRunner.class) 48 public class TombstoneTransmitTest extends BaseHostJUnit4Test { 49 /** Path on the device containing the tombstones */ 50 private static final String TOMBSTONE_PATH = "/data/tombstones/"; 51 private static final String TOMBSTONE_PRODUCER = "tombstone_producer"; 52 private static final int NUM_TOMBSTONES_IN_TEST = 1000; 53 54 /** 55 * Creates 15 tombstones on the virtual device of varying lenghts. 56 * Each tombstone is expected to be sync'd to the host and checked for integrity. 57 */ 58 @Test testTombstonesOfVaryingLengths()59 public void testTombstonesOfVaryingLengths() throws Exception { 60 InstanceType type = getDevice().getOptions().getInstanceType(); 61 // It can't be guaranteed that this test is run on a virtual device. 62 if(!InstanceType.CUTTLEFISH.equals(type) && !InstanceType.REMOTE_NESTED_AVD.equals(type)) { 63 CLog.i("This test must be run on a Cuttlefish device. Aborting."); 64 return; 65 } else { 66 CLog.i("This test IS being run on a Cuttlefish device."); 67 } 68 69 clearTombstonesFromCuttlefish(); 70 List<String> hostTombstoneListPreTest = convertFileListToStringList(getDevice().getTombstones()); 71 List<String> guestTombstoneListPreTest = convertFileListToStringList(getTombstonesViaAdb()); 72 73 // Generate tombstones in doubling sizes from 1k to 16M 74 for(int i = 0; i < 15; i++) { 75 generateTombstoneOfLengthInKb((int) Math.pow(2,i)); 76 } 77 78 List<String> hostTombstoneListPostTest = 79 convertFileListToStringList(getDevice().getTombstones()); 80 List<String> guestTombstoneListPostTest = 81 convertFileListToStringList(getTombstonesViaAdb()); 82 83 // Clear out all tombstones pretest. 84 hostTombstoneListPostTest.removeAll(hostTombstoneListPreTest); 85 guestTombstoneListPostTest.removeAll(guestTombstoneListPreTest); 86 87 CLog.i("===========Host Tombstone Statistics==========="); 88 printTombstoneListStats(hostTombstoneListPostTest); 89 CLog.i("===========Guest Tombstone Statistics==========="); 90 printTombstoneListStats(guestTombstoneListPostTest); 91 92 Assert.assertTrue("Tombstones on guest and host do not match", 93 hostTombstoneListPostTest.containsAll(guestTombstoneListPostTest)); 94 Assert.assertEquals("Host does not have expected tombstone count in this iteration", 95 hostTombstoneListPostTest.size(), 15); 96 Assert.assertEquals("Guest does not have expected tombstone count in this iteration", 97 guestTombstoneListPostTest.size(), 15); 98 } 99 100 /** 101 * Triggers 1000 tombstones on the virtual device and verifies the integrity of each one. 102 * Note that the tombstone generation is chunk'd since the virtual device overwrites the oldest 103 * tombstone once the 500th is created (or 50th in the case of most physical devices). 104 */ 105 private static final int NUM_TOMBSTONES_PER_LOOP = 500; 106 @Test testTombstoneTransmitIntegrity()107 public void testTombstoneTransmitIntegrity() throws Exception { 108 InstanceType type = getDevice().getOptions().getInstanceType(); 109 // It can't be guaranteed that this test is run on a virtual device. 110 if(!InstanceType.CUTTLEFISH.equals(type) && !InstanceType.REMOTE_NESTED_AVD.equals(type)) { 111 CLog.i("This test must be run on a Cuttlefish device. Aborting."); 112 return; 113 } else { 114 CLog.i("This test IS being run on a Cuttlefish device."); 115 } 116 117 for(int i = 0; i < 2; i++) { 118 clearTombstonesFromCuttlefish(); 119 List<String> hostTombstoneListPreCrash = convertFileListToStringList( 120 getDevice().getTombstones()); 121 List<String> guestTombstoneListPreCrash = convertFileListToStringList( 122 getTombstonesViaAdb()); 123 124 for(int j = 0; j < NUM_TOMBSTONES_PER_LOOP; j++) { 125 CommandResult commandResult = getDevice().executeShellV2Command(TOMBSTONE_PRODUCER); 126 Assert.assertEquals(CommandStatus.FAILED, commandResult.getStatus()); 127 } 128 129 List<String> hostTombstoneListPostCrash = 130 convertFileListToStringList(getDevice().getTombstones()); 131 List<String> guestTombstoneListPostCrash = 132 convertFileListToStringList(getTombstonesViaAdb()); 133 134 // Clear out all tombstones pretest. 135 hostTombstoneListPostCrash.removeAll(hostTombstoneListPreCrash); 136 guestTombstoneListPostCrash.removeAll(guestTombstoneListPreCrash); 137 138 CLog.i("===========Host Tombstone Statistics==========="); 139 printTombstoneListStats(hostTombstoneListPostCrash); 140 CLog.i("===========Guest Tombstone Statistics==========="); 141 printTombstoneListStats(guestTombstoneListPostCrash); 142 143 Assert.assertTrue("Tombstones on guest and host do not match", 144 hostTombstoneListPostCrash.containsAll(guestTombstoneListPostCrash)); 145 Assert.assertEquals("Host does not have expected tombstone count in this iteration", 146 hostTombstoneListPostCrash.size(), NUM_TOMBSTONES_PER_LOOP); 147 Assert.assertEquals("Guest does not have expected tombstone count in this iteration", 148 guestTombstoneListPostCrash.size(), NUM_TOMBSTONES_PER_LOOP); 149 } 150 } 151 printTombstoneListStats(List<String> tList)152 public static void printTombstoneListStats(List<String> tList) { 153 CLog.i("List contains %d tombstones.", tList.size()); 154 155 int averageTombstoneLength = 0; 156 for(String tombstone: tList) { 157 averageTombstoneLength += tombstone.length(); 158 } 159 160 if(tList.size() != 0) { 161 CLog.i("Average tombstone size is %d.", averageTombstoneLength / tList.size()); 162 } 163 } 164 clearTombstonesFromCuttlefish()165 public void clearTombstonesFromCuttlefish() throws DeviceNotAvailableException { 166 if (!getDevice().isAdbRoot()) { 167 throw new DeviceNotAvailableException("Device was not root, cannot collect tombstones." 168 , getDevice().getSerialNumber()); 169 } 170 171 // Clear all tombstones on AVD 172 CommandResult commandResult = getDevice(). 173 executeShellV2Command("rm -rf " + TOMBSTONE_PATH + "*"); 174 Assert.assertEquals(CommandStatus.SUCCESS, commandResult.getStatus()); 175 } 176 177 // This is blatantly copied from tradefed class NativeDevice's version of getTombstones getTombstonesViaAdb()178 private List<File> getTombstonesViaAdb() throws DeviceNotAvailableException { 179 List<File> tombstones = new ArrayList<>(); 180 if (!getDevice().isAdbRoot()) { 181 throw new DeviceNotAvailableException("Device was not root, cannot collect tombstones." 182 , getDevice().getSerialNumber()); 183 } 184 185 for (String tombName : getDevice().getChildren(TOMBSTONE_PATH)) { 186 File tombFile = getDevice().pullFile(TOMBSTONE_PATH + tombName); 187 if (tombFile != null) { 188 tombstones.add(tombFile); 189 } 190 } 191 return tombstones; 192 } 193 convertFileListToStringList(List<File> inputList)194 private List<String> convertFileListToStringList(List<File> inputList) throws IOException { 195 List<String> output = new ArrayList<String>(); 196 for(File f: inputList) { 197 output.add(convertFileContentsToString(f)); 198 } 199 200 return output; 201 } 202 convertFileContentsToString(File f)203 private String convertFileContentsToString(File f) throws IOException { 204 StringBuilder stringBuilder = new StringBuilder(); 205 BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(f))); 206 String line; 207 while ((line = br.readLine()) != null) { 208 stringBuilder.append(line).append('\n'); 209 } 210 211 return stringBuilder.toString(); 212 } 213 generateTombstoneOfLengthInKb(int requestedLengthInKb)214 private void generateTombstoneOfLengthInKb(int requestedLengthInKb) throws DeviceNotAvailableException { 215 if (!getDevice().isAdbRoot()) { 216 throw new DeviceNotAvailableException("Device was not root, cannot generate tombstone." 217 , getDevice().getSerialNumber()); 218 } 219 220 // Generate file in directory not monitored by tombstone daemon and then link it into the 221 // tombstone dir. 222 // Context - tombstones are created in a tmp dir and then linked into the tombstones 223 // dir. The tombstone daemon waits for the link inotify event and then copies 224 // the full contents of the linked file to the host. 225 // If the file is instead being written into the tombstones dir on the guest, the integrity 226 // of the file written out on the host side cannot be guaranteed. 227 CommandResult commandResult = getDevice(). 228 executeShellV2Command("dd if=/dev/urandom of=/data/tmp-file bs=1K count=" + 229 requestedLengthInKb); 230 Assert.assertEquals(CommandStatus.SUCCESS, commandResult.getStatus()); 231 232 commandResult = getDevice(). 233 executeShellV2Command("mv /data/tmp-file /data/tombstones/" + 234 System.currentTimeMillis()); 235 Assert.assertEquals(CommandStatus.SUCCESS, commandResult.getStatus()); 236 } 237 } 238