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