| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.framework.tests; |
| |
| import com.android.ddmlib.testrunner.IRemoteAndroidTestRunner; |
| import com.android.ddmlib.testrunner.RemoteAndroidTestRunner; |
| import com.android.tradefed.build.IBuildInfo; |
| import com.android.tradefed.config.Option; |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| import com.android.tradefed.device.ITestDevice; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.result.FileInputStreamSource; |
| import com.android.tradefed.result.ITestInvocationListener; |
| import com.android.tradefed.result.LogDataType; |
| import com.android.tradefed.testtype.IBuildReceiver; |
| import com.android.tradefed.testtype.IDeviceTest; |
| import com.android.tradefed.testtype.IRemoteTest; |
| import com.android.tradefed.util.CommandResult; |
| import com.android.tradefed.util.CommandStatus; |
| import com.android.tradefed.util.FileUtil; |
| import com.android.tradefed.util.RunUtil; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Runs a series of automated use cases and collects loaded class information in order to generate |
| * a list of preloaded classes based on the input thresholds. |
| */ |
| public class PreloadedClassesTest implements IRemoteTest, IDeviceTest, IBuildReceiver { |
| private static final String JUNIT_RUNNER = "android.support.test.runner.AndroidJUnitRunner"; |
| // Preload tool commands |
| private static final String TOOL_CMD = "java -cp %s com.android.preload.Main --seq %s %s"; |
| private static final String SCAN_ALL_CMD = "scan-all"; |
| private static final String COMPUTE_CMD = "comp %d %s"; |
| private static final String EXPORT_CMD = "export %s"; |
| private static final String IMPORT_CMD = "import %s"; |
| // Large, common timeouts |
| private static final long SCAN_TIMEOUT_MS = 5 * 60 * 1000; |
| private static final long COMPUTE_TIMEOUT_MS = 60 * 1000; |
| |
| @Option(name = "package", |
| description = "Instrumentation package for use case automation.", |
| mandatory = true) |
| private String mPackage = null; |
| |
| @Option(name = "test-case", |
| description = "List of use cases to exercise from the package.", |
| mandatory = true) |
| private List<String> mTestCases = new ArrayList<>(); |
| |
| @Option(name = "preload-tool", description = "Overridden location of the preload JAR file.") |
| private String mPreloadToolJarPath = null; |
| |
| @Option(name = "threshold", |
| description = "List of thresholds for computing preloaded classes.", |
| mandatory = true) |
| private List<String> mThresholds = new ArrayList<>(); |
| |
| @Option(name = "quit-on-error", |
| description = "Quits if errors are encountered anywhere in the process.", |
| mandatory = false) |
| private boolean mQuitOnError = false; |
| |
| private ITestDevice mDevice; |
| private IBuildInfo mBuildInfo; |
| private List<File> mExportFiles = new ArrayList<>(); |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { |
| // Download preload tool, if not supplied |
| if (mPreloadToolJarPath == null) { |
| File preload = mBuildInfo.getFile("preload2.jar"); |
| if (preload != null && preload.exists()) { |
| mPreloadToolJarPath = preload.getAbsolutePath(); |
| } else { |
| CLog.e("Unable to find the preload tool."); |
| } |
| } else { |
| CLog.v("Using alternative preload tool path, %s", mPreloadToolJarPath); |
| } |
| |
| IRemoteAndroidTestRunner runner = |
| new RemoteAndroidTestRunner(mPackage, JUNIT_RUNNER, getDevice().getIDevice()); |
| |
| for (String testCaseIdentifier : mTestCases) { |
| // Run an individual use case |
| runner.addInstrumentationArg("class", testCaseIdentifier); |
| getDevice().runInstrumentationTests(runner, listener); |
| // Scan loaded classes and export |
| File outfile = scanAndExportClasses(); |
| if (outfile != null) { |
| mExportFiles.add(outfile); |
| } else { |
| String msg = String.format("Failed to find outfile after %s", testCaseIdentifier); |
| if (mQuitOnError) { |
| throw new RuntimeException(msg); |
| } else { |
| CLog.e(msg + ". Continuing anyway..."); |
| } |
| } |
| } |
| |
| try { |
| // Consider each threshold input |
| for (String thresholdStr : mThresholds) { |
| int threshold = 0; |
| try { |
| threshold = Integer.parseInt(thresholdStr); |
| } catch (NumberFormatException e) { |
| if (mQuitOnError) { |
| throw e; |
| } else { |
| CLog.e("Failed to parse threshold: %s", thresholdStr); |
| CLog.e(e); |
| continue; |
| } |
| } |
| // Generate the corresponding preloaded classes |
| File classes = writePreloadedClasses(threshold); |
| if (classes != null) { |
| try (FileInputStreamSource stream = new FileInputStreamSource(classes)) { |
| String name = String.format("preloaded-classes-threshold-%s", thresholdStr); |
| listener.testLog(name, LogDataType.TEXT, stream); |
| } |
| // Clean up after uploading |
| FileUtil.deleteFile(classes); |
| } else { |
| String msg = String.format( |
| "Failed to generate classes file for threshold, %s", thresholdStr); |
| if (mQuitOnError) { |
| throw new RuntimeException(msg); |
| } else { |
| CLog.e(msg + ". Continuing anyway..."); |
| } |
| } |
| } |
| } finally { |
| // Clean up temporary export files. |
| for (File f : mExportFiles) { |
| FileUtil.deleteFile(f); |
| } |
| } |
| } |
| |
| /** |
| * Calls the preload tool to pull and scan heap profiles and to generate and export the list of |
| * loaded Java classes. |
| * @return {@link File} containing the loaded Java classes |
| */ |
| private File scanAndExportClasses() { |
| File temp = null; |
| try { |
| temp = FileUtil.createTempFile("scanned", ".txt"); |
| } catch (IOException e) { |
| CLog.e("Failed while creating temp file."); |
| CLog.e(e); |
| return null; |
| } |
| // Construct the command |
| String exportCmd = String.format(EXPORT_CMD, temp.getAbsolutePath()); |
| String actionCmd = String.format("%s %s", SCAN_ALL_CMD, exportCmd); |
| String[] fullCmd = constructPreloadCommand(actionCmd); |
| CommandResult result = RunUtil.getDefault().runTimedCmd(SCAN_TIMEOUT_MS, fullCmd); |
| if (CommandStatus.SUCCESS.equals(result.getStatus())) { |
| return temp; |
| } else { |
| // Clean up the temp file |
| FileUtil.deleteFile(temp); |
| // Log and return the failure |
| CLog.e("Error scanning: %s", result.getStderr()); |
| return null; |
| } |
| } |
| |
| /** |
| * Calls the preload tool to import the previously exported files and to generate the list of |
| * preloaded classes based on the threshold input. |
| * @return {@link File} containing the generated list of preloaded classes |
| */ |
| private File writePreloadedClasses(int threshold) { |
| File temp = null; |
| try { |
| temp = FileUtil.createTempFile("preloaded-classes", ".txt"); |
| } catch (IOException e) { |
| CLog.e("Failed while creating temp file."); |
| CLog.e(e); |
| return null; |
| } |
| // Construct the command |
| String actionCmd = ""; |
| for (File f : mExportFiles) { |
| String importCmd = String.format(IMPORT_CMD, f.getAbsolutePath()); |
| actionCmd += importCmd + " "; |
| } |
| actionCmd += String.format(COMPUTE_CMD, threshold, temp.getAbsolutePath()); |
| String[] fullCmd = constructPreloadCommand(actionCmd); |
| CommandResult result = RunUtil.getDefault().runTimedCmd(COMPUTE_TIMEOUT_MS, fullCmd); |
| if (CommandStatus.SUCCESS.equals(result.getStatus())) { |
| return temp; |
| } else { |
| // Clean up the temp file |
| FileUtil.deleteFile(temp); |
| // Log and return the failure |
| CLog.e("Error computing classes: %s", result.getStderr()); |
| return null; |
| } |
| } |
| |
| private String[] constructPreloadCommand(String command) { |
| return String.format(TOOL_CMD, mPreloadToolJarPath, getDevice().getSerialNumber(), command) |
| .split(" "); |
| } |
| |
| @Override |
| public void setDevice(ITestDevice device) { |
| mDevice = device; |
| } |
| |
| @Override |
| public ITestDevice getDevice() { |
| return mDevice; |
| } |
| |
| @Override |
| public void setBuild(IBuildInfo buildInfo) { |
| mBuildInfo = buildInfo; |
| } |
| } |