• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 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 package com.android.uicd.tests;
17 
18 import com.android.tradefed.build.IBuildInfo;
19 import com.android.tradefed.config.Option;
20 import com.android.tradefed.config.Option.Importance;
21 import com.android.tradefed.device.DeviceNotAvailableException;
22 import com.android.tradefed.device.ITestDevice;
23 import com.android.tradefed.log.LogUtil.CLog;
24 import com.android.tradefed.result.FileInputStreamSource;
25 import com.android.tradefed.result.ITestInvocationListener;
26 import com.android.tradefed.result.InputStreamSource;
27 import com.android.tradefed.result.LogDataType;
28 import com.android.tradefed.result.TestDescription;
29 import com.android.tradefed.testtype.IMultiDeviceTest;
30 import com.android.tradefed.testtype.IRemoteTest;
31 import com.android.tradefed.util.CommandResult;
32 import com.android.tradefed.util.FileUtil;
33 import com.android.tradefed.util.MultiMap;
34 import com.android.tradefed.util.RunUtil;
35 import java.io.File;
36 import java.io.IOException;
37 import java.nio.file.Paths;
38 import java.util.ArrayList;
39 import java.util.Collection;
40 import java.util.HashMap;
41 import java.util.List;
42 import java.util.Map;
43 import java.util.UUID;
44 import org.json.JSONArray;
45 import org.json.JSONException;
46 import org.json.JSONObject;
47 
48 /**
49  * The class enables user to run their pre-recorded UICD tests on tradefed. Go to
50  * https://github.com/google/android-uiconductor/releases/tag/v0.1.1 to download the uicd_cli.tar.gz
51  * and extract the jar and apks required for the tests. Please look at the sample xmls in
52  * res/config/uicd to configure your tests.
53  */
54 public class UiConductorTest implements IMultiDeviceTest, IRemoteTest {
55 
56     @Option(
57         name = "uicd-cli-jar",
58         description = "The cli jar that runs the user provided tests in commandline",
59         importance = Importance.IF_UNSET
60     )
61     private File cliJar;
62 
63     @Option(
64         name = "commandline-action-executable",
65         description =
66                 "the filesystem path of the binaries that are ran through command line actions on UICD. Can be repeated.",
67         importance = Importance.IF_UNSET
68     )
69     private Collection<File> binaries = new ArrayList<File>();
70 
71     @Option(
72         name = "global-variables",
73         description = "Global variable (uicd_key1=value1,uicd_key2=value2)",
74         importance = Importance.ALWAYS
75     )
76     private MultiMap<String, String> globalVariables = new MultiMap<>();
77 
78     @Option(
79         name = "play-mode",
80         description = "Play Mode (SINGLE|MULTIDEVICE|PLAYALL).",
81         importance = Importance.ALWAYS
82     )
83     private String playMode = "SINGLE";
84 
85     @Option(name = "test-name", description = "Name of the test.", importance = Importance.ALWAYS)
86     private String testName = "Your test results are here";
87 
88     // Same key can have multiple test files because global-variables can be referenced using the
89     // that particular key and shared across different tests.
90     // Refer res/config/uicd/uiconductor-globalvariable-sample.xml for more information.
91     @Option(
92         name = "uicd-test",
93         description =
94                 "the filesystem path of the json test files or directory of multiple json test files that needs to be run on devices. Can be repeated.",
95         importance = Importance.IF_UNSET
96     )
97     private MultiMap<String, File> uicdTest = new MultiMap<>();
98 
99     @Option(
100         name = "test-timeout",
101         description = "Time out for each test.",
102         importance = Importance.IF_UNSET
103     )
104     private int testTimeout = 1800000;
105 
106     private static final String BINARY_RELATIVE_PATH = "binary";
107 
108     private static final String OUTPUT_RELATIVE_PATH = "output";
109 
110     private static final String TESTS_RELATIVE_PATH = "tests";
111 
112     private static final String RESULTS_RELATIVE_PATH = "result";
113 
114     private static final String OPTION_SYMBOL = "-";
115     private static final String INPUT_OPTION_SHORT_NAME = "i";
116     private static final String OUTPUT_OPTION_SHORT_NAME = "o";
117     private static final String DEVICES_OPTION_SHORT_NAME = "d";
118     private static final String MODE_OPTION_SHORT_NAME = "m";
119     private static final String GLOBAL_VARIABLE_OPTION_SHORT_NAME = "g";
120 
121     private static final String CHILDRENRESULT_ATTRIBUTE = "childrenResult";
122     private static final String PLAYSTATUS_ATTRIBUTE = "playStatus";
123     private static final String VALIDATIONDETAILS_ATTRIBUTE = "validationDetails";
124 
125     private static final String EXECUTABLE = "u+x";
126 
127     private static String baseFilePath = System.getenv("HOME") + "/tmp/uicd-on-tf";
128 
129     Map<ITestDevice, IBuildInfo> deviceInfos;
130 
131     @Override
setDeviceInfos(Map<ITestDevice, IBuildInfo> deviceInfos)132     public void setDeviceInfos(Map<ITestDevice, IBuildInfo> deviceInfos) {
133         this.deviceInfos = deviceInfos;
134     }
135 
136     @Override
run(ITestInvocationListener listener)137     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
138         CLog.i("Starting the UIConductor tests:\n");
139         String runId = UUID.randomUUID().toString();
140         baseFilePath = Paths.get(baseFilePath, runId).toString();
141         String jarFileDir = Paths.get(baseFilePath, BINARY_RELATIVE_PATH).toString();
142         String testFilesDir = Paths.get(baseFilePath, TESTS_RELATIVE_PATH).toString();
143         String binaryFilesDir = Paths.get(baseFilePath).toString();
144         File jarFile;
145         MultiMap<String, File> copiedTestFileMap = new MultiMap<>();
146         if (cliJar == null || !cliJar.exists()) {
147             CLog.e("Unable to fetch provided binary.\n");
148             return;
149         }
150         try {
151             jarFile = copyFile(cliJar.getAbsolutePath(), jarFileDir);
152             FileUtil.chmod(jarFile, EXECUTABLE);
153 
154             for (Map.Entry<String, File> testFileOrDirEntry : uicdTest.entries()) {
155                 copiedTestFileMap.putAll(
156                         copyFile(
157                                 testFileOrDirEntry.getKey(),
158                                 testFileOrDirEntry.getValue().getAbsolutePath(),
159                                 testFilesDir));
160             }
161 
162             for (File binaryFile : binaries) {
163                 File binary = copyFile(binaryFile.getAbsolutePath(), binaryFilesDir);
164                 FileUtil.chmod(binary, EXECUTABLE);
165             }
166         } catch (IOException ex) {
167             throw new DeviceNotAvailableException(ex.getMessage());
168         }
169 
170         RunUtil rUtil = new RunUtil();
171         rUtil.setWorkingDir(new File(baseFilePath));
172         long runStartTime = System.currentTimeMillis();
173         listener.testRunStarted(testName, copiedTestFileMap.values().size());
174         for (Map.Entry<String, File> testFileEntry : copiedTestFileMap.entries()) {
175             runTest(
176                     listener,
177                     rUtil,
178                     jarFile,
179                     testFileEntry.getKey(),
180                     testFileEntry.getValue().getName());
181         }
182 
183         listener.testRunEnded(
184                 System.currentTimeMillis() - runStartTime, new HashMap<String, String>());
185         FileUtil.recursiveDelete(new File(baseFilePath));
186         CLog.i("Finishing the ui conductor tests\n");
187     }
188 
runTest( ITestInvocationListener listener, RunUtil rUtil, File jarFile, String key, String testFileName)189     public void runTest(
190             ITestInvocationListener listener,
191             RunUtil rUtil,
192             File jarFile,
193             String key,
194             String testFileName) {
195         TestDescription testDesc =
196                 new TestDescription(this.getClass().getSimpleName(), testFileName);
197         listener.testStarted(testDesc, System.currentTimeMillis());
198 
199         String testId = UUID.randomUUID().toString();
200         CommandResult cmndRes =
201                 rUtil.runTimedCmd(testTimeout, getCommand(jarFile, testFileName, testId, key));
202         logInfo(testId, "STD", cmndRes.getStdout());
203         logInfo(testId, "ERR", cmndRes.getStderr());
204 
205         File resultsFile =
206                 new File(
207                         Paths.get(
208                                         baseFilePath,
209                                         OUTPUT_RELATIVE_PATH,
210                                         testId,
211                                         RESULTS_RELATIVE_PATH,
212                                         "action_execution_result")
213                                 .toString());
214 
215         if (resultsFile.exists()) {
216             try {
217                 String content = FileUtil.readStringFromFile(resultsFile);
218                 JSONObject result = new JSONObject(content);
219                 List<String> errors = new ArrayList<>();
220                 errors = parseResult(errors, result);
221                 if (!errors.isEmpty()) {
222                     listener.testFailed(testDesc, errors.get(0));
223                     CLog.i("Test %s failed due to following errors: \n", testDesc.getTestName());
224                     for (String error : errors) {
225                         CLog.i(error + "\n");
226                     }
227                 }
228             } catch (IOException | JSONException e) {
229                 CLog.e(e);
230             }
231             String testResultFileName = testFileName + "_action_execution_result";
232             try (InputStreamSource iSSource = new FileInputStreamSource(resultsFile)) {
233                 listener.testLog(testResultFileName, LogDataType.TEXT, iSSource);
234             }
235         }
236         listener.testEnded(testDesc, System.currentTimeMillis(), new HashMap<String, String>());
237     }
238 
logInfo(String testId, String cmdOutputType, String content)239     private void logInfo(String testId, String cmdOutputType, String content) {
240         CLog.i(
241                 "==========================="
242                         + cmdOutputType
243                         + " logs for "
244                         + testId
245                         + " starts===========================\n");
246         CLog.i(content);
247         CLog.i(
248                 "==========================="
249                         + cmdOutputType
250                         + " logs for "
251                         + testId
252                         + " ends===========================\n");
253     }
254 
parseResult(List<String> errors, JSONObject result)255     private List<String> parseResult(List<String> errors, JSONObject result) throws JSONException {
256 
257         if (result != null) {
258             if (result.has(CHILDRENRESULT_ATTRIBUTE)) {
259                 JSONArray childResults = result.getJSONArray(CHILDRENRESULT_ATTRIBUTE);
260                 for (int i = 0; i < childResults.length(); i++) {
261                     errors = parseResult(errors, childResults.getJSONObject(i));
262                 }
263             }
264 
265             if (result.has(PLAYSTATUS_ATTRIBUTE)
266                     && result.getString(PLAYSTATUS_ATTRIBUTE).equalsIgnoreCase("FAIL")) {
267                 if (result.has(VALIDATIONDETAILS_ATTRIBUTE)) {
268                     errors.add(result.getString(VALIDATIONDETAILS_ATTRIBUTE));
269                 }
270             }
271         }
272         return errors;
273     }
274 
copyFile(String srcFilePath, String destDirPath)275     private File copyFile(String srcFilePath, String destDirPath) throws IOException {
276         File srcFile = new File(srcFilePath);
277         File destDir = new File(destDirPath);
278         if (srcFile.isDirectory()) {
279             for (File file : srcFile.listFiles()) {
280                 copyFile(file.getAbsolutePath(), Paths.get(destDirPath, file.getName()).toString());
281             }
282         }
283         if (!destDir.isDirectory() && !destDir.mkdirs()) {
284             throw new IOException(
285                     String.format("Could not create directory %s", destDir.getAbsolutePath()));
286         }
287         File destFile = new File(Paths.get(destDir.toString(), srcFile.getName()).toString());
288         FileUtil.copyFile(srcFile, destFile);
289         return destFile;
290     }
291 
292     // copy file to destDirPath while maintaining a map of key that refers to that src file
copyFile(String key, String srcFilePath, String destDirPath)293     private MultiMap<String, File> copyFile(String key, String srcFilePath, String destDirPath)
294             throws IOException {
295         MultiMap<String, File> copiedTestFileMap = new MultiMap<>();
296         File srcFile = new File(srcFilePath);
297         File destDir = new File(destDirPath);
298         if (srcFile.isDirectory()) {
299             for (File file : srcFile.listFiles()) {
300                 copiedTestFileMap.putAll(
301                         copyFile(
302                                 key,
303                                 file.getAbsolutePath(),
304                                 Paths.get(destDirPath, file.getName()).toString()));
305             }
306         }
307         if (!destDir.isDirectory() && !destDir.mkdirs()) {
308             throw new IOException(
309                     String.format("Could not create directory %s", destDir.getAbsolutePath()));
310         }
311         if (srcFile.isFile()) {
312             File destFile = new File(Paths.get(destDir.toString(), srcFile.getName()).toString());
313             FileUtil.copyFile(srcFile, destFile);
314             copiedTestFileMap.put(key, destFile);
315         }
316         return copiedTestFileMap;
317     }
318 
getTestFilesArgsForUicdBin(String testFilesDir, String filename)319     private String getTestFilesArgsForUicdBin(String testFilesDir, String filename) {
320         return (!testFilesDir.isEmpty() && !filename.isEmpty())
321                 ? Paths.get(testFilesDir, filename).toString()
322                 : "";
323     }
324 
getOutFilesArgsForUicdBin(String outFilesDir)325     private String getOutFilesArgsForUicdBin(String outFilesDir) {
326         return !outFilesDir.isEmpty() ? outFilesDir : "";
327     }
328 
getPlaymodeArgForUicdBin()329     private String getPlaymodeArgForUicdBin() {
330         return !playMode.isEmpty() ? playMode : "";
331     }
332 
getDevIdsArgsForUicdBin()333     private String getDevIdsArgsForUicdBin() {
334         List<String> devIds = new ArrayList<>();
335         for (ITestDevice device : deviceInfos.keySet()) {
336             devIds.add(device.getSerialNumber());
337         }
338         return String.join(",", devIds);
339     }
340 
getCommand(File jarFile, String testFileName, String testId, String key)341     private String[] getCommand(File jarFile, String testFileName, String testId, String key) {
342         List<String> command = new ArrayList<>();
343         command.add("java");
344         command.add("-jar");
345         command.add(jarFile.getAbsolutePath());
346         if (!getTestFilesArgsForUicdBin(TESTS_RELATIVE_PATH, testFileName).isEmpty()) {
347             command.add(OPTION_SYMBOL + INPUT_OPTION_SHORT_NAME);
348             command.add(getTestFilesArgsForUicdBin(TESTS_RELATIVE_PATH, testFileName));
349         }
350         if (!getOutFilesArgsForUicdBin(OUTPUT_RELATIVE_PATH + "/" + testId).isEmpty()) {
351             command.add(OPTION_SYMBOL + OUTPUT_OPTION_SHORT_NAME);
352             command.add(getOutFilesArgsForUicdBin(OUTPUT_RELATIVE_PATH + "/" + testId));
353         }
354         if (!getPlaymodeArgForUicdBin().isEmpty()) {
355             command.add(OPTION_SYMBOL + MODE_OPTION_SHORT_NAME);
356             command.add(getPlaymodeArgForUicdBin());
357         }
358         if (!getDevIdsArgsForUicdBin().isEmpty()) {
359             command.add(OPTION_SYMBOL + DEVICES_OPTION_SHORT_NAME);
360             command.add(getDevIdsArgsForUicdBin());
361         }
362         if (globalVariables.containsKey(key)) {
363             command.add(OPTION_SYMBOL + GLOBAL_VARIABLE_OPTION_SHORT_NAME);
364             command.add(String.join(",", globalVariables.get(key)));
365         }
366         return command.toArray(new String[] {});
367     }
368 }
369