Home | History | Annotate | Download | only in tests
      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