diff options
author | Jinhui Wang <jinhuiw@google.com> | 2016-06-20 19:42:59 -0700 |
---|---|---|
committer | Jinhui Wang <jinhuiw@google.com> | 2016-06-21 09:45:49 -0700 |
commit | 8c28a9831d53a17178c4f3acd96ff992eef2eb77 (patch) | |
tree | 1c827bcfdd656f8a1790943b59540f8adf06507d | |
parent | 2e8c262068ccf34de556967679083d72626c7876 (diff) | |
download | AfwTestHarness-8c28a9831d53a17178c4f3acd96ff992eef2eb77.tar.gz |
Import tools/tradefed module
Change-Id: I511b95fece32050614e5b9ab14e210e8c642350d
38 files changed, 3543 insertions, 0 deletions
diff --git a/tools/Android.mk b/tools/Android.mk new file mode 100644 index 0000000..c31684f --- /dev/null +++ b/tools/Android.mk @@ -0,0 +1,33 @@ +# +# Copyright (C) 2016 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. +# + +AFW_TEST_TF_JAR := $(HOST_OUT_JAVA_LIBRARIES)/afw-test-tradefed.jar +AFW_TEST_TF_EXEC_PATH ?= $(HOST_OUT_EXECUTABLES)/afw-test-tradefed + +afw_test_prebuilt_jar := $(HOST_OUT)/afw-test/android-cts/tools/afw-test-prebuilt.jar +$(afw_test_prebuilt_jar): PRIVATE_TESTS_DIR := $(HOST_OUT)/afw-test/android-cts/repository/testcases +$(afw_test_prebuilt_jar): PRIVATE_PLANS_DIR := $(HOST_OUT)/afw-test/android-cts/repository/plans +$(afw_test_prebuilt_jar): PRIVATE_TOOLS_DIR := $(HOST_OUT)/afw-test/android-cts/tools +$(afw_test_prebuilt_jar): $(TF_JAR) $(AFW_TEST_TF_JAR) $(AFW_TEST_TF_EXEC_PATH) | $(ACP) $(HOST_OUT_EXECUTABLES)/adb + mkdir -p $(PRIVATE_TESTS_DIR) + mkdir -p $(PRIVATE_PLANS_DIR) + mkdir -p $(PRIVATE_TOOLS_DIR) + $(ACP) -fp $(TF_JAR) $(AFW_TEST_TF_JAR) $(AFW_TEST_TF_EXEC_PATH) $(PRIVATE_TOOLS_DIR) + +.PHONY: afw-test-tools +afw-test-tools : $(afw_test_prebuilt_jar) + +include $(call all-subdir-makefiles) diff --git a/tools/prebuilt/CtsDeviceInfo.apk b/tools/prebuilt/CtsDeviceInfo.apk Binary files differnew file mode 100644 index 0000000..c245c81 --- /dev/null +++ b/tools/prebuilt/CtsDeviceInfo.apk diff --git a/tools/prebuilt/TestDeviceSetup.apk b/tools/prebuilt/TestDeviceSetup.apk Binary files differnew file mode 100644 index 0000000..379a34b --- /dev/null +++ b/tools/prebuilt/TestDeviceSetup.apk diff --git a/tools/tradefed-host/Android.mk b/tools/tradefed-host/Android.mk new file mode 100644 index 0000000..01295cb --- /dev/null +++ b/tools/tradefed-host/Android.mk @@ -0,0 +1,38 @@ +# +# Copyright (C) 2016 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) + +LOCAL_JAVA_RESOURCE_DIRS := res + +LOCAL_MODULE := afw-test-tradefed + +LOCAL_MODULE_TAGS := optional +LOCAL_JAVA_LIBRARIES := tradefed-prebuilt +LOCAL_STATIC_JAVA_LIBRARIES := cts-tradefed + +LOCAL_ADDITIONAL_DEPENDENCIES := $(LOCAL_PATH)/Android.mk + +LOCAL_JAR_MANIFEST := MANIFEST.mf + +include $(BUILD_HOST_JAVA_LIBRARY) + +# Build all sub-directories +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/tradefed-host/MANIFEST.mf b/tools/tradefed-host/MANIFEST.mf new file mode 100644 index 0000000..5528c06 --- /dev/null +++ b/tools/tradefed-host/MANIFEST.mf @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: com.android.cts.tradefed.testtype +Implementation-Version: %BUILD_NUMBER% diff --git a/tools/tradefed-host/README b/tools/tradefed-host/README new file mode 100644 index 0000000..25bffb6 --- /dev/null +++ b/tools/tradefed-host/README @@ -0,0 +1,50 @@ +Android For Work (Afw) Test Trade Federation +------------------------------------------- + +AfW Test Trade Federation, afw-test-tradefed for short, is the built on top +of the cts-tradefed test harness. + +It works in a similar manner to cts-tradefed harness but with customized +features for AFW testing automation, such as customized device preparers. + +Configuring afw-test-tradefed +---------------------------- + +1. Ensure 'adb' is in your current PATH. adb can be found in the +Android SDK available from http://developer.android.com + +Example: + PATH=$PATH:/home/myuser/android-sdk-linux_x86/platform-tools + +2. Connect the device to the host machine. + +3. Ensure device is visible via 'adb devices' + +Using afw-test-tradefed +---------------------- + +To run a test plan on a single device: + +1. Make sure you have at least one device connected. +2. Launch the afw-test-tradefed console by running the 'afw-test-tradefed'. + The "afw-test-tradefed' script can be found at android-cts/tools/afw-test-tradefed + from the android-afw-test.zip you downloaded. + +3. Type: 'run cts --plan afw-userdebug-build' to run test plan afw-userdebug-build. + + Type 'list plans' to find all test plans. + +Some other useful commands are: + +To run a test package: +'run cts --package <packagename>' + +To run a test class: +'run cts --class <full test class name>' + +To shard a plan test run on multiple devices +'run cts --plan afw --shards <number of shards> +note: all connected devices must be running the same build + +For more options: +'run cts --help' diff --git a/tools/tradefed-host/etc/Android.mk b/tools/tradefed-host/etc/Android.mk new file mode 100644 index 0000000..3d0920e --- /dev/null +++ b/tools/tradefed-host/etc/Android.mk @@ -0,0 +1,26 @@ +# +# Copyright (C) 2016 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. +# + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) + +LOCAL_MODULE_TAGS := optional + +LOCAL_PREBUILT_EXECUTABLES := afw-test-tradefed + +include $(BUILD_HOST_PREBUILT) + diff --git a/tools/tradefed-host/etc/afw-test-tradefed b/tools/tradefed-host/etc/afw-test-tradefed new file mode 100755 index 0000000..c04c875 --- /dev/null +++ b/tools/tradefed-host/etc/afw-test-tradefed @@ -0,0 +1,102 @@ +#!/bin/bash +# +# Copyright (C) 2016 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. +# + +# launcher script for afw-test-tradefed harness +# Can be used from an Android build environment, or a standalone android-afw-test zip + +checkFile() { + if [ ! -f "$1" ]; then + echo "Unable to locate $1" + exit + fi; +} + +checkPath() { + if ! type -P $1 &> /dev/null; then + echo "Unable to find $1 in path." + exit + fi; +} + +checkPath adb +checkPath java + +# check java version +JAVA_VERSION=$(java -version 2>&1 | head -n 2 | grep '[ "]1\.[78][\. "$$]') +if [ "${JAVA_VERSION}" == "" ]; then + echo "Wrong java version. 1.7 or 1.8 is required." + exit +fi + +# check debug flag and set up remote debugging +if [ -n "${TF_DEBUG}" ]; then + if [ -z "${TF_DEBUG_PORT}" ]; then + TF_DEBUG_PORT=10088 + fi + RDBG_FLAG=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=${TF_DEBUG_PORT} +fi + +# get OS +HOST=`uname` +if [ "$HOST" == "Linux" ]; then + OS="linux-x86" +elif [ "$HOST" == "Darwin" ]; then + OS="darwin-x86" +else + echo "Unrecognized OS" + exit +fi + +# check if in Android build env +if [ ! -z "${ANDROID_BUILD_TOP}" ]; then + if [ ! -z "${ANDROID_HOST_OUT}" ]; then + CTS_ROOT=${ANDROID_HOST_OUT}/afw-test + else + CTS_ROOT=${ANDROID_BUILD_TOP}/${OUT_DIR:-out}/host/${OS}/afw-test + fi + if [ ! -d ${CTS_ROOT} ]; then + echo "Could not find $CTS_ROOT in Android build environment. Try 'make afw-test-harness'" + exit + fi; +fi; + +if [ -z ${CTS_ROOT} ]; then + # assume we're in an extracted afw-test install + CTS_ROOT="$(dirname $0)/../.." +fi; + +JAR_DIR=${CTS_ROOT}/android-cts/tools +JARS="tradefed-prebuilt.jar afw-test-tradefed.jar" + +for JAR in $JARS; do + checkFile ${JAR_DIR}/${JAR} + JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR} +done + +# load any shared libraries for host-side executables +LIB_DIR=${CTS_ROOT}/android-cts/lib +if [ "$HOST" == "Linux" ]; then + LD_LIBRARY_PATH=${LIB_DIR}:${LIB_DIR}64:${LD_LIBRARY_PATH} + export LD_LIBRARY_PATH +elif [ "$HOST" == "Darwin" ]; then + DYLD_LIBRARY_PATH=${LIB_DIR}:${LIB_DIR}64:${DYLD_LIBRARY_PATH} + export DYLD_LIBRARY_PATH +fi + +java $RDBG_FLAG \ + -cp ${JAR_PATH} -DCTS_ROOT=${CTS_ROOT} com.android.cts.tradefed.command.CtsConsole "$@" + diff --git a/tools/tradefed-host/res/config/afw-test-common.xml b/tools/tradefed-host/res/config/afw-test-common.xml new file mode 100644 index 0000000..b32545a --- /dev/null +++ b/tools/tradefed-host/res/config/afw-test-common.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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="Common device setup configuration"> + + <!-- General device setup. --> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <!-- Disable heads up notifications. --> + <option name="run-command" value="settings put global heads_up_notifications_enabled 0"/> + <!-- Skip encryption, works for L only; for M+ it's disabled in nfc bump. --> + <option name="run-command" value="setprop persist.sys.no_req_encrypt true"/> + <!-- Set device screen always on. --> + <option name="run-command" value="svc power stayon true"/> + <!-- Disable package verification --> + <option name="run-command" value="settings put global package_verifier_enable 0" /> + </target_preparer> + +</configuration> diff --git a/tools/tradefed-host/res/config/afw-test-factory-reset.xml b/tools/tradefed-host/res/config/afw-test-factory-reset.xml new file mode 100644 index 0000000..5081af2 --- /dev/null +++ b/tools/tradefed-host/res/config/afw-test-factory-reset.xml @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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="Factory reset configuration"> + + <!-- Install privileged apps --> + <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestPrivAppInstaller"> + <option name="app-filename" value="AfwThSystemUtil.apk"/> + </target_preparer> + + <!-- Remove Google accounts before and after test to avoid FRP --> + <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestGoogleAccountRemover"/> + + <!-- Disable package verification so that AfwThDeviceAdmin can be installed--> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <option name="run-command" value="settings put global package_verifier_enable 0" /> + </target_preparer> + + <!-- Install device admin app --> + <target_preparer class="com.android.cts.tradefed.targetprep.CtsApkInstaller"> + <option name="test-file-name" value="AfwThDeviceAdmin.apk" /> + </target_preparer> + + <!-- Set device admin --> + <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> + <!-- 'dpm set-active-admin' requires Android min Sdk Version 22 --> + <option name="run-command" value="dpm set-active-admin com.android.afwtest.deviceadmin/com.android.afwtest.deviceadmin.AdminReceiver"/> + </target_preparer> + + <!-- Factory reset. --> + <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestFactoryReset"/> + +</configuration> diff --git a/tools/tradefed-host/res/config/afw-test-push-test-suite-config.xml b/tools/tradefed-host/res/config/afw-test-push-test-suite-config.xml new file mode 100644 index 0000000..32ad213 --- /dev/null +++ b/tools/tradefed-host/res/config/afw-test-push-test-suite-config.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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="Push the test suite configuration file: afw-test.props"> + + <!-- Push test suite configuration file --> + <target_preparer class="com.android.cts.tradefed.targetprep.CtsFilePusher"> + <option name="push" value="afw-test.props->/data/local/tmp/afw-test.props"/> + <option name="cleanup" value="true"/> + </target_preparer> + +</configuration> diff --git a/tools/tradefed-host/res/config/afw-test-remove-users.xml b/tools/tradefed-host/res/config/afw-test-remove-users.xml new file mode 100644 index 0000000..bc6c8db --- /dev/null +++ b/tools/tradefed-host/res/config/afw-test-remove-users.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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="Remove non-primary users."> + + <!-- Remove all non-primary users before and after the test. --> + <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestUserRemover"> + <option name="remove-users-before-test" value="true"/> + <option name="remove-users-after-test" value="true"/> + </target_preparer> + +</configuration> diff --git a/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-after-test.xml b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-after-test.xml new file mode 100644 index 0000000..db392d2 --- /dev/null +++ b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-after-test.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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="Uninstall TestDpc after the test"> + + <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestAppUninstaller" > + <option name="after-test" value="com.afwsamples.testdpc"/> + <option name="reboot-before-uninstall" value="true"/> + </target_preparer> + +</configuration> diff --git a/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-and-reset-users.xml b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-and-reset-users.xml new file mode 100644 index 0000000..43227f8 --- /dev/null +++ b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-and-reset-users.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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="Uninstall TestDpc and reset users, before and after the test"> + + <!-- Uninstall TestDpc after the test. --> + <include name="afw-test-uninstall-testdpc-after-test"/> + + <!-- Remove all non-primary users before and after the test. --> + <include name="afw-test-remove-users"/> + + <!-- Uninstall TestDpc before the test. --> + <include name="afw-test-uninstall-testdpc-before-test"/> + +</configuration> diff --git a/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-before-test.xml b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-before-test.xml new file mode 100644 index 0000000..5f25742 --- /dev/null +++ b/tools/tradefed-host/res/config/afw-test-uninstall-testdpc-before-test.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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="Uninstall TestDpc before the test"> + + <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestAppUninstaller" > + <option name="before-test" value="com.afwsamples.testdpc"/> + <option name="reboot-before-uninstall" value="true"/> + </target_preparer> + +</configuration> diff --git a/tools/tradefed-host/res/config/afw-test-wifi.xml b/tools/tradefed-host/res/config/afw-test-wifi.xml new file mode 100644 index 0000000..962aec3 --- /dev/null +++ b/tools/tradefed-host/res/config/afw-test-wifi.xml @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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="Connect to wifi network"> + + <target_preparer class="com.android.afwtest.tradefed.targetprep.AfwTestWifiPreparer" > + <option name="wifi-config-file" value="afw-test.props"/> + <option name="wifi-ssid-key" value="wifi_ssid"/> + <option name="wifi-password-key" value="wifi_password"/> + <option name="disconnect-wifi-after-test" value="false"/> + </target_preparer> + +</configuration> diff --git a/tools/tradefed-host/res/config/cts.xml b/tools/tradefed-host/res/config/cts.xml new file mode 100644 index 0000000..ebe5040 --- /dev/null +++ b/tools/tradefed-host/res/config/cts.xml @@ -0,0 +1,39 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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 a test plan from Afw Test Harness installation"> + + <build_provider class="com.android.cts.tradefed.build.CtsBuildProvider" /> + <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" > + <option name="device-wait-time" value="480000"/> + <option name="bootloader-wait-time" value="60000"/> + <option name="shell-wait-time" value="60000"/> + </device_recovery> + <test class="com.android.afwtest.tradefed.testtype.AfwTest"> + <option name="disable-reboot" value="true"/> + <option name="screenshot-on-failure" value="true"/> + <option name="screenshot" value="true"/> + <option name="bugreport" value="true"/> + </test> + <logger class="com.android.tradefed.log.FileLogger" /> + <result_reporter class="com.android.cts.tradefed.result.CtsXmlResultReporter" /> + <result_reporter class="com.android.cts.tradefed.result.CtsTestLogReporter" /> + + <!-- Retry logic is overwritten in AfwTestWifiPreparer. Please use its wifi-attempts + option directly. --> + <option name="wifi-attempts" value="1" /> + <option name="wifi-retry-wait-time" value="30000" /> +</configuration> diff --git a/tools/tradefed-host/res/report/cts_result.xsl b/tools/tradefed-host/res/report/cts_result.xsl new file mode 100644 index 0000000..d370ca4 --- /dev/null +++ b/tools/tradefed-host/res/report/cts_result.xsl @@ -0,0 +1,612 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2016 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. +--> + +<!DOCTYPE xsl:stylesheet [ <!ENTITY nbsp " "> ]> +<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> + <xsl:output method="html" version="1.0" encoding="UTF-8" indent="yes"/> + + <xsl:template match="/"> + + <html> + <head> + <title>Test Report for <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_model" /> - <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@deviceID"/></title> + <script> + function toggle(id) { + e = document.getElementById(id) + e.style.display = e.style.display == "none" ? "block" : "none" + } + </script> + <STYLE type="text/css"> + @import "cts_result.css"; + </STYLE> + </head> + <body> + <DIV> + <TABLE class="title"> + <TR> + <TD width="40%" align="left"><img src="logo.gif"></img></TD> + <TD width="60%" align="left"> + <h1>Test Report for <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_model"/> - + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@deviceID"/> + </h1> + </TD> + </TR> + </TABLE> + </DIV> + <img src="newrule-green.png" align="left"></img> + + <br></br> + + <center> + <a href="#" onclick="toggle('summary');">Show Device Information</a> + </center> + + <br></br> + + <DIV id="summary" style="display: none"> + <TABLE class="summary"> + <TR> + <TH colspan="2">Device Information</TH> + </TR> + <TR> + <TD width="50%"> + <!-- Device information --> + <TABLE> + <TR> + <TD class="rowtitle">Build Model</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_model"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Build Product</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@buildName"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Build Brand</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_brand"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Build Manufacturer</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_manufacturer"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Device ID</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@deviceID"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Android Version</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@buildVersion"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Build ID</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@buildID"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Build Fingerprint</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@build_fingerprint"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Build ABI</TD> + <TD> + <xsl:value-of + select="TestResult/DeviceInfo/BuildInfo/@build_abi"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Build ABI2</TD> + <TD> + <xsl:value-of + select="TestResult/DeviceInfo/BuildInfo/@build_abi2"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Android API Level</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@androidPlatformVersion"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Supported Locales</TD> + <TD> + <xsl:call-template name="formatDelimitedString"> + <xsl:with-param name="string" select="TestResult/DeviceInfo/BuildInfo/@locales"/> + </xsl:call-template> + </TD> + </TR> + <TR> + <TD class="rowtitle">Screen Size</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/Screen/@screen_size"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Resolution</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/Screen/@resolution"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Density</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/Screen/@screen_density"/> + (<xsl:value-of select="TestResult/DeviceInfo/Screen/@screen_density_bucket"/>) + </TD> + </TR> + <TR> + <TD class="rowtitle">Phone number</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/PhoneSubInfo/@subscriberId"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">X dpi</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@Xdpi"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Y dpi</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@Ydpi"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Touch</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@touch"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Navigation</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@navigation"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Keypad</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@keypad"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Network</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@network"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">IMEI</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@imei"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">IMSI</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@imsi"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Open GL ES Version</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@openGlEsVersion"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Open GL Compressed Texture Formats</TD> + <TD> + <UL> + <xsl:for-each select="TestResult/DeviceInfo/OpenGLCompressedTextureFormatsInfo/TextureFormat"> + <LI><xsl:value-of select="@name" /></LI> + </xsl:for-each> + </UL> + </TD> + </TR> + <TR> + <TD class="rowtitle">Root Processes</TD> + <TD> + <UL> + <xsl:for-each select="TestResult/DeviceInfo/ProcessInfo/Process[@uid='0']"> + <LI><xsl:value-of select="@name" /></LI> + </xsl:for-each> + </UL> + </TD> + </TR> + + </TABLE> + </TD> + + <TD width="50%"> + <TABLE> + + <TR> + <TD class="rowtitle">Features</TD> + <TD> + <xsl:for-each select="TestResult/DeviceInfo/FeatureInfo/Feature[@type='sdk']"> + <xsl:text>[</xsl:text> + <xsl:choose> + <xsl:when test="@available = 'true'"> + <xsl:text>X</xsl:text> + </xsl:when> + <xsl:otherwise> + <xsl:text>_</xsl:text> + </xsl:otherwise> + </xsl:choose> + <xsl:text>] </xsl:text> + + <xsl:value-of select="@name" /> + <br /> + </xsl:for-each> + </TD> + </TR> + <TR> + <TD class="rowtitle">Other Features</TD> + <TD> + <UL> + <xsl:for-each select="TestResult/DeviceInfo/FeatureInfo/Feature[@type='other']"> + <LI><xsl:value-of select="@name" /></LI> + </xsl:for-each> + </UL> + </TD> + </TR> + <TR> + <TD class="rowtitle">System Libraries</TD> + <TD> + <UL> + <xsl:for-each select="TestResult/DeviceInfo/SystemLibrariesInfo/Library"> + <LI><xsl:value-of select="@name" /></LI> + </xsl:for-each> + </UL> + </TD> + </TR> + <TR> + <TD class="rowtitle">Partitions</TD> + <TD> + <pre> + <xsl:call-template name="formatDelimitedString"> + <xsl:with-param name="string" select="TestResult/DeviceInfo/BuildInfo/@partitions" /> + <xsl:with-param name="numTokensPerRow" select="1" /> + </xsl:call-template> + </pre> + </TD> + </TR> + <TR> + <TD class="rowtitle">Storage devices</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@storage_devices"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Multi-user support</TD> + <TD> + <xsl:value-of select="TestResult/DeviceInfo/BuildInfo/@multi_user"/> + </TD> + </TR> + </TABLE> + </TD> + </TR> + </TABLE> + <br /> + <br /> + </DIV> + + <DIV> + <TABLE class="summary"> + <TR> + <TH colspan="2">Test Summary</TH> + </TR> + <TR> + <TD class="rowtitle">Afw Test Harness Version</TD> + <TD> + <xsl:value-of select="1.4"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Test timeout</TD> + <TD> + <xsl:value-of select="TestResult/HostInfo/Cts/IntValue[@name='testStatusTimeoutMs']/@value" /> ms + </TD> + </TR> + <TR> + <TD class="rowtitle">Host Info</TD> + <TD> + <xsl:value-of select="TestResult/HostInfo/@name"/> + (<xsl:value-of select="TestResult/HostInfo/Os/@name"/> - <xsl:value-of select="TestResult/HostInfo/Os/@version"/>) + </TD> + </TR> + <TR> + <TD class="rowtitle">Plan name</TD> + <TD> + <xsl:value-of select="TestResult/@testPlan"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Start time</TD> + <TD> + <xsl:value-of select="TestResult/@starttime"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">End time</TD> + <TD> + <xsl:value-of select="TestResult/@endtime"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Tests Passed</TD> + <TD> + <xsl:value-of select="TestResult/Summary/@pass"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Tests Failed</TD> + <TD> + <xsl:value-of select="TestResult/Summary/@failed"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Tests Timed out</TD> + <TD> + <xsl:value-of select="TestResult/Summary/@timeout"/> + </TD> + </TR> + <TR> + <TD class="rowtitle">Tests Not Executed</TD> + <TD> + <xsl:value-of select="TestResult/Summary/@notExecuted"/> + </TD> + </TR> + </TABLE> + </DIV> + + <!-- High level summary of test execution --> + <h2 align="center">Test Summary by Package</h2> + <DIV> + <TABLE class="testsummary"> + <TR> + <TH>Test Package</TH> + <TH>Passed</TH> + <TH>Failed</TH> + <TH>Timed Out</TH> + <TH>Not Executed</TH> + <TH>Total Tests</TH> + </TR> + <xsl:for-each select="TestResult/TestPackage"> + <TR> + <TD> + <xsl:variable name="href"><xsl:value-of select="@appPackageName"/></xsl:variable> + <a href="#{$href}"><xsl:value-of select="@appPackageName"/></a> + </TD> + <TD> + <xsl:value-of select="count(TestSuite//Test[@result = 'pass'])"/> + </TD> + <TD> + <xsl:value-of select="count(TestSuite//Test[@result = 'fail'])"/> + </TD> + <TD> + <xsl:value-of select="count(TestSuite//Test[@result = 'timeout'])"/> + </TD> + <TD> + <xsl:value-of select="count(TestSuite//Test[@result = 'notExecuted'])"/> + </TD> + <TD> + <xsl:value-of select="count(TestSuite//Test)"/> + </TD> + </TR> + </xsl:for-each> <!-- end package --> + </TABLE> + </DIV> + + <xsl:call-template name="filteredResultTestReport"> + <xsl:with-param name="header" select="'Test Failures'" /> + <xsl:with-param name="resultFilter" select="'fail'" /> + </xsl:call-template> + + <xsl:call-template name="filteredResultTestReport"> + <xsl:with-param name="header" select="'Test Timeouts'" /> + <xsl:with-param name="resultFilter" select="'timeout'" /> + </xsl:call-template> + + <h2 align="center">Detailed Test Report</h2> + <xsl:call-template name="detailedTestReport" /> + + </body> + </html> + </xsl:template> + + <xsl:template name="filteredResultTestReport"> + <xsl:param name="header" /> + <xsl:param name="resultFilter" /> + <xsl:variable name="numMatching" select="count(TestResult/TestPackage/TestSuite//TestCase/Test[@result=$resultFilter])" /> + <xsl:if test="$numMatching > 0"> + <h2 align="center"><xsl:value-of select="$header" /> (<xsl:value-of select="$numMatching"/>)</h2> + <xsl:call-template name="detailedTestReport"> + <xsl:with-param name="resultFilter" select="$resultFilter"/> + </xsl:call-template> + </xsl:if> + </xsl:template> + + <xsl:template name="detailedTestReport"> + <xsl:param name="resultFilter" /> + <DIV> + <xsl:for-each select="TestResult/TestPackage"> + <xsl:if test="$resultFilter='' + or count(TestSuite//TestCase/Test[@result=$resultFilter]) > 0"> + + <TABLE class="testdetails"> + <TR> + <TD class="package" colspan="3"> + <xsl:variable name="href"><xsl:value-of select="@appPackageName"/></xsl:variable> + <a name="{$href}">Compatibility Test Package: <xsl:value-of select="@appPackageName"/> + <xsl:if test="@abi"> + ABI: <xsl:value-of select="@abi"/> + </xsl:if> + </a> + </TD> + </TR> + + <TR> + <TH width="30%">Test</TH> + <TH width="5%">Result</TH> + <TH>Details</TH> + </TR> + + <!-- test case --> + <xsl:for-each select="TestSuite//TestCase"> + + <xsl:if test="$resultFilter='' or count(Test[@result=$resultFilter]) > 0"> + <!-- emit a blank row before every test suite name --> + <xsl:if test="position()!=1"> + <TR><TD class="testcasespacer" colspan="3"></TD></TR> + </xsl:if> + + <TR> + <TD class="testcase" colspan="3"> + <xsl:for-each select="ancestor::TestSuite"> + <xsl:if test="position()!=1">.</xsl:if> + <xsl:value-of select="@name"/> + </xsl:for-each> + <xsl:text>.</xsl:text> + <xsl:value-of select="@name"/> + </TD> + </TR> + </xsl:if> + + <!-- test --> + <xsl:for-each select="Test"> + <xsl:if test="$resultFilter='' or $resultFilter=@result"> + <TR> + <TD class="testname"> -- <xsl:value-of select="@name"/></TD> + + <!-- test results --> + <xsl:choose> + <xsl:when test="string(@KnownFailure)"> + <!-- "pass" indicates the that test actually passed (results have been inverted already) --> + <xsl:if test="@result='pass'"> + <TD class="pass"> + <div style="text-align: center; margin-left:auto; margin-right:auto;"> + Known problem + </div> + </TD> + <TD class="failuredetails"></TD> + </xsl:if> + + <!-- "fail" indicates that a known failure actually passed (results have been inverted already) --> + <xsl:if test="@result='fail'"> + <TD class="failed"> + <div style="text-align: center; margin-left:auto; margin-right:auto;"> + <xsl:value-of select="@result"/> + </div> + </TD> + <TD class="failuredetails"> + <div class="details"> + A test that was a known failure actually passed. Please check. + </div> + </TD> + </xsl:if> + </xsl:when> + + <xsl:otherwise> + <xsl:if test="@result='pass'"> + <TD class="pass"> + <div style="text-align: center; margin-left:auto; margin-right:auto;"> + <xsl:value-of select="@result"/> + </div> + </TD> + <TD class="failuredetails"/> + </xsl:if> + + <xsl:if test="@result='fail'"> + <TD class="failed"> + <div style="text-align: center; margin-left:auto; margin-right:auto;"> + <xsl:value-of select="@result"/> + </div> + </TD> + <TD class="failuredetails"> + <div class="details"> + <xsl:value-of select="FailedScene/@message"/> + </div> + </TD> + </xsl:if> + + <xsl:if test="@result='timeout'"> + <TD class="timeout"> + <div style="text-align: center; margin-left:auto; margin-right:auto;"> + <xsl:value-of select="@result"/> + </div> + <TD class="failuredetails"></TD> + </TD> + </xsl:if> + + <xsl:if test="@result='notExecuted'"> + <TD class="notExecuted"> + <div style="text-align: center; margin-left:auto; margin-right:auto;"> + <xsl:value-of select="@result"/> + </div> + </TD> + <TD class="failuredetails"></TD> + </xsl:if> + </xsl:otherwise> + </xsl:choose> + </TR> <!-- finished with a row --> + </xsl:if> + </xsl:for-each> <!-- end test --> + </xsl:for-each> <!-- end test case --> + </TABLE> + </xsl:if> + </xsl:for-each> <!-- end test package --> + </DIV> + </xsl:template> + + <!-- Take a delimited string and insert line breaks after a some number of elements. --> + <xsl:template name="formatDelimitedString"> + <xsl:param name="string" /> + <xsl:param name="numTokensPerRow" select="10" /> + <xsl:param name="tokenIndex" select="1" /> + <xsl:if test="$string"> + <!-- Requires the last element to also have a delimiter after it. --> + <xsl:variable name="token" select="substring-before($string, ';')" /> + <xsl:value-of select="$token" /> + <xsl:text> </xsl:text> + + <xsl:if test="$tokenIndex mod $numTokensPerRow = 0"> + <br /> + </xsl:if> + + <xsl:call-template name="formatDelimitedString"> + <xsl:with-param name="string" select="substring-after($string, ';')" /> + <xsl:with-param name="numTokensPerRow" select="$numTokensPerRow" /> + <xsl:with-param name="tokenIndex" select="$tokenIndex + 1" /> + </xsl:call-template> + </xsl:if> + </xsl:template> + +</xsl:stylesheet> diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/TestConfig.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/TestConfig.java new file mode 100644 index 0000000..1b1dbbc --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/TestConfig.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed; + +import com.android.tradefed.log.LogUtil.CLog; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * A singleton class that reads properties from afw-test.props and provides interfaces + * to get test configurations. + */ +public class TestConfig { + /** + * Property key of factory reset timeout in minutes. + */ + public static final String KEY_FACTORY_RESET_TIMEOUT_MIN = "factory_reset_timeout_min"; + + /** + * Property key of Timeout size, used in the test configuration file. + */ + public static final String KEY_TIMEOUT_SIZE = "timeout_size"; + + /** + * Mapping from timeout size to its integer value. + */ + private static final Map<String, Integer> TIMEOUT_SIZE_MAPPING = + new HashMap<String, Integer>() {{ + put("S", 1); + put("M", 2); + put("L", 3); + put("XL", 5); + put("XXL", 8); + }}; + + /** + * Property key of test timeout in minute, used in the test configuration file. + */ + public static final String KEY_TEST_TIMEOUT_MIN = "test_timeout_min"; + + /** + * Singleton of this class. + */ + private static TestConfig sTestConfig; + + // Stores configurations loaded from a file. + private final Properties mProps; + + /** + * Creates a new object by loading configurations from file. + * + * @param configFile test configuration file + */ + private TestConfig(File configFile) throws IOException { + mProps = new Properties(); + mProps.load(new FileInputStream(configFile)); + } + + /** + * Inits a {@link TestConfig} from a file. + * + * @param configFile configuration file to read + */ + public static void init(File configFile) throws IOException { + sTestConfig = new TestConfig(configFile); + } + + /** + * Gets the singleton of this class. + * + * @return {@link TestConfig} singleton, null if it's not initialized. + */ + public static TestConfig getInstance() { + return sTestConfig; + } + + /** + * Gets the property of a specific key. + * + * @param key property key + * @return propery value of the given key + * If the key doesn't exist in the configuration file, null will be returned; + * If the value of the key is empty, empty string will be returned + */ + public String getProperty(String key) { + return mProps.getProperty(key); + } + + /** + * Gets the property of a specific key. + * + * @param key property key + * @param defaultValue default value if given key is not found + * @return property value of the given key; + * if given key not found, {@link #defaultValue} is returned + */ + public String getProperty(String key, String defaultValue) { + return mProps.getProperty(key, defaultValue); + } + + /** + * Gets factory reset timeout in minute. + * + * @return timeout in minute or -1 if either timeout is not specified or invalid + */ + public int getFactoryResetTimeoutMin() { + return getFactoryResetTimeoutMin(-1); + } + + /** + * Gets factory reset timeout in minute. + * + * @param defaultValue default value if test timeout not specified + * @return factory reset timeout in minute + */ + public int getFactoryResetTimeoutMin(int defaultValue) { + String value = mProps.getProperty(KEY_FACTORY_RESET_TIMEOUT_MIN); + if (value == null || value.isEmpty()) { + return defaultValue; + } + + return Integer.valueOf(value); + } + + /** + * Gets timeout size value from props file. + * + * <p>Possible size values are strings "S", "M", "L", "XL" or "XXL".</p> + * + * @return integer value of the timeout size. + */ + public int getTimeoutSize() { + // Default to M + String timeoutSize = getProperty(KEY_TIMEOUT_SIZE, "M"); + if (!TIMEOUT_SIZE_MAPPING.containsKey(timeoutSize)) { + CLog.w("Invalid timeout size, defaulting to M"); + timeoutSize = "M"; + } + + return TIMEOUT_SIZE_MAPPING.get(timeoutSize); + } + + /** + * Gets test timeout in minute. + * + * @return test timeout in minute or -1 if either timeout is not specified or invalid + */ + public int getTestTimeoutMin() { + return getTestTimeoutMin(-1); + } + + /** + * Gets test timeout in minute. + * + * @param defaultValue default value if test timeout not specified + * @return test timeout in minute + */ + public int getTestTimeoutMin(int defaultValue) { + String value = mProps.getProperty(KEY_TEST_TIMEOUT_MIN); + if (value == null || value.isEmpty()) { + return defaultValue; + } + + return Integer.valueOf(value); + } +} + diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAdbRootOptionPreparer.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAdbRootOptionPreparer.java new file mode 100644 index 0000000..74c4480 --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAdbRootOptionPreparer.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.targetprep.ITargetCleaner; +import com.android.tradefed.targetprep.TargetSetupError; + +/** + * A {@link ITargetPreparer} that sets enable-root option of the testing device. + */ +@OptionClass(alias = "afw-test-adb-root-option") +public class AfwTestAdbRootOptionPreparer implements ITargetCleaner { + + @Option(name = "enable-root-option", + description = "Whether to enable adb root option on the testing device.") + private boolean mEnableAdbRootOption = true; + + /** + * Original adb root option. + */ + private Boolean mOriginalRootOption = null; + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, + DeviceNotAvailableException { + mOriginalRootOption = device.getOptions().isEnableAdbRoot(); + device.getOptions().setEnableAdbRoot(mEnableAdbRootOption); + } + + /** + * {@inheritDoc} + */ + @Override + public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) + throws DeviceNotAvailableException { + if (mOriginalRootOption != null) { + device.getOptions().setEnableAdbRoot(mOriginalRootOption); + } + } +} diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAppUninstaller.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAppUninstaller.java new file mode 100644 index 0000000..599e993 --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestAppUninstaller.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.targetprep.ITargetCleaner; +import com.android.tradefed.targetprep.TargetSetupError; + +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +/** + * A {@link ITargetPreparer} that helps uninstalling apps before or after the test. + */ +@OptionClass(alias="afw-test-app-uninstaller") +public class AfwTestAppUninstaller implements ITargetCleaner { + + @Option(name = "before-test", description = "packages to uninstall before test") + private List<String> mPackageNamesBeforeTest = new LinkedList<>(); + + @Option(name = "after-test", description = "packages to uninstall after test") + private List<String> mPackageNamesAfterTest = new LinkedList<>(); + + @Option(name = "reboot-before-uninstall", + description = "if reboot the device before uinstallation") + private boolean mRebootBeforeUninstall = false; + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) + throws TargetSetupError, DeviceNotAvailableException { + String result = uninstall(device, mPackageNamesBeforeTest); + + if (result != null) { + throw new TargetSetupError(String.format("Failed to uninstall %s on %s", + result, device.getSerialNumber())); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) + throws DeviceNotAvailableException { + + String result = uninstall(device, mPackageNamesAfterTest); + + if (result != null) { + throw new RuntimeException(String.format("Failed to uninstall %s on %s", + result, device.getSerialNumber())); + } + } + + /** + * Uninstalls list of packages. + * + * @param device the testing device + * @param packages packages to uninstall + * @return {@code null} if all given packages are uninstalled, or the name of the first package + * that failed to be uninstalled + */ + private String uninstall(ITestDevice device, List<String> packages) + throws DeviceNotAvailableException { + Set<String> installedPackages = device.getInstalledPackageNames(); + installedPackages.retainAll(packages); + + if (installedPackages.isEmpty()) { + return null; + } + + if (mRebootBeforeUninstall) { + device.reboot(); + } + + for (String pkgName : installedPackages) { + String result = device.uninstallPackage(pkgName); + if (result != null) { + return pkgName; + } + + CLog.i(String.format("Successfully uninstalled %s on %s", + pkgName, device.getSerialNumber())); + } + + return null; + } +} diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEncryptDevice.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEncryptDevice.java new file mode 100644 index 0000000..da66069 --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEncryptDevice.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.targetprep.ITargetPreparer; +import com.android.tradefed.targetprep.TargetSetupError; + +import java.util.concurrent.TimeUnit; + +/** + * A {@link ITargetPreparer} that encrypts the testing device. + * + * <p>Requires a device where 'adb root' is possible, typically a userdebug build type.</p> + * + * <p>If the device is already encrypted, nothing will be done. </p> + */ +@OptionClass(alias = "afw-test-encrypt-device") +public class AfwTestEncryptDevice extends AfwTestTargetPreparer implements ITargetPreparer { + + @Option(name = "inplace", + description = "Whether to encrypt the device inplace or by wipe") + private Boolean mInplace = true; + + @Option(name = "timeout-secs", + description = "Timeout in seconds.") + private Long mTimeoutSecs = TimeUnit.MINUTES.toSeconds(5); + + @Option(name = "attempts", + description = "Number of attempts to encrypt the device") + private int mAttempts = 3; + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, + DeviceNotAvailableException { + + for (int i = 0; i < mAttempts; ++i) { + CLog.i(String.format("Encrypting device %s: #%d", device.getSerialNumber(), i+1)); + + // Exit from loop if the device is encrypted successfully + if (device.encryptDevice(mInplace)) { + break; + } + + // The device may become unavailable, wait for it. + device.waitForDeviceAvailable(TimeUnit.SECONDS.toMillis(10)); + } + + // Fail the target preparer if encryption failed + if (!device.isDeviceEncrypted()) { + throw new TargetSetupError( + "Failed to encrypt device " + device.getSerialNumber()); + } + + device.waitForDeviceAvailable(TimeUnit.SECONDS.toMillis(mTimeoutSecs) * getTimeoutSize()); + + CLog.i(String.format("Device %s is encrypted successfully", device.getSerialNumber())); + } +} diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEnvDumper.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEnvDumper.java new file mode 100644 index 0000000..cdb0b28 --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestEnvDumper.java @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.google.common.base.Joiner; + +import com.android.afwtest.tradefed.utils.TimeUtil; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.device.PackageInfo; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.targetprep.ITargetCleaner; +import com.android.tradefed.targetprep.TargetSetupError; +import com.android.tradefed.util.StreamUtil; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * A target preparer that dumps testing environment info to a text file. + * + * <p> + * The file is saved at: + * ${CTS_ROOT}/android-cts/repository/logs/{given-prefix}_EnvDump_{TimeStamp}.txt. + * Generally {given-prefix} should be the test apk file name. + * </p> + */ +@OptionClass(alias = "afw-test-env-dumper") +public class AfwTestEnvDumper extends AfwTestTargetPreparer implements ITargetCleaner { + + private static final String DUMP_FILE_NAME_SUFFIX = "EnvDump"; + + @Option(name = "file-name-prefix", + description = "Dump file name prefix, suggested to be test apk file name", + mandatory = true) + private String mFileNamePrefix = null; + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) + throws TargetSetupError, + DeviceNotAvailableException { + // Do nothing, dump after the test only + } + + /** + * {@inheritDoc} + */ + @Override + public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) + throws DeviceNotAvailableException { + + try { + String str = getEnv(device); + + // Creates the dump file + File dumpFile = getDumpFile(buildInfo); + + OutputStream os = new FileOutputStream(dumpFile); + os.write(str.getBytes()); + StreamUtil.flushAndCloseStream(os); + + CLog.i("Dumped file: " + dumpFile.getAbsolutePath()); + } catch (IOException exception) { + CLog.e("Failed to dump app versions to file", e); + } + } + + /** + * Gets environment configurations as string. + * + * @param deivce testing device + * @return environment configuration as string + */ + protected String getEnv(ITestDevice device) throws DeviceNotAvailableException { + return getAppVersions(device); + } + + /** + * Gets a {@link File} to dump environment info. + * + * @param buildInfo reference to {@link IBuildInfo} + * @return {@link File} object of a unique file + */ + private File getDumpFile(IBuildInfo buildInfo) { + return new File(getCtsBuildHelper(buildInfo).getLogsDir(), + String.format("%s_%s_%s.txt", + mFileNamePrefix, + DUMP_FILE_NAME_SUFFIX, + TimeUtil.getResultTimestamp())); + } + + /** + * Gets versions of all {@link App} as a string. + * + * @param device reference to {@link ITestDevice} + * @return versions of all {@link App} as string + */ + private String getAppVersions(ITestDevice device) throws DeviceNotAvailableException { + Set<String> installPkgs = device.getInstalledPackageNames(); + + List<String> appVersions = new ArrayList<String>(); + + for (Map.Entry<String, String> pkg: getPackagesToDump().entrySet()) { + String pkgName = pkg.getKey(); + String appName = pkg.getValue(); + // Gets versions of install app only + if (installPkgs.contains(pkgName)) { + PackageInfo pkgInfo = device.getAppPackageInfo(pkgName); + String appVer = String.format("%s: Ver %s", appName, pkgInfo.getVersionName()); + + // Log it to tradefed console for debugging purpose + CLog.i(appVer); + + appVersions.add(appVer); + } + } + + return Joiner.on("\n").join(appVersions.iterator()); + } + + + /** + * Gets set of packages whose info should be dumped. + * + * @return a {@link Map} with key=full package name and value=app representation name + */ + protected Map<String, String> getPackagesToDump() { + Map<String, String> pkgs = new HashMap<String, String>(); + + pkgs.put("com.google.android.gms", "Google Mobile Service"); + pkgs.put("com.android.managedprovisioning", "Managed Provisioning"); + pkgs.put("com.afwsamples.testdpc", "TestDPC"); + + return pkgs; + } +} + diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestFactoryReset.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestFactoryReset.java new file mode 100644 index 0000000..4c849f2 --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestFactoryReset.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.afwtest.tradefed.utils.TimeUtil; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.targetprep.ITargetPreparer; +import com.android.tradefed.targetprep.TargetSetupError; +import com.android.tradefed.util.CommandResult; +import com.android.tradefed.util.CommandStatus; + +import java.util.concurrent.TimeUnit; + +/** + * A {@link ITargetPreparer} that factory resets the device by sending an intent + * to package com.android.afwtest.deviceadmin (AfwThDeviceAdmin.apk). + * + * <p>Requires a device where 'adb' is available after factory reset, typically + * a userdebug build type. + * </p> + */ +@OptionClass(alias = "afw-test-factory-reset") +public class AfwTestFactoryReset extends AfwTestTargetPreparer implements ITargetPreparer { + + @Option(name = "use-fastboot", + description = "Whether to use 'fastboot format' to do factory reset") + private boolean mUseFastboot = false; + + @Option(name = "timeout-secs", + description = "Factory reset timeout, in seconds.") + private Long mTimeoutSecs = TimeUnit.MINUTES.toSeconds(15); + + @Option(name = "attempts", + description = "Number of attempts to start factory reset") + private int mAttempts = 2; + + @Option (name = "wipe-protection-data", + description = "If factory reset protection data should be wiped") + private boolean mWipeProtectionData = false; + + // Command string to change system locale to en_US + private static final String CHANGE_LOCALE_TO_EN_US_CMD = + "am instrument -w -e locale en_US com.android.afwtest.systemutil/.ChangeLocale"; + + // After sending the factory reset intent out, time allowed for + // the device to start rebooting + private static final long REBOOT_WAIT_TIME_MS = TimeUnit.MINUTES.toMillis(2); + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, + DeviceNotAvailableException { + if (getUseFastboot(device)) { + CLog.i("Factory resetting device via fastboot"); + wipeDevice(device); + } else { + CLog.i("Factory resetting device via DeviceAdmin app"); + doFactoryReset(device); + } + + // Wait for the device to be available + long factoryResetTimeoutMs = getFactoryResetTimeout(); + CLog.i(String.format("Waiting for factory reset to finish: timeout=%d seconds", + factoryResetTimeoutMs / 1000)); + device.waitForDeviceAvailable(factoryResetTimeoutMs); + + // re-enable adb root + enableAdbRoot(device); + + CLog.i(String.format("Device %s is factory reset successfully", device.getSerialNumber())); + + postFactoryReset(device); + } + + /** + * Gets if configured to use fastboot to do factory reset. + * + * @return {@code true} if use fastboot, {@code false} otherwise + */ + protected boolean getUseFastboot(ITestDevice device) throws DeviceNotAvailableException { + return mUseFastboot; + } + + /** + * Executes actions after factory reset. + */ + protected void postFactoryReset(ITestDevice device) + throws DeviceNotAvailableException, TargetSetupError { + // Sync time with the host + TimeUtil.syncHostTime(device); + + CLog.i("Disabling auto-rotate"); + device.executeShellCommand("settings put system accelerometer_rotation 0"); + + // Change system locale to en_US + CLog.i("Changing system locale to en_US"); + String result = device.executeShellCommand(CHANGE_LOCALE_TO_EN_US_CMD); + if (result == null || !result.contains("result=SUCCESS")) { + CLog.e(result); + } else { + CLog.i("System locale changed to en_US"); + } + } + + /** + * Gets configured factory reset time out, in ms. + * + * @return factory reset timeout, in ms + */ + private long getFactoryResetTimeout() { + long result = 0; + // Get from afw-test.props file + if (getTestConfig() != null) { + result = TimeUnit.MINUTES.toMillis(getTestConfig().getFactoryResetTimeoutMin(0)); + } + // If not specified in afw-test.props, use mTimeoutSecs + if (result == 0) { + result = TimeUnit.SECONDS.toMillis(mTimeoutSecs); + } + + return result; + } + + /** + * Does factory reset with device admin. + * + * @param device test device + */ + private void doFactoryReset(ITestDevice device) + throws DeviceNotAvailableException, TargetSetupError { + int count = 0; + boolean factoryResetStarted; + do { + count++; + CLog.i(String.format("Factory resetting device %s, #%d", + device.getSerialNumber(), count)); + + CLog.i(device.executeShellCommand(getFactoryResetCommand())); + factoryResetStarted = device.waitForDeviceNotAvailable(REBOOT_WAIT_TIME_MS); + } while (count < mAttempts && !factoryResetStarted); + + // Wait for the device to reboot + if (!factoryResetStarted) { + throw new TargetSetupError( + "Failed to start factory reset on device " + device.getSerialNumber()); + } + } + + /** + * Gets factory reset command string. + */ + private String getFactoryResetCommand() { + String result = "am start -n com.android.afwtest.deviceadmin/.FactoryResetActivity"; + if (mWipeProtectionData) { + result += " --ez afwtest.wipe.protection.data true"; + } + return result; + } + + /** + * Wipes device via fastboot. + * + * @param device test device + */ + private void wipeDevice(ITestDevice device) + throws DeviceNotAvailableException, TargetSetupError { + + CLog.i(String.format("Wiping device %s".format(device.getSerialNumber()))); + + device.rebootIntoBootloader(); + fastbootFormat(device, "cache"); + fastbootFormat(device, "userdata"); + device.executeFastbootCommand("reboot"); + } + + /** + * Performs fastboot erase/format operation on certain partition + * + * @param device test device + * @param partition android partition, e.g. userdata, cache, system + */ + private void fastbootFormat(ITestDevice device, String partition) + throws DeviceNotAvailableException, TargetSetupError { + + CLog.i("Attempting: fastboot format %s", partition); + CommandResult r = device.executeLongFastbootCommand("format", partition); + if (r.getStatus() != CommandStatus.SUCCESS) { + throw new TargetSetupError( + String.format("format %s failed: %s", partition, r.getStderr())); + } + } +} + diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestGoogleAccountRemover.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestGoogleAccountRemover.java new file mode 100644 index 0000000..aee6e9d --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestGoogleAccountRemover.java @@ -0,0 +1,201 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.targetprep.ITargetCleaner; +import com.android.tradefed.targetprep.TargetSetupError; +import com.android.tradefed.util.RunUtil; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +/** + * Target preparer to remove Google accounts. + * + * <p>Requires a device where 'adb root' is possible, typically a userdebug build.</p> + * + * <p>Requires com.anroid.afwtest.systemutil to be pre-installed as privileged app.</p> + */ +@OptionClass(alias = "afw-test-google-account-remover") +public class AfwTestGoogleAccountRemover extends AfwTestTargetPreparer implements ITargetCleaner { + + /** + * Package name of the system util app. + */ + private static final String SYSTEM_UTIL_PKG_NAME = "com.android.afwtest.systemutil"; + + /** + * Action to remove Google account. + */ + private static final String REMOVE_GOOGLE_ACCOUNT_CLASS = ".RemoveGoogleAccount"; + + /** + * Waiting time for system util app. + */ + private static final long SYSTEM_UTIL_PKG_WAIT_TIME_MS = TimeUnit.MINUTES.toMillis(3); + + @Option(name = "google-account", + description = "Google accounts to remove, remove all Google accounts if not none given") + private Collection<String> mGoogleAccounts = new ArrayList<String>(); + + @Option(name = "remove-before-test", + description = "Remove given Google account or all Google accounts before test.") + private boolean mRemoveBeforeTest = true; + + @Option(name = "remove-after-test", + description = "Remove given Google account or all Google accounts after the test.") + private boolean mRemoveAfterTest = true; + + @Option(name = "attempts", + description = "Number of attempts") + private int mAttempts = 3; + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) + throws TargetSetupError, DeviceNotAvailableException { + if (waitForAppPkgInfo(device, SYSTEM_UTIL_PKG_NAME, SYSTEM_UTIL_PKG_WAIT_TIME_MS) == null) { + throw new TargetSetupError(String.format("%s not installed successfully.", + SYSTEM_UTIL_PKG_NAME)); + } + + if (mRemoveBeforeTest) { + doRemoval(device); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) + throws DeviceNotAvailableException { + if (waitForAppPkgInfo(device, SYSTEM_UTIL_PKG_NAME, SYSTEM_UTIL_PKG_WAIT_TIME_MS) == null) { + throw new RuntimeException(String.format("%s not installed successfully.", + SYSTEM_UTIL_PKG_NAME)); + } + + if (mRemoveAfterTest) { + try { + doRemoval(device); + } catch (TargetSetupError ex) { + throw new RuntimeException(ex); + } + } + } + + /** + * Help function to remove requested Google accounts. + * + * @param device test device + */ + private void doRemoval(ITestDevice device) + throws TargetSetupError, DeviceNotAvailableException { + if (mGoogleAccounts.isEmpty()) { + removeAllAccounts(device); + } else { + for (String account : mGoogleAccounts) { + removeAccount(device, account); + } + } + } + + /** + * Remove all Google accounts. + * + * @param device test device + */ + private void removeAllAccounts(ITestDevice device) + throws TargetSetupError, DeviceNotAvailableException { + String removeCmd = String.format("am instrument -w %s/%s", + SYSTEM_UTIL_PKG_NAME, + REMOVE_GOOGLE_ACCOUNT_CLASS); + + if (executeRemoveAccountCommand(device, removeCmd)) { + CLog.i(String.format("All Google accounts were removed from device %s.", + device.getSerialNumber())); + } else { + throw new TargetSetupError( + String.format("Failed to remove all Google accounts from device %s.", + device.getSerialNumber())); + } + } + + /** + * Remove a Google account. + * + * @param device test device + * @param account account to remove + */ + private void removeAccount(ITestDevice device, String account) + throws TargetSetupError, DeviceNotAvailableException { + String removeCmd = String.format("am instrument -w -e account \"%s\" %s/%s", + account, SYSTEM_UTIL_PKG_NAME, REMOVE_GOOGLE_ACCOUNT_CLASS); + if (executeRemoveAccountCommand(device, removeCmd)) { + CLog.i(String.format("Google account %s was removed from device %s.", + account, device.getSerialNumber())); + } else { + throw new TargetSetupError( + String.format("Failed to remove Google account %s from device %s.", + account, device.getSerialNumber())); + } + } + + /** + * Executes shell command to remove Google account. + * + * @param device test device + * @param cmd command to execute + * @return {@code true} if success, {@code false} otherwise + */ + private boolean executeRemoveAccountCommand(ITestDevice device, String cmd) + throws DeviceNotAvailableException { + CLog.v(cmd); + for (int i = 0; i < mAttempts; ++i) { + CLog.i(String.format("Removing account on device %s, #%d", + device.getSerialNumber(), i + 1)); + + String result = device.executeShellCommand(cmd); + // expected result format is (on success): + // + // INSTRUMENTATION_RESULT: result=SUCCESS + // INSTRUMENTATION_CODE: -1 + // + if (result != null && result.contains("result=SUCCESS")) { + return true; + } else { + CLog.e(String.format("Failed to remove account from device %s: %s.", + device.getSerialNumber(), result)); + // Sleep for 15 seconds before next attempt + RunUtil.getDefault().sleep(TimeUnit.SECONDS.toMillis(15)); + } + } + + // OK, failed after all attempts + return false; + } +} + diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPrivAppInstaller.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPrivAppInstaller.java new file mode 100644 index 0000000..f2f1a57 --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPrivAppInstaller.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.targetprep.ITargetCleaner; +import com.android.tradefed.targetprep.TargetSetupError; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Target preparer to install an app as privileged app by pushing it to /system/priv-app. + * + * <p>Requires a device where 'adb root' is possible, typically a userdebug build.</p> + */ +public class AfwTestPrivAppInstaller extends AfwTestTargetPreparer implements ITargetCleaner { + + /** + * System folder for privileged apps. + */ + private static final String PRIV_APP_DIR = "/system/priv-app"; + + @Option(name = "app-filename", + description = "app to install") + private Collection<String> mApps = new ArrayList<String>(); + + @Option(name = "cleanup", + description = "true to delete the apk in teardown") + private boolean mCleanup = true; + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) + throws TargetSetupError, DeviceNotAvailableException { + + if (mApps.isEmpty()) { + return; + } + + // Enable adb root and remount system partition + enableAdbRoot(device); + device.remountSystemWritable(); + + for (String app : mApps) { + String privApp = String.format("%s/%s", PRIV_APP_DIR, app); + if (device.pushFile(getApk(buildInfo, app), privApp)) { + CLog.i(String.format("Privileged app installed: %s", privApp)); + } else { + throw new TargetSetupError(String.format("Failed to install %s", privApp)); + } + } + + // Reboot to activate the installed apps + device.reboot(); + device.waitForDeviceAvailable(); + // Enable adb root again + enableAdbRoot(device); + } + + /** + * {@inheritDoc} + */ + @Override + public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) + throws DeviceNotAvailableException { + if (!mCleanup || mApps.isEmpty()) { + return; + } + + if (!device.enableAdbRoot()) { + throw new RuntimeException( + String.format("Failed to enable adb root: %s", device.getSerialNumber())); + } + + device.remountSystemWritable(); + + for (String app : mApps) { + device.executeShellCommand(String.format("rm %s/%s", PRIV_APP_DIR, app)); + } + } +} + diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPullExternalFile.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPullExternalFile.java new file mode 100644 index 0000000..4efd8ca --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestPullExternalFile.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.targetprep.ITargetCleaner; +import com.android.tradefed.targetprep.TargetSetupError; +import com.android.tradefed.util.FileUtil; + +import java.io.File; +import java.io.IOException; + +/** + * A target cleaner to copy file from the external storage of the device to host after the + * test execution. + */ +@OptionClass(alias = "afw-test-pull-external-file") +public class AfwTestPullExternalFile extends AfwTestTargetPreparer implements ITargetCleaner { + + @Option(name = "remote-file", + description = "File to copy from external storage.", + mandatory = true) + private String mRemoteFile = null; + + @Option(name = "local-file", + description = "File path relative to android-cts/repository.", + mandatory = true) + private String mLocalFile = null; + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, + DeviceNotAvailableException { + // Do nothing. + } + + /** + * {@inheritDoc} + */ + @Override + public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) + throws DeviceNotAvailableException { + + try { + File localFile = new File(getRepositoryDir(buildInfo), mLocalFile); + if (localFile.getParentFile().mkdirs()) { + localFile.createNewFile(); + } + FileUtil.copyFile(device.pullFileFromExternal(mRemoteFile), localFile); + } catch (DeviceNotAvailableException e1) { + throw e1; + } catch (Exception e2) { + // Log error but not failing the target preparer. + CLog.e(String.format("Failed to copy remote file.", e2)); + } + } +} + diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestSyncTime.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestSyncTime.java new file mode 100644 index 0000000..8f4fbcd --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestSyncTime.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.afwtest.tradefed.utils.TimeUtil; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.targetprep.ITargetPreparer; +import com.android.tradefed.targetprep.TargetSetupError; + +/** + * A {@link ITargetPreparer} that syncs device time with the running host. + * + * <p>Requires a device where 'adb root' is possible, typically a userdebug build</p> + */ +@OptionClass(alias = "afw-test-sync-time") +public class AfwTestSyncTime extends AfwTestTargetPreparer implements ITargetPreparer { + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, + DeviceNotAvailableException { + enableAdbRoot(device); + TimeUtil.syncHostTime(device); + } +} + diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestTargetPreparer.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestTargetPreparer.java new file mode 100644 index 0000000..b066374 --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestTargetPreparer.java @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.afwtest.tradefed.TestConfig; +import com.android.cts.tradefed.build.CtsBuildHelper; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.device.PackageInfo; +import com.android.tradefed.targetprep.TargetSetupError; +import com.android.tradefed.util.RunUtil; + +import java.io.File; +import java.util.concurrent.TimeUnit; + +/** + * Abstract class to keep common functionality. + */ +public abstract class AfwTestTargetPreparer { + + private CtsBuildHelper mBuildHelper; + + /** + * Gets an instance of {@link CtsBuildHelper}. + * + * @param buildInfo reference to {@link IBuildInfo} + * @return reference to {@link CtsBuildHelper} instance + */ + protected CtsBuildHelper getCtsBuildHelper(IBuildInfo buildInfo) { + if (mBuildHelper == null) { + mBuildHelper = CtsBuildHelper.createBuildHelper(buildInfo); + } + return mBuildHelper; + } + + /** + * Gets repository directory. + * + * @param buildInfo reference to {@link IBuildInfo} + * @return File path to repository folder. + */ + protected File getRepositoryDir(IBuildInfo buildInfo) { + return new File(getCtsBuildHelper(buildInfo).getCtsDir(), "repository"); + } + + /** + * Gets apk file from testcases dir. + * + * @param buildInfo reference to {@link IBuildInfo} + * @param fileName apk file name + * @return {@link File} of the apk + */ + protected File getApk(IBuildInfo buildInfo, String fileName) { + return new File(getCtsBuildHelper(buildInfo).getTestCasesDir(), fileName); + } + + /** + * Enable adb root. + * + * @param device test device + * @throws TargetSetupError if failed to enable adb root + * @throws DeviceNotAvailableException if test device becomes unavailable + */ + protected void enableAdbRoot(ITestDevice device) + throws TargetSetupError, DeviceNotAvailableException { + // Enable adb root + if (!device.enableAdbRoot()) { + throw new TargetSetupError( + String.format("Failed to enable adb root: %s", device.getSerialNumber())); + } + } + + /** + * Waits and gets certain app package info. + * + * @param device test device + * @param pkgName app package name + * @param timeoutMs timeout in ms + * @return App package info of given package if found, null otherwise + * @throws DeviceNotAvailableException if test device becomes unavailable + */ + protected PackageInfo waitForAppPkgInfo(ITestDevice device, String pkgName, long timeoutMs) + throws DeviceNotAvailableException { + + long threadSleepTimeMs = TimeUnit.SECONDS.toMillis(5); + + PackageInfo pkgInfo = device.getAppPackageInfo(pkgName); + + while (pkgInfo == null && timeoutMs > 0) { + RunUtil.getDefault().sleep(threadSleepTimeMs); + timeoutMs -= threadSleepTimeMs; + + pkgInfo = device.getAppPackageInfo(pkgName); + } + + return pkgInfo; + } + + /** + * Gets {@link TestConfig} instance. + * + * @return {@link TestConfig} instance or null if not found; + */ + protected TestConfig getTestConfig() { + return TestConfig.getInstance(); + } + + /** + * Gets timeout size from config file. + * + * @return timeout size in integer + */ + protected int getTimeoutSize() { + TestConfig testConfig = TestConfig.getInstance(); + + // Target prepares should still work without test config file; + if (testConfig == null) { + return 1; + } + + return TestConfig.getInstance().getTimeoutSize(); + } +} + diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestUserRemover.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestUserRemover.java new file mode 100644 index 0000000..832a9a9 --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestUserRemover.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.targetprep.ITargetCleaner; +import com.android.tradefed.targetprep.TargetSetupError; +import com.android.tradefed.util.RunUtil; + +import java.util.ArrayList; +import java.util.concurrent.TimeUnit; + +/** + * A target preparer that can remove all non-primary users (user id not 0). + */ +@OptionClass(alias = "afw-test-user-remover") +public class AfwTestUserRemover implements ITargetCleaner { + + @Option(name = "remove-users-before-test", + description = "Remove all non-primary users before the test.") + private boolean mRemoveUsersBeforeTest = false; + + @Option(name = "remove-users-after-test", + description = "Remove all non-primary users after the test.") + private boolean mRemoveUsersAfterTest = true; + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, + DeviceNotAvailableException { + if (mRemoveUsersBeforeTest && !removeUsers(device)) { + throw new TargetSetupError( + "Failed to remove all non-primary users on device " + device.getSerialNumber()); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) + throws DeviceNotAvailableException { + if (mRemoveUsersAfterTest && !removeUsers(device)) { + throw new RuntimeException( + "Failed to remove all non-primary users on device " + device.getSerialNumber()); + } + } + + /** + * Remove all non-primary users. + * + * @param device the testing device + * @return {@code true} if all non-primary users are removed, {@code false} otherwise + */ + private boolean removeUsers(ITestDevice device) throws DeviceNotAvailableException { + // Ensure package manager is running + device.waitForDeviceAvailable(); + + ArrayList<Integer> users = device.listUsers(); + + if (users == null) { + return true; + } + + boolean success = true; + + for (Integer userId : users) { + if (userId == 0) { + // Can't remove user 0, so don't try. + continue; + } + + if (device.removeUser(userId)) { + CLog.i("Successfully removed user %d on %s", userId, device.getSerialNumber()); + } else { + CLog.e("Failed to remove user %d on %s", userId, device.getSerialNumber()); + success = false; + } + } + + RunUtil.getDefault().sleep(TimeUnit.SECONDS.toMillis(60)); + + // Make sure device is still available + device.waitForDeviceAvailable(); + + return success; + } +} + diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestWifiPreparer.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestWifiPreparer.java new file mode 100644 index 0000000..1a039a4 --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/targetprep/AfwTestWifiPreparer.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.targetprep; + +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.config.OptionClass; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.targetprep.ITargetCleaner; +import com.android.tradefed.targetprep.TargetSetupError; +import com.android.tradefed.util.RunUtil; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +/** + * A {@link ITargetPreparer} that connects to wifi network with configurations read from file. + */ +@OptionClass(alias = "afw-test-wifi") +public final class AfwTestWifiPreparer extends AfwTestTargetPreparer implements ITargetCleaner { + + @Option(name = "wifi-config-file", + description = "The file name containing wifi configuration.") + private String mConfigFileName = null; + + @Option(name = "wifi-ssid-key", + description = "The key of wifi ssid in the config file.") + private String mWifiSsidKey = null; + + @Option(name = "wifi-password-key", + description = "The key of wifi password in the config file.") + private String mWifiPasswordKey = null; + + @Option(name = "wifi-attempts", + description = "Number of attempts to connect to wifi network.") + private int mWifiAttempts = 5; + + @Option(name = "disconnect-wifi-after-test", + description = "Disconnect from wifi network after test completes.") + private boolean mDisconnectWifiAfterTest = true; + + /** + * {@inheritDoc} + */ + @Override + public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, + DeviceNotAvailableException { + + if (mConfigFileName == null) { + throw new TargetSetupError("wifi-config-file not specified"); + } + + if (mWifiSsidKey == null) { + throw new TargetSetupError("wifi-ssid-key not specified"); + } + + File configFile = new File(getCtsBuildHelper(buildInfo).getTestCasesDir(), mConfigFileName); + Properties props = null; + try { + props = readProperties(configFile); + } catch (IOException e) { + throw new TargetSetupError("Failed to read prop file: " + configFile.getAbsolutePath()); + } + + String wifiSsid = props.getProperty(mWifiSsidKey, ""); + String wifiPassword = props.getProperty(mWifiPasswordKey, ""); + + if (wifiSsid.isEmpty()) { + throw new TargetSetupError( + mWifiSsidKey + " not specified in file " + configFile.getAbsolutePath()); + } + + // Implement retry. + for (int i = 0; i < mWifiAttempts; ++i) { + if (device.connectToWifiNetworkIfNeeded(wifiSsid, wifiPassword)) { + return; + } + boolean lastAttempt = (i + 1) == mWifiAttempts; + if (!lastAttempt) { + RunUtil.getDefault().sleep(device.getOptions().getWifiRetryWaitTime()); + } + } + + throw new TargetSetupError(String.format("Failed to connect to wifi network %s on %s", + wifiSsid, device.getSerialNumber())); + } + + /** + * {@inheritDoc} + */ + @Override + public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e) + throws DeviceNotAvailableException { + + if (mDisconnectWifiAfterTest && device.isWifiEnabled()) { + if (device.disconnectFromWifi()) { + CLog.i("Successfully disconnected from wifi network on %s", + device.getSerialNumber()); + } else { + CLog.w("Failed to disconnect from wifi network on %s", device.getSerialNumber()); + } + } + } + + /** + * Help function to read {@link Properties} from a file. + * + * @param file {@link File} to read properties from + * @return {@link Properties} read from given file + * + * @throws IOException if read failed + */ + private static Properties readProperties(File file) throws IOException { + InputStream input = null; + + try { + input = new FileInputStream(file); + Properties props = new Properties(); + props.load(input); + return props; + } finally { + if (input != null) { + input.close(); + } + } + } +} + diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwTest.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwTest.java new file mode 100644 index 0000000..a39a91a --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/testtype/AfwTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.testtype; + +import com.android.afwtest.tradefed.TestConfig; +import com.android.cts.tradefed.build.CtsBuildHelper; +import com.android.cts.tradefed.testtype.CtsTest; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.tradefed.result.ITestInvocationListener; +import com.android.tradefed.util.FileUtil; + +import java.io.File; +import java.io.IOException; + +/** + * Customized {@link CtsTest} for running afw-test tests. + * <p>Supports running all the tests contained in an afw-test plan, or individual test packages. + * </p> + */ +public class AfwTest extends CtsTest { + + private static final String CMD_DISABLE_PKG_VERIFICATION = + "settings put global package_verifier_enable 0"; + + @Option(name = "afw-test-config-file", + description = "Test harness config file to replace afw-test.props.") + private String mConfigFile = null; + + /** + * A {@link CtsBuildHelper} instance to access afw-test build info, such as the + * absolute paths of the test plan file and test case apks. + */ + private CtsBuildHelper mAfwTestBuild = null; + + /** + * {@inheritDoc} + */ + public AfwTest() { + this(0 /*shardAssignment*/, 1 /*totalShards*/); + } + + /** + * {@inheritDoc} + */ + public AfwTest(int shardAssignment, int totalShards) { + super(shardAssignment, totalShards); + } + + /** + * {@inheritDoc} + */ + @Override + public void setBuild(IBuildInfo build) { + super.setBuild(build); + + mAfwTestBuild = CtsBuildHelper.createBuildHelper(build); + } + + /** + * {@inheritDoc} + */ + @Override + public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { + CLog.i("Running afw-test"); + + ITestDevice device = getDevice(); + if (device == null) { + throw new IllegalArgumentException("Device not found"); + } + + try { + // Replace afw-test.props with a customized file if there is. + if (mConfigFile != null) { + FileUtil.copyFile(new File(mConfigFile), getTestConfigFile()); + } + + // Init TestConfig singleton. + TestConfig.init(getTestConfigFile()); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // Disable package verification. + // This is necessary because CtsTest.run() will push TestDeviceSetup.apk + // to the testing device which will trigger package verification dialog. + device.executeShellCommand(CMD_DISABLE_PKG_VERIFICATION); + + super.run(listener); + } + + /** + * Reads test configuration file, afw-test.props. + * + * @return {@link File} object keeping loaded content from afw-test.props + */ + protected File getTestConfigFile() throws IOException { + return new File(mAfwTestBuild.getTestCasesDir(), "afw-test.props"); + } +} diff --git a/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/TimeUtil.java b/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/TimeUtil.java new file mode 100644 index 0000000..d5730ce --- /dev/null +++ b/tools/tradefed-host/src/com/android/afwtest/tradefed/utils/TimeUtil.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 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.afwtest.tradefed.utils; + +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.log.LogUtil.CLog; + +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * Time related utils. + */ +public class TimeUtil { + + /** + * Return the current timestamp in a compressed format, e.g.: 2015.11.25_11.42.12 + */ + public static String getResultTimestamp() { + SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy.MM.dd_HH.mm.ss"); + return dateFormat.format(new Date()); + } + + /** + * Sync device time with host. + * + * @param device test device + */ + public static void syncHostTime(ITestDevice device) throws DeviceNotAvailableException { + CLog.i(String.format("Before time sync: %s", device.executeShellCommand("date -u"))); + + Date date = new Date(); + String dateString = null; + if (device.getApiLevel() < 23) { + // set date in epoch format + dateString = Long.toString(date.getTime() / 1000); //ms to s + } else { + // set date with POSIX like params + SimpleDateFormat sdf = new java.text.SimpleDateFormat("MMddHHmmyyyy.ss"); + sdf.setTimeZone(java.util.TimeZone.getTimeZone("UTC")); + dateString = sdf.format(date); + } + // best effort, no verification + String cmd = String.format("date -u %s", dateString); + CLog.i(String.format("Setting time: %s", cmd)); + CLog.i(device.executeShellCommand(cmd)); + + CLog.i(String.format("After time sync: %s", device.executeShellCommand("date -u"))); + } + +} diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java new file mode 100644 index 0000000..847aeef --- /dev/null +++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2016 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.cts.tradefed.build; + +import com.android.tradefed.build.FolderBuildInfo; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.build.IBuildProvider; +import com.android.tradefed.build.IFolderBuildInfo; +import com.android.tradefed.config.Option; +import com.android.tradefed.log.LogUtil.CLog; +import com.android.ddmlib.Log; + +import java.io.File; + +/** + * A simple {@link IBuildProvider} that uses a pre-existing CTS install. + */ +public class CtsBuildProvider implements IBuildProvider { + private static final String TAG = "afwtest"; + + public static final String CTS_BUILD_VERSION = "1.5"; + + public static final String CTS_PACKAGE = "com.android.cts.tradefed.testtype"; + + @Option(name="cts-install-path", description="the path to the cts installation to use") + private String mCtsRootDirPath = System.getProperty("CTS_ROOT"); + + /** + * {@inheritDoc} + */ + @Override + public IBuildInfo getBuild() { + Log.i(TAG, String.format("Afw Test Harness Version: %s", CTS_BUILD_VERSION)); + + if (mCtsRootDirPath == null) { + throw new IllegalArgumentException("Missing --cts-install-path"); + } + IFolderBuildInfo ctsBuild = new FolderBuildInfo( + Package.getPackage(CTS_PACKAGE).getImplementationVersion(), "cts", "cts"); + ctsBuild.setRootDir(new File(mCtsRootDirPath)); + return ctsBuild; + } + + /** + * {@inheritDoc} + */ + @Override + public void buildNotTested(IBuildInfo info) { + // ignore + } + + /** + * {@inheritDoc} + */ + @Override + public void cleanUp(IBuildInfo info) { + // ignore + } +} diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java new file mode 100644 index 0000000..920fb9f --- /dev/null +++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsInstrumentationApkTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2016 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.cts.tradefed.testtype; + +import com.android.afwtest.tradefed.TestConfig; +import com.android.cts.tradefed.build.CtsBuildHelper; +import com.android.cts.util.AbiUtils; +import com.android.ddmlib.Log; +import com.android.tradefed.build.IBuildInfo; +import com.android.tradefed.device.DeviceNotAvailableException; +import com.android.tradefed.device.ITestDevice; +import com.android.tradefed.result.ITestInvocationListener; +import com.android.tradefed.testtype.IAbi; +import com.android.tradefed.testtype.IBuildReceiver; +import com.android.tradefed.testtype.InstrumentationTest; + +import java.io.File; +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.TimeUnit; + +/** + * An {@link InstrumentationTest} that will install CTS apks + * before test execution, and uninstall on execution completion. + */ +public class CtsInstrumentationApkTest extends InstrumentationTest implements IBuildReceiver { + + private static final String LOG_TAG = "afwtest.CtsInstrumentationApkTest"; + + // Default execution timeout for afw test packages + private static final long TEST_TIMEOUT_MS = TimeUnit.MINUTES.toMillis(10); + + // 1 min in milliseconds + private static final long ONE_MIN_MS = TimeUnit.MINUTES.toMillis(1); + + + /** The file names of the CTS apks to install */ + private Collection<String> mInstallFileNames = new ArrayList<String>(); + private Collection<String> mUninstallPackages = new ArrayList<String>(); + protected CtsBuildHelper mCtsBuild = null; + protected IAbi mAbi = null; + + /** + * @param abi the ABI to run the test on + */ + public void setAbi(IAbi abi) { + mAbi = abi; + } + + /** + * {@inheritDoc} + */ + @Override + public void setBuild(IBuildInfo build) { + mCtsBuild = CtsBuildHelper.createBuildHelper(build); + } + + /** + * Add an apk to install. + * + * @param apkFileName the apk file name + * @param packageName the apk's Android package name + */ + public void addInstallApk(String apkFileName, String packageName) { + mInstallFileNames.add(apkFileName); + mUninstallPackages.add(packageName); + } + + /** + * {@inheritDoc} + */ + @Override + public void run(final ITestInvocationListener listener) + throws DeviceNotAvailableException { + ITestDevice testDevice = getDevice(); + + Log.d(LOG_TAG, "Running customized CtsInstrumentationRunner."); + + if (testDevice == null) { + Log.e(LOG_TAG, "Missing device."); + return; + } + if (mCtsBuild == null) { + Log.e(LOG_TAG, "Missing build"); + return; + } + boolean success = true; + for (String apkFileName : mInstallFileNames) { + Log.d(LOG_TAG, String.format("Installing %s on %s", apkFileName, + testDevice.getSerialNumber())); + try { + File apkFile = mCtsBuild.getTestApp(apkFileName); + String errorCode = null; + String[] options = {AbiUtils.createAbiFlag(mAbi.getName())}; + errorCode = testDevice.installPackage(apkFile, true, options); + if (errorCode != null) { + Log.e(LOG_TAG, String.format("Failed to install %s on %s. Reason: %s", + apkFileName, testDevice.getSerialNumber(), errorCode)); + success = false; + } + } catch (FileNotFoundException e) { + Log.e(LOG_TAG, String.format("Could not find file %s", apkFileName)); + success = false; + } + } + if (success) { + long testTimeout = + TimeUnit.MINUTES.toMillis(TestConfig.getInstance().getTestTimeoutMin(0)); + if (testTimeout == 0) { + testTimeout = TEST_TIMEOUT_MS + + TestConfig.getInstance().getTimeoutSize() * ONE_MIN_MS * 5; + } + Log.i(LOG_TAG, String.format("Instrumentation test timeout = %d seconds", + TimeUnit.MILLISECONDS.toSeconds(testTimeout))); + + super.setTestTimeout((int)testTimeout); + // Shell timeout must be larger than test timeout + super.setShellTimeout(testTimeout + ONE_MIN_MS); + super.run(listener); + } + for (String packageName : mUninstallPackages) { + Log.d(LOG_TAG, String.format("Uninstalling %s on %s", packageName, + testDevice.getSerialNumber())); + testDevice.uninstallPackage(packageName); + } + } +} diff --git a/tools/utils/afwtest/__init__.py b/tools/utils/afwtest/__init__.py new file mode 100644 index 0000000..7391cf0 --- /dev/null +++ b/tools/utils/afwtest/__init__.py @@ -0,0 +1,19 @@ +#!/usr/bin/python2.4 + +# Copyright (C) 2009 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 initialization for the afw-test package.""" + +__all__ = ['tools'] diff --git a/tools/utils/afwtest/tools.py b/tools/utils/afwtest/tools.py new file mode 100644 index 0000000..3b352ce --- /dev/null +++ b/tools/utils/afwtest/tools.py @@ -0,0 +1,251 @@ +#!/usr/bin/python2.4 + +# Copyright (C) 2016 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. + +"""Utility classes for AfW Test.""" + +import os +import re +import xml.dom.minidom as minidom + + +class TestPackage(object): + """This class represents a test package. + + Each test package consists of one or more suites, each containing one or more subsuites and/or + one or more test cases. Each test case contains one or more tests. + + The package structure is currently stored using Python dictionaries and lists. Translation + to XML is done via a DOM tree that gets created on demand. + + TODO: Instead of using an internal data structure, using a DOM tree directly would increase + the usability. For example, one could easily create an instance by parsing an existing XML. + """ + + class TestSuite(object): + """A test suite.""" + + def __init__(self, is_root=False): + self.is_root = is_root + self.test_cases = {} + self.test_suites = {} + + def Add(self, names): + if len(names) == 2: + # names contains the names of the test case and the test + test_case = self.test_cases.setdefault(names[0], []) + test_case.append(names[1]) + else: + sub_suite = self.test_suites.setdefault(names[0], TestPackage.TestSuite()) + sub_suite.Add(names[1:]) + + def WriteDescription(self, doc, parent): + """Recursively append all suites and testcases to the parent tag.""" + for (suite_name, suite) in self.test_suites.iteritems(): + child = doc.createElement('TestSuite') + child.setAttribute('name', suite_name) + parent.appendChild(child) + # recurse into child suites + suite.WriteDescription(doc, child) + for (case_name, test_list) in self.test_cases.iteritems(): + child = doc.createElement('TestCase') + child.setAttribute('name', case_name) + parent.appendChild(child) + for test_name in test_list: + test = doc.createElement('Test') + test.setAttribute('name', test_name) + child.appendChild(test) + + def __init__(self, package_name, app_package_name=''): + self.encoding = 'UTF-8' + self.attributes = {'name': package_name, 'AndroidFramework': 'Android 1.0', + 'version': '1.0', 'targetNameSpace': '', 'targetBinaryName': '', + 'jarPath': '', 'appPackageName': app_package_name} + self.root_suite = self.TestSuite(is_root=True) + + def AddTest(self, name): + """Add a test to the package. + + Test names are given in the form "testSuiteName.testSuiteName.TestCaseName.testName". + Test suites can be nested to any depth. + + Args: + name: The name of the test to add. + """ + parts = name.split('.') + self.root_suite.Add(parts) + + def AddAttribute(self, name, value): + """Add an attribute to the test package itself.""" + self.attributes[name] = value + + def GetDocument(self): + """Returns a minidom Document representing the test package structure.""" + doc = minidom.Document() + package = doc.createElement('TestPackage') + for (attr, value) in self.attributes.iteritems(): + package.setAttribute(attr, value) + self.root_suite.WriteDescription(doc, package) + doc.appendChild(package) + return doc + + def WriteDescription(self, writer): + """Write the description as XML to the given writer.""" + doc = self.GetDocument() + doc.writexml(writer, addindent=' ', newl='\n', encoding=self.encoding) + doc.unlink() + + +class TestPlan(object): + """A afw-test test plan generator.""" + + def __init__(self, name, all_packages): + """Instantiate a test plan with a list of available package names. + + Args: + name: The name of the test plan + all_packages: The full list of available packages. Subsequent calls to Exclude() and + Include() select from the packages given here. + """ + self.name = name + self.all_packages = all_packages + self.map = None + + def GetName(self): + """Gets the name of the test plan. + + Returns: + The plan name. + """ + + return self.name + + def Exclude(self, pattern): + """Exclude all packages matching the given regular expression from the plan. + + If this is the first call to Exclude() or Include(), all packages are selected before applying + the exclusion. + + Args: + pattern: A regular expression selecting the package names to exclude. + """ + if not self.map: + self.Include('.*') + exp = re.compile(pattern) + for package in self.all_packages: + if exp.match(package): + self.map[package] = False + + def Include(self, pattern): + """Include all packages matching the given regular expressions in the plan. + + Args: + pattern: A regular expression selecting the package names to include. + """ + if not self.map: + self.map = {} + for package in self.all_packages: + self.map[package] = False + exp = re.compile(pattern) + for package in self.all_packages: + if exp.match(package): + self.map[package] = True + + def Write(self, file_name): + """Write the test plan to the given file. + + Requires Include() or Exclude() to be called prior to calling this method. + + Args: + file_name: The name of the file into which the test plan should be written. + """ + doc = minidom.Document() + plan = doc.createElement('TestPlan') + plan.setAttribute('version', '1.0') + doc.appendChild(plan) + for package in self.all_packages: + if self.map[package]: + entry = doc.createElement('Entry') + entry.setAttribute('name', package) + plan.appendChild(entry) + stream = open(file_name, 'w') + doc.writexml(stream, addindent=' ', newl='\n', encoding='UTF-8') + stream.close() + +def GetTestPlans(test_plan_def_file, test_repository): + """Gets all test plans from a test plan definition file. + + Args: + test_plan_def_file: The file path of the test plan definitions. + test_repository: The directory containing the test packages and their package descriptions. + + Returns: + List of TestPlan. + """ + + plan_def = minidom.parse(test_plan_def_file) + plans = [] + + for plan in plan_def.getElementsByTagName('plan'): + descriptions = [] + plan_name = plan.getAttribute('name') + for p in plan.getElementsByTagName('package'): + pkg_name = p.getAttribute('name') + descriptions.append(os.path.join(test_repository, pkg_name + '.xml')) + + packages = [] + for description in descriptions: + doc = XmlFile(description) + packages.append(doc.GetAttr('TestPackage', 'appPackageName')) + + test_plan = TestPlan(plan_name, packages); + test_plan.Include('.*') + plans.append(test_plan) + + return plans + + +class XmlFile(object): + """This class parses Xml files and allows reading attribute values by tag and attribute name.""" + + def __init__(self, path): + """Instantiate the class using the manifest file denoted by path.""" + self.doc = minidom.parse(path) + + def GetAndroidAttr(self, tag, attr_name): + """Get the value of the given attribute in the first matching tag. + + Args: + tag: The name of the tag to search. + attr_name: An attribute name in the android manifest namespace. + + Returns: + The value of the given attribute in the first matching tag. + """ + element = self.doc.getElementsByTagName(tag)[0] + return element.getAttributeNS('http://schemas.android.com/apk/res/android', attr_name) + + def GetAttr(self, tag, attr_name): + """Return the value of the given attribute in the first matching tag. + + Args: + tag: The name of the tag to search. + attr_name: An attribute name in the default namespace. + + Returns: + The value of the given attribute in the first matching tag. + """ + element = self.doc.getElementsByTagName(tag)[0] + return element.getAttribute(attr_name) diff --git a/tools/utils/buildAfwTest.py b/tools/utils/buildAfwTest.py new file mode 100755 index 0000000..d32719e --- /dev/null +++ b/tools/utils/buildAfwTest.py @@ -0,0 +1,104 @@ +#!/usr/bin/python + +# Copyright (C) 2016 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. + +"""Module for generating AfW test descriptions and test plans.""" + +import glob +import os +import re +import subprocess +import sys +import xml.dom.minidom as dom +from afwtest import tools +from multiprocessing import Pool + +def GetSubDirectories(root): + """Return all directories under the given root directory.""" + return [x for x in os.listdir(root) if os.path.isdir(os.path.join(root, x))] + + +def GetMakeFileVars(makefile_path): + """Extracts variable definitions from the given make file. + + Args: + makefile_path: Path to the make file. + + Returns: + A dictionary mapping variable names to their assigned value. + """ + result = {} + pattern = re.compile(r'^\s*([^:#=\s]+)\s*:=\s*(.*?[^\\])$', re.MULTILINE + re.DOTALL) + stream = open(makefile_path, 'r') + content = stream.read() + for match in pattern.finditer(content): + result[match.group(1)] = match.group(2) + stream.close() + return result + + +class AfwTestBuilder(object): + """Main class for generating test descriptions and test plans.""" + + def __init__(self, argv): + """Initialize the AfwTestBuilder from command line arguments.""" + if not len(argv) == 6: + print 'Usage: %s <testRoot> <xtsOutputDir> <tempDir> <androidRootDir> <planDefFile>' % argv[0] + print '' + print 'testRoot: Directory under which to search for AfW tests.' + print 'afwtestOutputDir: Directory in which the AfW Test repository should be created.' + print 'tempDir: Directory to use for storing temporary files.' + print 'androidRootDir: Root directory of the Android source tree.' + print 'planDefFile: test plan definition file' + sys.exit(1) + self.test_root = sys.argv[1] + self.out_dir = sys.argv[2] + self.temp_dir = sys.argv[3] + self.android_root = sys.argv[4] + self.plan_def_file = sys.argv[5] + + self.test_repository = os.path.join(self.out_dir, 'repository/testcases') + self.plan_repository = os.path.join(self.out_dir, 'repository/plans') + + def GenerateTestDescriptions(self): + """Generate test descriptions for all packages.""" + pool = Pool(processes=2) + + # generate test descriptions for android tests + results = [] + pool.close() + pool.join() + return sum(map(lambda result: result.get(), results)) + + def __WritePlan(self, plan): + print 'Generating test plan %s' % plan.GetName() + plan.Write(os.path.join(self.plan_repository, plan.GetName() + '.xml')) + + def GenerateTestPlans(self): + """Generate test plans.""" + + test_plans = tools.GetTestPlans(self.plan_def_file, self.test_repository) + for plan in test_plans: + self.__WritePlan(plan) + +def LogGenerateDescription(name): + print 'Generating test description for package %s' % name + +if __name__ == '__main__': + builder = AfwTestBuilder(sys.argv) + result = builder.GenerateTestDescriptions() + if result != 0: + sys.exit(result) + builder.GenerateTestPlans() |