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