Home | History | Annotate | Download | only in tests
      1 /*
      2  * Copyright (C) 2014 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.graphics.tests;
     18 
     19 import com.android.tradefed.config.Option;
     20 import com.android.tradefed.config.OptionClass;
     21 import com.android.tradefed.device.DeviceNotAvailableException;
     22 import com.android.tradefed.device.IFileEntry;
     23 import com.android.tradefed.device.ITestDevice;
     24 import com.android.tradefed.log.LogUtil.CLog;
     25 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
     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.result.TestDescription;
     30 import com.android.tradefed.testtype.IDeviceTest;
     31 import com.android.tradefed.testtype.IRemoteTest;
     32 import com.android.tradefed.util.RunUtil;
     33 
     34 import java.io.File;
     35 import java.util.HashMap;
     36 
     37 /**
     38  *  Test for running Skia native tests.
     39  *
     40  *  The test is not necessarily Skia specific, but it provides
     41  *  functionality that allows native Skia tests to be run.
     42  *
     43  *  Includes options to specify the Skia test app to run (inside
     44  *  nativetest directory), flags to pass to the test app, and a file
     45  *  to retrieve off the device after the test completes. (Skia test
     46  *  apps record their results to a json file, so retrieving this file
     47  *  allows us to view the results so long as the app completed.)
     48  */
     49 @OptionClass(alias = "skia_native_tests")
     50 public class SkiaTest implements IRemoteTest, IDeviceTest {
     51     private ITestDevice mDevice;
     52 
     53     static final String DEFAULT_NATIVETEST_PATH = "/data/nativetest";
     54 
     55     @Option(name = "native-test-device-path",
     56       description = "The path on the device where native tests are located.")
     57     private String mNativeTestDevicePath = DEFAULT_NATIVETEST_PATH;
     58 
     59     @Option(name = "skia-flags",
     60         description = "Flags to pass to the skia program.")
     61     private String mFlags = "";
     62 
     63     @Option(name = "skia-app",
     64         description = "Skia program to run.",
     65         mandatory = true)
     66     private String mSkiaApp = "";
     67 
     68     @Option(name = "skia-json",
     69         description = "Full path on device for json output file.")
     70     private File mOutputFile = null;
     71 
     72     @Option(name = "skia-pngs",
     73         description = "Directory on device for holding png results for retrieval.")
     74     private File mPngDir = null;
     75 
     76     @Override
     77     public void setDevice(ITestDevice device) {
     78         mDevice = device;
     79     }
     80 
     81     @Override
     82     public ITestDevice getDevice() {
     83         return mDevice;
     84     }
     85 
     86     @Override
     87     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
     88         if (mDevice == null) {
     89             throw new IllegalArgumentException("Device has not been set");
     90         }
     91 
     92         listener.testRunStarted(mSkiaApp, 1);
     93         long start = System.currentTimeMillis();
     94 
     95         // Native Skia tests are in nativeTestDirectory/mSkiaApp/mSkiaApp.
     96         String fullPath = mNativeTestDevicePath + "/"
     97                 + mSkiaApp + "/" + mSkiaApp;
     98         IFileEntry app = mDevice.getFileEntry(fullPath);
     99         TestDescription testId = new TestDescription(mSkiaApp, "testFileExists");
    100         listener.testStarted(testId);
    101         if (app == null) {
    102             CLog.w("Could not find test %s in %s!", fullPath, mDevice.getSerialNumber());
    103             listener.testFailed(testId, "Device does not have " + fullPath);
    104             listener.testEnded(testId, new HashMap<String, Metric>());
    105         } else {
    106             // The test for detecting the file has ended.
    107             listener.testEnded(testId, new HashMap<String, Metric>());
    108             prepareDevice();
    109             runTest(app);
    110             retrieveFiles(mSkiaApp, listener);
    111         }
    112 
    113         listener.testRunEnded(System.currentTimeMillis() - start, new HashMap<String, Metric>());
    114     }
    115 
    116     /**
    117      *  Emulates running mkdirs on an ITestDevice.
    118      *
    119      *  Creates the directory named by dir *on device*, recursively creating missing parent
    120      *  directories if necessary.
    121      *
    122      *  @param dir Directory to create.
    123      */
    124     private void mkdirs(File dir) throws DeviceNotAvailableException {
    125         if (dir == null || mDevice.doesFileExist(dir.getPath())) {
    126             return;
    127         }
    128 
    129         String dirName = dir.getPath();
    130         CLog.v("creating folder '%s'", dirName);
    131         mDevice.executeShellCommand("mkdir -p " + dirName);
    132     }
    133 
    134     /**
    135      *  Do pre-test setup on the device.
    136      *
    137      *  Setup involves ensuring necessary directories exist and removing old
    138      *  test result files.
    139      */
    140     private void prepareDevice() throws DeviceNotAvailableException {
    141         if (mOutputFile != null) {
    142             String path = mOutputFile.getPath();
    143             if (mDevice.doesFileExist(path)) {
    144                 // Delete the file. We don't want to think this file from an
    145                 // earlier run represents this one.
    146                 CLog.v("Removing old file " + path);
    147                 mDevice.executeShellCommand("rm " + path);
    148             } else {
    149                 // Ensure its containing folder exists.
    150                 mkdirs(mOutputFile.getParentFile());
    151             }
    152         }
    153 
    154         if (mPngDir != null) {
    155             String pngPath = mPngDir.getPath();
    156             if (mDevice.doesFileExist(pngPath)) {
    157                 // Empty the old directory
    158                 mDevice.executeShellCommand("rm -rf " + pngPath + "/*");
    159             } else {
    160                 mkdirs(mPngDir);
    161             }
    162         }
    163     }
    164 
    165     /**
    166      * Retrieve a file from the device and upload it to the listener.
    167      *
    168      * <p>Each file for uploading is considered its own test, so we can track whether or not
    169      * uploading succeeded.
    170      *
    171      * @param remoteFile File on the device.
    172      * @param testIdClass String to be passed to TestDescription's constructor as className.
    173      * @param testIdMethod String passed to TestDescription's constructor as testName.
    174      * @param listener Listener for reporting test failure/success and uploading files.
    175      * @param type LogDataType of the file being uploaded.
    176      */
    177     private void retrieveAndUploadFile(
    178             File remoteFile,
    179             String testIdClass,
    180             String testIdMethod,
    181             ITestInvocationListener listener,
    182             LogDataType type)
    183             throws DeviceNotAvailableException {
    184         String remotePath = remoteFile.getPath();
    185         CLog.v("adb pull %s (using pullFile)", remotePath);
    186         File localFile = mDevice.pullFile(remotePath);
    187 
    188         TestDescription testId = new TestDescription(testIdClass, testIdMethod);
    189         listener.testStarted(testId);
    190         if (localFile == null) {
    191             listener.testFailed(testId, "Failed to pull " + remotePath);
    192         } else {
    193             CLog.v("pulled result file to " + localFile.getPath());
    194             try (FileInputStreamSource source = new FileInputStreamSource(localFile)) {
    195                 // Use the original name, for clarity.
    196                 listener.testLog(remoteFile.getName(), type, source);
    197             }
    198             if (!localFile.delete()) {
    199                 CLog.w("Failed to delete temporary file %s", localFile.getPath());
    200             }
    201         }
    202         listener.testEnded(testId, new HashMap<String, Metric>());
    203     }
    204 
    205     /**
    206      *  Retrieve files from the device.
    207      *
    208      *  Report to the listener whether retrieving the files succeeded.
    209      *
    210      *  @param appName Name of the app.
    211      *  @param listener Listener for reporting results of file retrieval.
    212      */
    213     private void retrieveFiles(String appName,
    214             ITestInvocationListener listener) throws DeviceNotAvailableException {
    215         // FIXME: This could be achieved with DeviceFileReporter. Blocked on b/18408206.
    216         if (mOutputFile != null) {
    217             retrieveAndUploadFile(mOutputFile, appName, "outputJson", listener, LogDataType.TEXT);
    218         }
    219 
    220         if (mPngDir != null) {
    221             String pngDir = mPngDir.getPath();
    222             IFileEntry remotePngDir = mDevice.getFileEntry(pngDir);
    223             for (IFileEntry pngFile : remotePngDir.getChildren(false)) {
    224                 if (pngFile.getName().endsWith("png")) {
    225                     retrieveAndUploadFile(new File(pngFile.getFullPath()),
    226                             "PngRetrieval", pngFile.getName(), listener, LogDataType.PNG);
    227                 }
    228             }
    229         }
    230     }
    231 
    232     /**
    233      *  Run a test on a device.
    234      *
    235      *  @param app Test app to run.
    236      */
    237     private void runTest(IFileEntry app) throws DeviceNotAvailableException {
    238         String fullPath = app.getFullEscapedPath();
    239         // force file to be executable
    240         mDevice.executeShellCommand(String.format("chmod 755 %s", fullPath));
    241 
    242         // The device will not immediately capture logs in response to
    243         // startLogcat. Instead, it delays 5 * 1000ms. See TestDevice.java
    244         // mLogStartDelay. To ensure we see all the logs, sleep by the same
    245         // amount.
    246         mDevice.startLogcat();
    247         RunUtil.getDefault().sleep(5 * 1000);
    248 
    249         String cmd = fullPath + " " + mFlags;
    250         CLog.v("Running '%s' on %s", cmd, mDevice.getSerialNumber());
    251 
    252         mDevice.executeShellCommand("stop");
    253         mDevice.executeShellCommand(cmd);
    254         mDevice.executeShellCommand("start");
    255     }
    256 }
    257