Snap for 5606075 from 1bc59c39bf6a116bfa7f1da5587f883e19bdb65b to qt-c2f2-release
Change-Id: Ib3e8c9e400bcfd4ecb20aecaa77996e4a127fea4
diff --git a/res/config/build/kernel-image-check.xml b/res/config/build/kernel-image-check.xml
new file mode 100644
index 0000000..73043c9
--- /dev/null
+++ b/res/config/build/kernel-image-check.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs host-based kernel image check tests">
+ <option name="null-device" value="true" />
+ <test class="com.android.tradefed.testtype.HostTest" >
+ <option name="class" value="com.android.build.tests.KernelImageCheck" />
+ </test>
+</configuration>
diff --git a/res/config/example/multi-devices.xml b/res/config/example/multi-devices.xml
new file mode 100644
index 0000000..2c556e6
--- /dev/null
+++ b/res/config/example/multi-devices.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="A simple multi-devices example in Tradefed">
+ <option name="test-tag" value="multi-devices-example" />
+
+ <device name="device1">
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="disable" value="true" />
+ </target_preparer>
+ </device>
+
+ <device name="device2">
+ <target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
+ <option name="disable" value="true" />
+ </target_preparer>
+ </device>
+ <multi_target_preparer class="com.android.tradefed.targetprep.multi.HelloWorldMultiTargetPreparer" />
+
+ <test class="com.android.tradefed.HelloWorldMultiDevices" />
+
+ <logger class="com.android.tradefed.log.FileLogger">
+ <option name="log-level" value="verbose" />
+ <option name="log-level-display" value="verbose" />
+ </logger>
+ <result_reporter class="com.android.tradefed.result.ConsoleResultReporter" />
+
+</configuration>
diff --git a/res/config/google/example/BT-discovery-sl4a.xml b/res/config/google/example/BT-discovery-sl4a.xml
new file mode 100644
index 0000000..20f8aa0
--- /dev/null
+++ b/res/config/google/example/BT-discovery-sl4a.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Bluetooth discovery test to test sl4a with multi-devices">
+
+ <device name="dut">
+ <build_provider class="com.android.tradefed.build.BootstrapBuildProvider" />
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="sl4a.apk" />
+ </target_preparer>
+ </device>
+
+ <device name="discoverer">
+ <build_provider class="com.android.tradefed.build.BootstrapBuildProvider" />
+ <target_preparer class="com.android.tradefed.targetprep.TestAppInstallSetup">
+ <option name="test-file-name" value="sl4a.apk" />
+ </target_preparer>
+ </device>
+
+ <logger class="com.android.tradefed.log.FileLogger">
+ <option name="log-level" value="VERBOSE" />
+ <option name="log-level-display" value="VERBOSE" />
+ </logger>
+
+ <test class="com.android.tradefed.Sl4aBluetoothDiscovery" />
+
+ <result_reporter class="com.android.tradefed.result.ConsoleResultReporter" />
+</configuration>
diff --git a/res/config/uicd/uiconductor-commandlineaction-sample.xml b/res/config/uicd/uiconductor-commandlineaction-sample.xml
new file mode 100644
index 0000000..df8f3ae
--- /dev/null
+++ b/res/config/uicd/uiconductor-commandlineaction-sample.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs the ui conductor test">
+
+ <!--These are the dependency apks that needs to be installed in the devices to run uicd successfully-->
+ <target_preparer class="com.android.tradefed.targetprep.InstallApkSetup">
+
+ <!--option apk-path can be repeated with apks that are needed for the device specific tests-->
+ <option name="apk-path" value="gs://uicd-deps/deps/apks/uicd-xmldumper-server-test-v1.0.2.apk" />
+ <option name="apk-path" value="gs://uicd-deps/deps/apks/uicd-xmldumper-server-v1.0.2.apk" />
+ </target_preparer>
+
+ <!--Provide the test related information here-->
+ <test class="com.android.uicd.tests.UiConductorTest" >
+
+ <!--This is the required uicd-cli jar for the uicd-tests to run-->
+ <option name="uicd-cli-jar" value="gs://uicd-deps/cli/uicd-commandline.jar" />
+
+ <!--mode of the tests. Can be SINGLE, MULTIDEVICE or PLAYALL. But only one at a time.-->
+ <option name="play-mode" value="SINGLE" />
+
+ <!--test file's identifier key and path which can be a local path or a GCS path(as below)-->
+ <option name="uicd-test" key="nuwa_python_test" value="gs://uicd-samples/tests/nuwa_python_test" />
+
+ <!--Path of the executable that is referenced in command line action-->
+ <option name="commandline-action-executable" value="gs://uicd-samples/executables/basic_nuwa_script" />
+ </test>
+
+ <logger class="com.android.tradefed.log.FileLogger" >
+ <option name="log-level-display" value="debug" />
+ </logger>
+
+
+</configuration>
\ No newline at end of file
diff --git a/res/config/uicd/uiconductor-globalvariable-sample.xml b/res/config/uicd/uiconductor-globalvariable-sample.xml
new file mode 100644
index 0000000..13277db
--- /dev/null
+++ b/res/config/uicd/uiconductor-globalvariable-sample.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs the ui conductor test">
+
+ <!--These are the dependency apks that needs to be installed in the devices to run uicd successfully-->
+ <target_preparer class="com.android.tradefed.targetprep.InstallApkSetup">
+ <!--option apk-path can be repeated with apks that are needed for the device specific tests-->
+ <option name="apk-path" value="gs://uicd-deps/deps/apks/uicd-xmldumper-server-test-v1.0.2.apk" />
+ <option name="apk-path" value="gs://uicd-deps/deps/apks/uicd-xmldumper-server-v1.0.2.apk" />
+ </target_preparer>
+
+ <!--Provide the test related information here-->
+ <test class="com.android.uicd.tests.UiConductorTest" >
+
+ <!--This is the required uicd-cli jar for the uicd-tests to run-->
+ <option name="uicd-cli-jar" value="gs://uicd-deps/cli/uicd-commandline.jar" />
+
+ <!--Mode of the tests. Can be SINGLE, MULTIDEVICE or PLAYALL. But only one at a time.-->
+ <option name="play-mode" value="SINGLE" />
+
+ <!--Global variables for a particular tests are identified through the xml keys-->
+ <!--Global variables are key value pairs separated by "=" in the xml values as shown below-->
+ <!--Multiple global variables can be repeated as shown below-->
+ <option name="global-variables" key="dummytest1" value="uicd_key1=value1Fordummytest1" />
+ <option name="global-variables" key="dummytest1" value="uicd_key2=value2Fordummytest1" />
+
+ <!--Global variable pairs can be grouped together separated by ","-->
+ <option name="global-variables" key="dummytest2" value="uicd_key1=value1Fordummytest2,uicd_key2=value2Fordummytest2" />
+
+ <!--test file's identifier key and path which can be a local path or a GCS path(as below)-->
+ <!--Global variables for dummytest1 is resolved as "uicd_key1=value1Fordummytest1,uicd_key2=value2Fordummytest1"-->
+ <option name="uicd-test" key="dummytest1" value="gs://uicd-samples/tests/tests_subdir1/dummytest1" />
+
+ <!--Global variables for dummytest2 is resolved as "uicd_key1=value1Fordummytest2,uicd_key2=value2Fordummytest2"-->
+ <option name="uicd-test" key="dummytest2" value="gs://uicd-samples/tests/tests_subdir1/tests_subdir2/dummytest2" />
+
+ <!--No global variables for hellothisworks-->
+ <option name="uicd-test" key="hellothisworks" value="gs://uicd-samples/tests/hellothisworks" />
+ </test>
+
+ <logger class="com.android.tradefed.log.FileLogger" >
+ <option name="log-level-display" value="debug" />
+ </logger>
+</configuration>
\ No newline at end of file
diff --git a/res/config/uicd/uiconductor-multidevice-sample.xml b/res/config/uicd/uiconductor-multidevice-sample.xml
new file mode 100644
index 0000000..070dafb
--- /dev/null
+++ b/res/config/uicd/uiconductor-multidevice-sample.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs the ui conductor multi-device test">
+
+ <!-- Each device configuration -->
+ <device name="device1">
+
+ <!--These are the dependency apks that needs to be installed in the devices to run uicd successfully-->
+ <target_preparer class="com.android.tradefed.targetprep.InstallApkSetup">
+
+ <!--option apk-path can be repeated with apks that are needed for the device specific tests-->
+ <option name="apk-path" value="gs://uicd-deps/deps/apks/uicd-xmldumper-server-test-v1.0.2.apk" />
+ <option name="apk-path" value="gs://uicd-deps/deps/apks/uicd-xmldumper-server-v1.0.2.apk" />
+ </target_preparer>
+ </device>
+
+ <!-- A device tag can be completely empty if it doesn't require any setup -->
+ <device name="device2">
+
+ <!--These are the dependency apks that needs to be installed in the devices to run uicd successfully-->
+ <target_preparer class="com.android.tradefed.targetprep.InstallApkSetup">
+
+ <!--option apk-path can be repeated with apks that are needed for the device specific tests-->
+ <option name="apk-path" value="gs://uicd-deps/deps/apks/uicd-xmldumper-server-test-v1.0.2.apk" />
+ <option name="apk-path" value="gs://uicd-deps/deps/apks/uicd-xmldumper-server-v1.0.2.apk" />
+ </target_preparer>
+ </device>
+
+ <!--Provide the test related information here-->
+ <test class="com.android.uicd.tests.UiConductorTest" >
+
+ <!--This is the required uicd-cli jar for the uicd-tests to run-->
+ <option name="uicd-cli-jar" value="gs://uicd-deps/cli/uicd-commandline.jar" />
+
+ <!--mode of the tests. Can be SINGLE, MULTIDEVICE or PLAYALL. But only one at a time.-->
+ <option name="play-mode" value="MULTIDEVICE" />
+
+ <!--Global variables for a particular tests are identified through the keys-->
+ <!--Global variables are key value pairs separated by "=" as shown below-->
+ <option name="global-variables" key="tracker" value="uicd_key1=value1Fortracker" />
+
+ <!--test file's identifier key and path which can be a local path or a GCS path(as below)-->
+ <option name="uicd-test" key="tracker" value="gs://uicd-samples/tests/tracker-1.2.1" />
+ </test>
+
+ <logger class="com.android.tradefed.log.FileLogger" >
+ <option name="log-level-display" value="debug" />
+ </logger>
+
+
+</configuration>
\ No newline at end of file
diff --git a/res/config/uicd/uiconductor-sample.xml b/res/config/uicd/uiconductor-sample.xml
new file mode 100644
index 0000000..f2e87cb
--- /dev/null
+++ b/res/config/uicd/uiconductor-sample.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<configuration description="Runs the ui conductor test">
+
+ <!--These are the dependency apks that needs to be installed in the devices to run uicd successfully-->
+ <target_preparer class="com.android.tradefed.targetprep.InstallApkSetup">
+
+ <!--option apk-path can be repeated with apks that are needed for the device specific tests-->
+ <option name="apk-path" value="gs://uicd-deps/deps/apks/uicd-xmldumper-server-test-v1.0.2.apk" />
+ <option name="apk-path" value="gs://uicd-deps/deps/apks/uicd-xmldumper-server-v1.0.2.apk" />
+ </target_preparer>
+
+ <!--Provide the test related information here-->
+ <test class="com.android.uicd.tests.UiConductorTest" >
+
+ <!--This is the required uicd-cli jar for the uicd-tests to run-->
+ <option name="uicd-cli-jar" value="gs://uicd-deps/cli/uicd-commandline.jar" />
+
+ <!--mode of the tests. Can be SINGLE, MULTIDEVICE or PLAYALL. But only one at a time.-->
+ <option name="play-mode" value="SINGLE" />
+
+ <!--test file's identifier key and path which can be a local path or a GCS path(as below)-->
+ <option name="uicd-test" key="dummytest1" value="gs://uicd-samples/tests/tests_subdir1/dummytest1" />
+ <option name="uicd-test" key="dummytest2" value="gs://uicd-samples/tests/tests_subdir1/tests_subdir2/dummytest2" />
+ <option name="uicd-test" key="hellothisworks" value="gs://uicd-samples/tests/hellothisworks" />
+ </test>
+
+ <logger class="com.android.tradefed.log.FileLogger" >
+ <option name="log-level-display" value="debug" />
+ </logger>
+
+
+</configuration>
\ No newline at end of file
diff --git a/src/com/android/build/tests/KernelImageCheck.java b/src/com/android/build/tests/KernelImageCheck.java
new file mode 100644
index 0000000..b4d5248
--- /dev/null
+++ b/src/com/android/build/tests/KernelImageCheck.java
@@ -0,0 +1,134 @@
+/*
+ * 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.build.tests;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.RunUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.IOException;
+
+/** A device-less test that test kernel image */
+@OptionClass(alias = "kernel-image-check")
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class KernelImageCheck extends BaseHostJUnit4Test {
+
+ private static final String KERNEL_IMAGE_NAME = "vmlinux";
+ private static final int CMD_TIMEOUT = 1000000;
+
+ @Option(
+ name = "kernel-image-check-tool",
+ description = "The file path of kernel image check tool (mandatory)",
+ mandatory = true
+ )
+ private File mKernelImageCheckTool = null;
+
+ @Option(
+ name = "kernel-image-name",
+ description = "The file name of the kernel image. Default: vmlinux"
+ )
+ private String mKernelImageName = KERNEL_IMAGE_NAME;
+
+ @Option(
+ name = "kernel-image-alt-path",
+ description = "The kernel image alternative path string"
+ )
+ private String mKernelImageAltPath = null;
+
+ @Option(
+ name = "kernel-abi-file",
+ description = "The file path of kernel ABI file",
+ mandatory = true
+ )
+ private File mKernelAbiFile = null;
+
+ private IBuildInfo mBuildInfo;
+ private File mKernelImageFile = null;
+
+ @Before
+ public void setUp() throws Exception {
+ if (!mKernelImageCheckTool.exists()) {
+ throw new IOException("Cannot find kernel image tool at: " + mKernelImageCheckTool);
+ }
+ if (!mKernelAbiFile.exists()) {
+ throw new IOException("Cannot find kernel ABI representation at: " + mKernelAbiFile);
+ }
+ // First try to get kernel image from BuildInfo
+ mKernelImageFile = getBuild().getFile(mKernelImageName);
+ if (mKernelImageFile == null || !mKernelImageFile.exists()) {
+ // Then check within alternative path.
+ File imageDir = new File(mKernelImageAltPath);
+ if (imageDir.isDirectory()) {
+ mKernelImageFile = new File(imageDir, mKernelImageName);
+ }
+ }
+
+ if (mKernelImageFile == null || !mKernelImageFile.exists()) {
+ throw new RuntimeException("Cannot find kernel image file: " + mKernelImageName);
+ }
+ }
+
+ /** Test that kernel ABI is not different from the given ABI representation */
+ @Test
+ public void test_stable_abi() throws Exception {
+ // Generate kernel ABI
+ String[] cmd =
+ new String[] {
+ mKernelImageCheckTool.getAbsolutePath() + "/abidw",
+ "--linux-tree",
+ mKernelImageFile.getParent(),
+ "--out-file",
+ "abi-new.out"
+ };
+ CommandResult result = RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT, cmd);
+ CLog.i("Result stdout: %s", result.getStdout());
+ // TODO: differentiate non-zero exit codes.
+ if (result.getExitCode() != 0) {
+ CLog.e("Result stderr: %s", result.getStderr());
+ CLog.e("Result exit code: %d", result.getExitCode());
+ }
+ assertEquals(CommandStatus.SUCCESS, result.getStatus());
+
+ // Diff kernel ABI with the given ABI file
+ cmd =
+ new String[] {
+ mKernelImageCheckTool.getAbsolutePath() + "/abidiff",
+ "abi-new.out",
+ mKernelAbiFile.getAbsolutePath()
+ };
+ result = RunUtil.getDefault().runTimedCmd(CMD_TIMEOUT, cmd);
+ CLog.i("Result stdout: %s", result.getStdout());
+ if (result.getExitCode() != 0) {
+ CLog.e("Result stderr: %s", result.getStderr());
+ CLog.e("Result exit code: %d", result.getExitCode());
+ }
+ assertEquals(CommandStatus.SUCCESS, result.getStatus());
+ }
+}
diff --git a/src/com/android/tradefed/presubmit/TestMappingsValidation.java b/src/com/android/tradefed/presubmit/TestMappingsValidation.java
index d9912f4..408f136 100644
--- a/src/com/android/tradefed/presubmit/TestMappingsValidation.java
+++ b/src/com/android/tradefed/presubmit/TestMappingsValidation.java
@@ -31,6 +31,7 @@
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -69,6 +70,9 @@
private static final String LOCAL_COMPATIBILITY_SUITES = "compatibility_suites";
private static final String GENERAL_TESTS = "general-tests";
private static final String DEVICE_TESTS = "device-tests";
+ // Only Check the tests with group in presubmit or postsubmit.
+ private static final Set<String> TEST_GROUPS_TO_VALIDATE =
+ new HashSet<>(Arrays.asList("presubmit", "postsubmit"));
private File testMappingsDir = null;
private IDeviceBuildInfo deviceBuildInfo = null;
@@ -115,8 +119,12 @@
public void testTestSuiteSetting() throws JSONException {
List<String> errors = new ArrayList<>();
for (String testGroup : allTests.keySet()) {
+ if (!TEST_GROUPS_TO_VALIDATE.contains(testGroup)) {
+ CLog.d("Skip checking tests with group: %s", testGroup);
+ continue;
+ }
for (TestInfo testInfo : allTests.get(testGroup)) {
- if (!validateSuiteSetting(testInfo.getName())) {
+ if (!validateSuiteSetting(testInfo.getName(), testInfo.getKeywords())) {
errors.add(
String.format(
"Missing test_suite setting for test: %s, test group: %s, " +
@@ -242,11 +250,17 @@
* Validate if the name exists in module-info.json and with the correct suite setting.
*
* @param name A {@code String} name of the test.
+ * @param keywords A {@code Set<String>} keywords of the test.
* @return true if name exists in module-info.json and matches either "general-tests" or
- * "device-tests".
+ * "device-tests", or name doesn't exist but has keywords attribute set.
*/
- private boolean validateSuiteSetting(String name) throws JSONException {
+ private boolean validateSuiteSetting(String name, Set<String> keywords) throws JSONException {
if (!moduleInfo.has(name)) {
+ if (!keywords.isEmpty()) {
+ CLog.d("Test Module: %s can't be found in module-info.json, but it has " +
+ "keyword setting. Ignore checking...", name);
+ return true;
+ }
CLog.w("Test Module: %s can't be found in module-info.json.", name);
return false;
}
diff --git a/src/com/android/uicd/tests/UiConductorTest.java b/src/com/android/uicd/tests/UiConductorTest.java
new file mode 100644
index 0000000..4fa9ebe
--- /dev/null
+++ b/src/com/android/uicd/tests/UiConductorTest.java
@@ -0,0 +1,368 @@
+/*
+ * 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[] {});
+ }
+}