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