blob: 32c543eae0f0cbaf880bf1f752ff30a6b2981b3f [file] [log] [blame]
/*
* 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;
}
}