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