• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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