1 /* 2 * Copyright (C) 2016 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.framework.tests; 18 19 import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner; 20 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; 21 import com.android.tradefed.build.IBuildInfo; 22 import com.android.tradefed.config.Option; 23 import com.android.tradefed.device.DeviceNotAvailableException; 24 import com.android.tradefed.device.ITestDevice; 25 import com.android.tradefed.log.LogUtil.CLog; 26 import com.android.tradefed.result.FileInputStreamSource; 27 import com.android.tradefed.result.ITestInvocationListener; 28 import com.android.tradefed.result.LogDataType; 29 import com.android.tradefed.testtype.IBuildReceiver; 30 import com.android.tradefed.testtype.IDeviceTest; 31 import com.android.tradefed.testtype.IRemoteTest; 32 import com.android.tradefed.util.CommandResult; 33 import com.android.tradefed.util.CommandStatus; 34 import com.android.tradefed.util.FileUtil; 35 import com.android.tradefed.util.RunUtil; 36 37 import java.io.File; 38 import java.io.IOException; 39 import java.util.ArrayList; 40 import java.util.List; 41 42 /** 43 * Runs a series of automated use cases and collects loaded class information in order to generate 44 * a list of preloaded classes based on the input thresholds. 45 */ 46 public class PreloadedClassesTest implements IRemoteTest, IDeviceTest, IBuildReceiver { 47 private static final String JUNIT_RUNNER = "android.support.test.runner.AndroidJUnitRunner"; 48 // Preload tool commands 49 private static final String TOOL_CMD = "java -cp %s com.android.preload.Main --seq %s %s"; 50 private static final String SCAN_ALL_CMD = "scan-all"; 51 private static final String COMPUTE_CMD = "comp %d %s"; 52 private static final String EXPORT_CMD = "export %s"; 53 private static final String IMPORT_CMD = "import %s"; 54 // Large, common timeouts 55 private static final long SCAN_TIMEOUT_MS = 5 * 60 * 1000; 56 private static final long COMPUTE_TIMEOUT_MS = 60 * 1000; 57 58 @Option(name = "package", 59 description = "Instrumentation package for use case automation.", 60 mandatory = true) 61 private String mPackage = null; 62 63 @Option(name = "test-case", 64 description = "List of use cases to exercise from the package.", 65 mandatory = true) 66 private List<String> mTestCases = new ArrayList<>(); 67 68 @Option(name = "preload-tool", description = "Overridden location of the preload JAR file.") 69 private String mPreloadToolJarPath = null; 70 71 @Option(name = "threshold", 72 description = "List of thresholds for computing preloaded classes.", 73 mandatory = true) 74 private List<String> mThresholds = new ArrayList<>(); 75 76 @Option(name = "quit-on-error", 77 description = "Quits if errors are encountered anywhere in the process.", 78 mandatory = false) 79 private boolean mQuitOnError = false; 80 81 private ITestDevice mDevice; 82 private IBuildInfo mBuildInfo; 83 private List<File> mExportFiles = new ArrayList<>(); 84 85 /** 86 * {@inheritDoc} 87 */ 88 @Override 89 public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { 90 // Download preload tool, if not supplied 91 if (mPreloadToolJarPath == null) { 92 File preload = mBuildInfo.getFile("preload2.jar"); 93 if (preload != null && preload.exists()) { 94 mPreloadToolJarPath = preload.getAbsolutePath(); 95 } else { 96 CLog.e("Unable to find the preload tool."); 97 } 98 } else { 99 CLog.v("Using alternative preload tool path, %s", mPreloadToolJarPath); 100 } 101 102 IRemoteAndroidTestRunner runner = 103 new RemoteAndroidTestRunner(mPackage, JUNIT_RUNNER, getDevice().getIDevice()); 104 105 for (String testCaseIdentifier : mTestCases) { 106 // Run an individual use case 107 runner.addInstrumentationArg("class", testCaseIdentifier); 108 getDevice().runInstrumentationTests(runner, listener); 109 // Scan loaded classes and export 110 File outfile = scanAndExportClasses(); 111 if (outfile != null) { 112 mExportFiles.add(outfile); 113 } else { 114 String msg = String.format("Failed to find outfile after %s", testCaseIdentifier); 115 if (mQuitOnError) { 116 throw new RuntimeException(msg); 117 } else { 118 CLog.e(msg + ". Continuing anyway..."); 119 } 120 } 121 } 122 123 try { 124 // Consider each threshold input 125 for (String thresholdStr : mThresholds) { 126 int threshold = 0; 127 try { 128 threshold = Integer.parseInt(thresholdStr); 129 } catch (NumberFormatException e) { 130 if (mQuitOnError) { 131 throw e; 132 } else { 133 CLog.e("Failed to parse threshold: %s", thresholdStr); 134 CLog.e(e); 135 continue; 136 } 137 } 138 // Generate the corresponding preloaded classes 139 File classes = writePreloadedClasses(threshold); 140 if (classes != null) { 141 try (FileInputStreamSource stream = new FileInputStreamSource(classes)) { 142 String name = String.format("preloaded-classes-threshold-%s", thresholdStr); 143 listener.testLog(name, LogDataType.TEXT, stream); 144 } 145 // Clean up after uploading 146 FileUtil.deleteFile(classes); 147 } else { 148 String msg = String.format( 149 "Failed to generate classes file for threshold, %s", thresholdStr); 150 if (mQuitOnError) { 151 throw new RuntimeException(msg); 152 } else { 153 CLog.e(msg + ". Continuing anyway..."); 154 } 155 } 156 } 157 } finally { 158 // Clean up temporary export files. 159 for (File f : mExportFiles) { 160 FileUtil.deleteFile(f); 161 } 162 } 163 } 164 165 /** 166 * Calls the preload tool to pull and scan heap profiles and to generate and export the list of 167 * loaded Java classes. 168 * @return {@link File} containing the loaded Java classes 169 */ 170 private File scanAndExportClasses() { 171 File temp = null; 172 try { 173 temp = FileUtil.createTempFile("scanned", ".txt"); 174 } catch (IOException e) { 175 CLog.e("Failed while creating temp file."); 176 CLog.e(e); 177 return null; 178 } 179 // Construct the command 180 String exportCmd = String.format(EXPORT_CMD, temp.getAbsolutePath()); 181 String actionCmd = String.format("%s %s", SCAN_ALL_CMD, exportCmd); 182 String[] fullCmd = constructPreloadCommand(actionCmd); 183 CommandResult result = RunUtil.getDefault().runTimedCmd(SCAN_TIMEOUT_MS, fullCmd); 184 if (CommandStatus.SUCCESS.equals(result.getStatus())) { 185 return temp; 186 } else { 187 // Clean up the temp file 188 FileUtil.deleteFile(temp); 189 // Log and return the failure 190 CLog.e("Error scanning: %s", result.getStderr()); 191 return null; 192 } 193 } 194 195 /** 196 * Calls the preload tool to import the previously exported files and to generate the list of 197 * preloaded classes based on the threshold input. 198 * @return {@link File} containing the generated list of preloaded classes 199 */ 200 private File writePreloadedClasses(int threshold) { 201 File temp = null; 202 try { 203 temp = FileUtil.createTempFile("preloaded-classes", ".txt"); 204 } catch (IOException e) { 205 CLog.e("Failed while creating temp file."); 206 CLog.e(e); 207 return null; 208 } 209 // Construct the command 210 String actionCmd = ""; 211 for (File f : mExportFiles) { 212 String importCmd = String.format(IMPORT_CMD, f.getAbsolutePath()); 213 actionCmd += importCmd + " "; 214 } 215 actionCmd += String.format(COMPUTE_CMD, threshold, temp.getAbsolutePath()); 216 String[] fullCmd = constructPreloadCommand(actionCmd); 217 CommandResult result = RunUtil.getDefault().runTimedCmd(COMPUTE_TIMEOUT_MS, fullCmd); 218 if (CommandStatus.SUCCESS.equals(result.getStatus())) { 219 return temp; 220 } else { 221 // Clean up the temp file 222 FileUtil.deleteFile(temp); 223 // Log and return the failure 224 CLog.e("Error computing classes: %s", result.getStderr()); 225 return null; 226 } 227 } 228 229 private String[] constructPreloadCommand(String command) { 230 return String.format(TOOL_CMD, mPreloadToolJarPath, getDevice().getSerialNumber(), command) 231 .split(" "); 232 } 233 234 @Override 235 public void setDevice(ITestDevice device) { 236 mDevice = device; 237 } 238 239 @Override 240 public ITestDevice getDevice() { 241 return mDevice; 242 } 243 244 @Override 245 public void setBuild(IBuildInfo buildInfo) { 246 mBuildInfo = buildInfo; 247 } 248 } 249