blob: 4fa9ebe8d5d1e95a2073846946ca6d146c1ac1bb [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.uicd.tests;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.Option.Importance;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.testtype.IMultiDeviceTest;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.RunUtil;
import java.io.File;
import java.io.IOException;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* The class enables user to run their pre-recorded UICD tests on tradefed. Go to
* https://github.com/google/android-uiconductor/releases/tag/v0.1.1 to download the uicd_cli.tar.gz
* and extract the jar and apks required for the tests. Please look at the sample xmls in
* res/config/uicd to configure your tests.
*/
public class UiConductorTest implements IMultiDeviceTest, IRemoteTest {
@Option(
name = "uicd-cli-jar",
description = "The cli jar that runs the user provided tests in commandline",
importance = Importance.IF_UNSET
)
private File cliJar;
@Option(
name = "commandline-action-executable",
description =
"the filesystem path of the binaries that are ran through command line actions on UICD. Can be repeated.",
importance = Importance.IF_UNSET
)
private Collection<File> binaries = new ArrayList<File>();
@Option(
name = "global-variables",
description = "Global variable (uicd_key1=value1,uicd_key2=value2)",
importance = Importance.ALWAYS
)
private MultiMap<String, String> globalVariables = new MultiMap<>();
@Option(
name = "play-mode",
description = "Play Mode (SINGLE|MULTIDEVICE|PLAYALL).",
importance = Importance.ALWAYS
)
private String playMode = "SINGLE";
@Option(name = "test-name", description = "Name of the test.", importance = Importance.ALWAYS)
private String testName = "Your test results are here";
// Same key can have multiple test files because global-variables can be referenced using the
// that particular key and shared across different tests.
// Refer res/config/uicd/uiconductor-globalvariable-sample.xml for more information.
@Option(
name = "uicd-test",
description =
"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.",
importance = Importance.IF_UNSET
)
private MultiMap<String, File> uicdTest = new MultiMap<>();
@Option(
name = "test-timeout",
description = "Time out for each test.",
importance = Importance.IF_UNSET
)
private int testTimeout = 1800000;
private static final String BINARY_RELATIVE_PATH = "binary";
private static final String OUTPUT_RELATIVE_PATH = "output";
private static final String TESTS_RELATIVE_PATH = "tests";
private static final String RESULTS_RELATIVE_PATH = "result";
private static final String OPTION_SYMBOL = "-";
private static final String INPUT_OPTION_SHORT_NAME = "i";
private static final String OUTPUT_OPTION_SHORT_NAME = "o";
private static final String DEVICES_OPTION_SHORT_NAME = "d";
private static final String MODE_OPTION_SHORT_NAME = "m";
private static final String GLOBAL_VARIABLE_OPTION_SHORT_NAME = "g";
private static final String CHILDRENRESULT_ATTRIBUTE = "childrenResult";
private static final String PLAYSTATUS_ATTRIBUTE = "playStatus";
private static final String VALIDATIONDETAILS_ATTRIBUTE = "validationDetails";
private static final String EXECUTABLE = "u+x";
private static String baseFilePath = System.getenv("HOME") + "/tmp/uicd-on-tf";
Map<ITestDevice, IBuildInfo> deviceInfos;
@Override
public void setDeviceInfos(Map<ITestDevice, IBuildInfo> deviceInfos) {
this.deviceInfos = deviceInfos;
}
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
CLog.i("Starting the UIConductor tests:\n");
String runId = UUID.randomUUID().toString();
baseFilePath = Paths.get(baseFilePath, runId).toString();
String jarFileDir = Paths.get(baseFilePath, BINARY_RELATIVE_PATH).toString();
String testFilesDir = Paths.get(baseFilePath, TESTS_RELATIVE_PATH).toString();
String binaryFilesDir = Paths.get(baseFilePath).toString();
File jarFile;
MultiMap<String, File> copiedTestFileMap = new MultiMap<>();
if (cliJar == null || !cliJar.exists()) {
CLog.e("Unable to fetch provided binary.\n");
return;
}
try {
jarFile = copyFile(cliJar.getAbsolutePath(), jarFileDir);
FileUtil.chmod(jarFile, EXECUTABLE);
for (Map.Entry<String, File> testFileOrDirEntry : uicdTest.entries()) {
copiedTestFileMap.putAll(
copyFile(
testFileOrDirEntry.getKey(),
testFileOrDirEntry.getValue().getAbsolutePath(),
testFilesDir));
}
for (File binaryFile : binaries) {
File binary = copyFile(binaryFile.getAbsolutePath(), binaryFilesDir);
FileUtil.chmod(binary, EXECUTABLE);
}
} catch (IOException ex) {
throw new DeviceNotAvailableException(ex.getMessage());
}
RunUtil rUtil = new RunUtil();
rUtil.setWorkingDir(new File(baseFilePath));
long runStartTime = System.currentTimeMillis();
listener.testRunStarted(testName, copiedTestFileMap.values().size());
for (Map.Entry<String, File> testFileEntry : copiedTestFileMap.entries()) {
runTest(
listener,
rUtil,
jarFile,
testFileEntry.getKey(),
testFileEntry.getValue().getName());
}
listener.testRunEnded(
System.currentTimeMillis() - runStartTime, new HashMap<String, String>());
FileUtil.recursiveDelete(new File(baseFilePath));
CLog.i("Finishing the ui conductor tests\n");
}
public void runTest(
ITestInvocationListener listener,
RunUtil rUtil,
File jarFile,
String key,
String testFileName) {
TestDescription testDesc =
new TestDescription(this.getClass().getSimpleName(), testFileName);
listener.testStarted(testDesc, System.currentTimeMillis());
String testId = UUID.randomUUID().toString();
CommandResult cmndRes =
rUtil.runTimedCmd(testTimeout, getCommand(jarFile, testFileName, testId, key));
logInfo(testId, "STD", cmndRes.getStdout());
logInfo(testId, "ERR", cmndRes.getStderr());
File resultsFile =
new File(
Paths.get(
baseFilePath,
OUTPUT_RELATIVE_PATH,
testId,
RESULTS_RELATIVE_PATH,
"action_execution_result")
.toString());
if (resultsFile.exists()) {
try {
String content = FileUtil.readStringFromFile(resultsFile);
JSONObject result = new JSONObject(content);
List<String> errors = new ArrayList<>();
errors = parseResult(errors, result);
if (!errors.isEmpty()) {
listener.testFailed(testDesc, errors.get(0));
CLog.i("Test %s failed due to following errors: \n", testDesc.getTestName());
for (String error : errors) {
CLog.i(error + "\n");
}
}
} catch (IOException | JSONException e) {
CLog.e(e);
}
String testResultFileName = testFileName + "_action_execution_result";
try (InputStreamSource iSSource = new FileInputStreamSource(resultsFile)) {
listener.testLog(testResultFileName, LogDataType.TEXT, iSSource);
}
}
listener.testEnded(testDesc, System.currentTimeMillis(), new HashMap<String, String>());
}
private void logInfo(String testId, String cmdOutputType, String content) {
CLog.i(
"==========================="
+ cmdOutputType
+ " logs for "
+ testId
+ " starts===========================\n");
CLog.i(content);
CLog.i(
"==========================="
+ cmdOutputType
+ " logs for "
+ testId
+ " ends===========================\n");
}
private List<String> parseResult(List<String> errors, JSONObject result) throws JSONException {
if (result != null) {
if (result.has(CHILDRENRESULT_ATTRIBUTE)) {
JSONArray childResults = result.getJSONArray(CHILDRENRESULT_ATTRIBUTE);
for (int i = 0; i < childResults.length(); i++) {
errors = parseResult(errors, childResults.getJSONObject(i));
}
}
if (result.has(PLAYSTATUS_ATTRIBUTE)
&& result.getString(PLAYSTATUS_ATTRIBUTE).equalsIgnoreCase("FAIL")) {
if (result.has(VALIDATIONDETAILS_ATTRIBUTE)) {
errors.add(result.getString(VALIDATIONDETAILS_ATTRIBUTE));
}
}
}
return errors;
}
private File copyFile(String srcFilePath, String destDirPath) throws IOException {
File srcFile = new File(srcFilePath);
File destDir = new File(destDirPath);
if (srcFile.isDirectory()) {
for (File file : srcFile.listFiles()) {
copyFile(file.getAbsolutePath(), Paths.get(destDirPath, file.getName()).toString());
}
}
if (!destDir.isDirectory() && !destDir.mkdirs()) {
throw new IOException(
String.format("Could not create directory %s", destDir.getAbsolutePath()));
}
File destFile = new File(Paths.get(destDir.toString(), srcFile.getName()).toString());
FileUtil.copyFile(srcFile, destFile);
return destFile;
}
// copy file to destDirPath while maintaining a map of key that refers to that src file
private MultiMap<String, File> copyFile(String key, String srcFilePath, String destDirPath)
throws IOException {
MultiMap<String, File> copiedTestFileMap = new MultiMap<>();
File srcFile = new File(srcFilePath);
File destDir = new File(destDirPath);
if (srcFile.isDirectory()) {
for (File file : srcFile.listFiles()) {
copiedTestFileMap.putAll(
copyFile(
key,
file.getAbsolutePath(),
Paths.get(destDirPath, file.getName()).toString()));
}
}
if (!destDir.isDirectory() && !destDir.mkdirs()) {
throw new IOException(
String.format("Could not create directory %s", destDir.getAbsolutePath()));
}
if (srcFile.isFile()) {
File destFile = new File(Paths.get(destDir.toString(), srcFile.getName()).toString());
FileUtil.copyFile(srcFile, destFile);
copiedTestFileMap.put(key, destFile);
}
return copiedTestFileMap;
}
private String getTestFilesArgsForUicdBin(String testFilesDir, String filename) {
return (!testFilesDir.isEmpty() && !filename.isEmpty())
? Paths.get(testFilesDir, filename).toString()
: "";
}
private String getOutFilesArgsForUicdBin(String outFilesDir) {
return !outFilesDir.isEmpty() ? outFilesDir : "";
}
private String getPlaymodeArgForUicdBin() {
return !playMode.isEmpty() ? playMode : "";
}
private String getDevIdsArgsForUicdBin() {
List<String> devIds = new ArrayList<>();
for (ITestDevice device : deviceInfos.keySet()) {
devIds.add(device.getSerialNumber());
}
return String.join(",", devIds);
}
private String[] getCommand(File jarFile, String testFileName, String testId, String key) {
List<String> command = new ArrayList<>();
command.add("java");
command.add("-jar");
command.add(jarFile.getAbsolutePath());
if (!getTestFilesArgsForUicdBin(TESTS_RELATIVE_PATH, testFileName).isEmpty()) {
command.add(OPTION_SYMBOL + INPUT_OPTION_SHORT_NAME);
command.add(getTestFilesArgsForUicdBin(TESTS_RELATIVE_PATH, testFileName));
}
if (!getOutFilesArgsForUicdBin(OUTPUT_RELATIVE_PATH + "/" + testId).isEmpty()) {
command.add(OPTION_SYMBOL + OUTPUT_OPTION_SHORT_NAME);
command.add(getOutFilesArgsForUicdBin(OUTPUT_RELATIVE_PATH + "/" + testId));
}
if (!getPlaymodeArgForUicdBin().isEmpty()) {
command.add(OPTION_SYMBOL + MODE_OPTION_SHORT_NAME);
command.add(getPlaymodeArgForUicdBin());
}
if (!getDevIdsArgsForUicdBin().isEmpty()) {
command.add(OPTION_SYMBOL + DEVICES_OPTION_SHORT_NAME);
command.add(getDevIdsArgsForUicdBin());
}
if (globalVariables.containsKey(key)) {
command.add(OPTION_SYMBOL + GLOBAL_VARIABLE_OPTION_SHORT_NAME);
command.add(String.join(",", globalVariables.get(key)));
}
return command.toArray(new String[] {});
}
}