/* * 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 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 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 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; } }