Snap for 9679998 from 75c1d6970331a21bb5780b5abfc5c2443393ed80 to sdk-release
Change-Id: I90baae1dad065d66fcf8b285dab075f9acbabfec
diff --git a/Android.bp b/Android.bp
index face49d..cbe5c43 100644
--- a/Android.bp
+++ b/Android.bp
@@ -34,6 +34,7 @@
include_dirs: ["external/protobuf/src"],
type: "full",
},
+ // b/267831518: Pin tradefed and dependencies to Java 11.
java_version: "11",
}
@@ -52,6 +53,7 @@
"guava",
"javax-annotation-api-prebuilt-host-jar",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
java_version: "11",
}
@@ -75,6 +77,7 @@
"guava",
"javax-annotation-api-prebuilt-host-jar",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
java_version: "11",
}
@@ -93,6 +96,7 @@
"guava",
"javax-annotation-api-prebuilt-host-jar",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
java_version: "11",
}
@@ -111,6 +115,7 @@
"guava",
"javax-annotation-api-prebuilt-host-jar",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
java_version: "11",
}
@@ -124,13 +129,15 @@
java_resources: [
":TradefedContentProvider",
":TelephonyUtility",
- ":WifiUtil"
+ ":WifiUtil",
+ ":test-services.apk",
],
static_libs: [
"tradefed-lib-core",
"tradefed-test-framework",
],
manifest: "MANIFEST.mf",
+ // b/267831518: Pin tradefed and dependencies to Java 11.
java_version: "11",
}
@@ -148,6 +155,7 @@
"tradefed-test-framework",
],
manifest: "MANIFEST.mf",
+ // b/267831518: Pin tradefed and dependencies to Java 11.
java_version: "11",
}
@@ -202,6 +210,7 @@
libs: [
"loganalysis",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
java_version: "11",
}
diff --git a/aoa_helper/Android.bp b/aoa_helper/Android.bp
index 5493761..73cacf3 100644
--- a/aoa_helper/Android.bp
+++ b/aoa_helper/Android.bp
@@ -29,12 +29,16 @@
static_libs: [
"jna-prebuilt",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
}
java_test_host {
name: "aoa-helper-test",
visibility: ["//visibility:private"],
srcs: ["javatests/**/*.java"],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
test_options: {
unit_test: true,
},
diff --git a/clearcut_client/Android.bp b/clearcut_client/Android.bp
index 6e37229..34bba40 100644
--- a/clearcut_client/Android.bp
+++ b/clearcut_client/Android.bp
@@ -34,4 +34,6 @@
"tradefed-common-util",
"devtools-annotations-prebuilt",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
}
diff --git a/common_util/Android.bp b/common_util/Android.bp
index 3a60e1f..f4abe34 100644
--- a/common_util/Android.bp
+++ b/common_util/Android.bp
@@ -27,6 +27,7 @@
"//tools/tradefederation/core/test_result_interfaces",
],
defaults: ["tradefed_defaults"],
+ java_version: "11",
srcs: [
"com/**/*.java",
],
diff --git a/common_util/com/android/tradefed/invoker/logger/InvocationMetricLogger.java b/common_util/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
index 63cb72d..beb9c9d 100644
--- a/common_util/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
+++ b/common_util/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
@@ -45,8 +45,10 @@
AUTO_RETRY_TIME("auto_retry_time_ms", true),
BACKFILL_BUILD_INFO("backfill_build_info", false),
STAGE_TESTS_TIME("stage_tests_time_ms", true),
+ STAGE_REMOTE_TIME("stage_remote_time_ms", true),
STAGE_TESTS_BYTES("stage_tests_bytes", true),
STAGE_TESTS_INDIVIDUAL_DOWNLOADS("stage_tests_individual_downloads", true),
+ STAGE_UNDEFINED_DEPENDENCY("stage_undefined_dependency", true),
SERVER_REFERENCE("server_reference", false),
INSTRUMENTATION_RERUN_FROM_FILE("instrumentation_rerun_from_file", true),
INSTRUMENTATION_RERUN_SERIAL("instrumentation_rerun_serial", true),
@@ -165,6 +167,7 @@
CF_INSTANCE_COUNT("cf_instance_count", false),
CF_OXYGEN_SERVER_URL("cf_oxygen_server_url", false),
CF_OXYGEN_SESSION_ID("cf_oxygen_session_id", false),
+ CF_OXYGEN_VERSION("cf_oxygen_version", false),
CRASH_FAILURES("crash_failures", true),
UNCAUGHT_CRASH_FAILURES("uncaught_crash_failures", true),
TEST_CRASH_FAILURES("test_crash_failures", true),
@@ -218,6 +221,7 @@
MODULE_SETUP_PAIR("tf_module_setup_pair_timestamp", true),
MODULE_TEARDOWN_PAIR("tf_module_teardown_pair_timestamp", true),
+ STATUS_CHECKER_PAIR("status_checker_pair", true),
LAB_PREPARER_NOT_ILAB("lab_preparer_not_ilab", true),
TARGET_PREPARER_IS_ILAB("target_preparer_is_ilab", true),
@@ -246,9 +250,14 @@
// Ab downloader metrics
AB_DOWNLOAD_SIZE_ELAPSED_TIME("ab_download_size_elapsed_time", true),
+ DUPLICATE_MAPPING_DIFFERENT_OPTIONS("duplicate_mapping_different_options", true),
+
HAS_ANY_RUN_FAILURES("has_any_run_failures", false),
TOTAL_TEST_COUNT("total_test_count", true),
+ // Metrics to store Device failure signatures
+ DEVICE_ERROR_SIGNATURES("device_failure_signatures", true),
+
// Following are trace events also reporting as metrics
invocation_warm_up("invocation_warm_up", true),
dynamic_download("dynamic_download", true),
@@ -267,6 +276,7 @@
test_cleanup("test_cleanup", true),
log_and_release_device("log_and_release_device", true),
invocation_events_processing("invocation_events_processing", true),
+ stage_suite_test_artifacts("stage_suite_test_artifacts", true),
;
private final String mKeyName;
diff --git a/common_util/com/android/tradefed/invoker/tracing/ActiveTrace.java b/common_util/com/android/tradefed/invoker/tracing/ActiveTrace.java
index 2a495a2..9f52cfc 100644
--- a/common_util/com/android/tradefed/invoker/tracing/ActiveTrace.java
+++ b/common_util/com/android/tradefed/invoker/tracing/ActiveTrace.java
@@ -105,6 +105,13 @@
}
/**
+ * thread id of the thread that initiated the tracing.
+ */
+ public long reportingThreadId() {
+ return tid;
+ }
+
+ /**
* Very basic event reporting to do START / END of traces.
*
* @param categories Category associated with event
diff --git a/common_util/com/android/tradefed/invoker/tracing/CloseableTraceScope.java b/common_util/com/android/tradefed/invoker/tracing/CloseableTraceScope.java
index d9952f4..ca7d638 100644
--- a/common_util/com/android/tradefed/invoker/tracing/CloseableTraceScope.java
+++ b/common_util/com/android/tradefed/invoker/tracing/CloseableTraceScope.java
@@ -74,7 +74,7 @@
category, name, threadId, threadName, TrackEvent.Type.TYPE_SLICE_END);
Optional<InvocationMetricKey> optionalKey =
Enums.getIfPresent(InvocationMetricKey.class, name);
- if (optionalKey.isPresent()) {
+ if (optionalKey.isPresent() && Thread.currentThread().getId() == trace.reportingThreadId()) {
InvocationMetricLogger.addInvocationPairMetrics(
optionalKey.get(), startTime, System.currentTimeMillis());
}
diff --git a/common_util/com/android/tradefed/result/LogDataType.java b/common_util/com/android/tradefed/result/LogDataType.java
index d75d38e..2d78dc4 100644
--- a/common_util/com/android/tradefed/result/LogDataType.java
+++ b/common_util/com/android/tradefed/result/LogDataType.java
@@ -15,11 +15,8 @@
*/
package com.android.tradefed.result;
-/**
- * Represents the data type of log data.
- */
+/** Represents the data type of log data. */
public enum LogDataType {
-
TEXT("txt", "text/plain", false, true),
UIX("uix", "text/xml", false, true),
XML("xml", "text/xml", false, true),
@@ -74,7 +71,7 @@
CPU_INFO("txt", "text/plain", false, true), // dumpsys cpuinfo
JACOCO_CSV("csv", "text/csv", false, true), // JaCoCo coverage report in CSV format
JACOCO_XML("xml", "text/xml", false, true), // JaCoCo coverage report in XML format
- JACOCO_EXEC("exec", "application/octet-stream", false, false), //JaCoCo coverage execution file
+ JACOCO_EXEC("exec", "application/octet-stream", false, false), // JaCoCo coverage execution file
ATRACE("atr", "text/plain", true, false), // atrace -z format
KERNEL_TRACE("dat", "text/plain", false, false), // raw kernel ftrace buffer
DIR("", "text/plain", false, false),
@@ -92,6 +89,7 @@
true), // ScreenshotTest proto result
CUTTLEFISH_LOG("txt", "text/plain", true, true), // Log from cuttlefish instance
TOMBSTONEZ("zip", "application/zip", true, false),
+ BT_SNOOP_LOG("log", "application/octet-stream", false, false), // Bluetooth HCI snoop logs
/* Unknown file type */
UNKNOWN("dat", "text/plain", false, false);
diff --git a/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java b/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java
index e293cac..0a9d417 100644
--- a/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java
+++ b/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java
@@ -33,7 +33,9 @@
UNEXPECTED_MOBLY_CONFIG(530_010, FailureStatus.CUSTOMER_ISSUE),
UNEXPECTED_MOBLY_BEHAVIOR(530_011, FailureStatus.CUSTOMER_ISSUE),
HOST_COMMAND_FAILED(530_012, FailureStatus.CUSTOMER_ISSUE),
- TEST_PHASE_TIMED_OUT(530_013, FailureStatus.TIMED_OUT);
+ TEST_PHASE_TIMED_OUT(530_013, FailureStatus.TIMED_OUT),
+ TEST_FILTER_NEEDS_UPDATE(530_014, FailureStatus.SYSTEM_UNDER_TEST_CRASHED),
+ TEST_TIMEOUT(530_015, FailureStatus.TIMED_OUT);
private final long code;
private final @Nonnull FailureStatus status;
diff --git a/device_build_interfaces/Android.bp b/device_build_interfaces/Android.bp
index 3ebcf09..656b3bf 100644
--- a/device_build_interfaces/Android.bp
+++ b/device_build_interfaces/Android.bp
@@ -37,4 +37,6 @@
"tf-remote-client",
"tradefed-result-interfaces",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
}
diff --git a/device_build_interfaces/com/android/tradefed/device/INativeDevice.java b/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
index dded229..a14fe9c 100644
--- a/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
+++ b/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
@@ -476,15 +476,32 @@
throws DeviceNotAvailableException;
/**
- * Helper method which executes a fastboot command as a system command with a default timeout
- * of 2 minutes.
- * <p/>
- * Expected to be used when device is already in fastboot mode.
+ * Helper method which executes a adb command as a system command with a specified timeout.
+ *
+ * <p>{@link #executeShellCommand(String)} should be used instead wherever possible, as that
+ * method provides better failure detection and performance.
+ *
+ * @param timeout the time in milliseconds before the device is considered unresponsive, 0L for
+ * no timeout
+ * @param envMap environment to set for the command
+ * @param commandArgs the adb command and arguments to run
+ * @return the stdout from command. <code>null</code> if command failed to execute.
+ * @throws DeviceNotAvailableException if connection with device is lost and cannot be
+ * recovered.
+ */
+ public String executeAdbCommand(long timeout, Map<String, String> envMap, String... commandArgs)
+ throws DeviceNotAvailableException;
+
+ /**
+ * Helper method which executes a fastboot command as a system command with a default timeout of
+ * 2 minutes.
+ *
+ * <p>Expected to be used when device is already in fastboot mode.
*
* @param commandArgs the fastboot command and arguments to run
* @return the CommandResult containing output of command
* @throws DeviceNotAvailableException if connection with device is lost and cannot be
- * recovered.
+ * recovered.
*/
public CommandResult executeFastbootCommand(String... commandArgs)
throws DeviceNotAvailableException;
diff --git a/device_build_interfaces/com/android/tradefed/device/UserInfo.java b/device_build_interfaces/com/android/tradefed/device/UserInfo.java
index 853f1a0..cac498c 100644
--- a/device_build_interfaces/com/android/tradefed/device/UserInfo.java
+++ b/device_build_interfaces/com/android/tradefed/device/UserInfo.java
@@ -30,6 +30,7 @@
public static final int FLAG_EPHEMERAL = 0x00000100;
public static final int FLAG_MANAGED_PROFILE = 0x00000020;
public static final int USER_SYSTEM = 0;
+ public static final int FLAG_MAIN = 0x00004000;
public static final int FLAGS_NOT_SECONDARY =
FLAG_PRIMARY | FLAG_MANAGED_PROFILE | FLAG_GUEST | FLAG_RESTRICTED;
@@ -51,6 +52,11 @@
PRIMARY,
/** system user = user 0 */
SYSTEM,
+ /**
+ * user flagged as main user on the device; on non-hsum main user = system user = user 0 on
+ * hsum main user = first human user.
+ */
+ MAIN,
/** secondary user, i.e. non-primary and non-system. */
SECONDARY,
/** managed profile user, e.g. work profile. */
@@ -72,6 +78,10 @@
return this == SYSTEM;
}
+ public boolean isMain() {
+ return this == MAIN;
+ }
+
public boolean isSecondary() {
return this == SECONDARY;
}
@@ -120,6 +130,10 @@
return mUserId == USER_SYSTEM;
}
+ public boolean isMain() {
+ return (mFlag & FLAG_MAIN) == FLAG_MAIN;
+ }
+
public boolean isManagedProfile() {
return (mFlag & FLAG_MANAGED_PROFILE) == FLAG_MANAGED_PROFILE;
}
@@ -139,6 +153,8 @@
return isPrimary();
case SYSTEM:
return isSystem();
+ case MAIN:
+ return isMain();
case SECONDARY:
return isSecondary();
case MANAGED_PROFILE:
diff --git a/external_dependencies/Android.bp b/external_dependencies/Android.bp
index 54fed3d..834e8ee 100644
--- a/external_dependencies/Android.bp
+++ b/external_dependencies/Android.bp
@@ -21,4 +21,6 @@
srcs: [
"com/**/*.java",
],
-}
\ No newline at end of file
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
+}
diff --git a/invocation_interfaces/Android.bp b/invocation_interfaces/Android.bp
index 8d86706..94e9e50 100644
--- a/invocation_interfaces/Android.bp
+++ b/invocation_interfaces/Android.bp
@@ -34,4 +34,6 @@
"tradefed-result-interfaces",
"tradefed-device-build-interfaces",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
}
diff --git a/invocation_interfaces/com/android/tradefed/invoker/ExecutionProperties.java b/invocation_interfaces/com/android/tradefed/invoker/ExecutionProperties.java
index 0c058c1..906fadf 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/ExecutionProperties.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/ExecutionProperties.java
@@ -117,4 +117,9 @@
public void clear() {
mProperties.clear();
}
+
+ @Override
+ public String toString() {
+ return "ExecutionProperties: " + mProperties;
+ }
}
diff --git a/invocation_interfaces/com/android/tradefed/invoker/TestInformation.java b/invocation_interfaces/com/android/tradefed/invoker/TestInformation.java
index 12360ae..f2748be 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/TestInformation.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/TestInformation.java
@@ -18,6 +18,8 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.util.FileUtil;
import java.io.File;
@@ -232,6 +234,8 @@
// approach to do individual download from remote artifact.
// Try to stage the files from remote zip files.
file = getBuildInfo().stageRemoteFile(fileName, testsDir);
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.STAGE_UNDEFINED_DEPENDENCY, fileName);
}
return file;
}
diff --git a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
index ecfcded..e12a855 100644
--- a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
+++ b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
@@ -119,7 +119,7 @@
}
/** Gets the set of tests in given statuses. */
- private Set<TestDescription> getTestsInState(List<TestStatus> statuses) {
+ public Set<TestDescription> getTestsInState(List<TestStatus> statuses) {
Set<TestDescription> tests = new LinkedHashSet<>();
for (Map.Entry<TestDescription, TestResult> testEntry : getTestResults().entrySet()) {
TestStatus status = testEntry.getValue().getStatus();
diff --git a/isolation/Android.bp b/isolation/Android.bp
index 8c9a77f..4e746f6 100644
--- a/isolation/Android.bp
+++ b/isolation/Android.bp
@@ -32,6 +32,8 @@
"commons-cli-1.2",
],
jarjar_rules: "jarjar_rules.txt",
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
}
java_library_host {
@@ -50,4 +52,6 @@
include_dirs: ["external/protobuf/src"],
type: "full",
},
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
}
diff --git a/javatests/Android.bp b/javatests/Android.bp
index 92c0324..1a8344f 100644
--- a/javatests/Android.bp
+++ b/javatests/Android.bp
@@ -20,6 +20,8 @@
name: "tradefed-test-protos",
visibility: ["//visibility:private"],
srcs: ["res/**/*.proto"],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
libs: [
"libprotobuf-java-full",
],
@@ -36,6 +38,9 @@
// Only compile source java files in this lib.
srcs: ["com/**/*.java"],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
+
java_resource_dirs: ["res"],
java_resources: [
":SimpleFailingTest",
diff --git a/javatests/com/android/tradefed/UnitTests.java b/javatests/com/android/tradefed/UnitTests.java
index ba416ee..47950b8 100644
--- a/javatests/com/android/tradefed/UnitTests.java
+++ b/javatests/com/android/tradefed/UnitTests.java
@@ -108,6 +108,7 @@
import com.android.tradefed.device.metric.AtraceRunMetricCollectorTest;
import com.android.tradefed.device.metric.AutoLogCollectorTest;
import com.android.tradefed.device.metric.BaseDeviceMetricCollectorTest;
+import com.android.tradefed.device.metric.BluetoothHciSnoopLogCollectorTest;
import com.android.tradefed.device.metric.BugreportzOnFailureCollectorTest;
import com.android.tradefed.device.metric.BugreportzOnTestCaseFailureCollectorTest;
import com.android.tradefed.device.metric.ClangCodeCoverageCollectorTest;
@@ -282,6 +283,7 @@
import com.android.tradefed.targetprep.TestAppInstallSetupTest;
import com.android.tradefed.targetprep.TestFilePushSetupTest;
import com.android.tradefed.targetprep.UserCleanerTest;
+import com.android.tradefed.targetprep.VisibleBackgroundUserPreparerTest;
import com.android.tradefed.targetprep.adb.AdbStopServerPreparerTest;
import com.android.tradefed.targetprep.app.NoApkTestSkipperTest;
import com.android.tradefed.targetprep.multi.MergeMultiBuildTargetPreparerTest;
@@ -369,6 +371,7 @@
import com.android.tradefed.testtype.suite.params.ModuleParametersHelperTest;
import com.android.tradefed.testtype.suite.params.RunOnSdkSandboxHandlerTest;
import com.android.tradefed.testtype.suite.params.SecondaryUserHandlerTest;
+import com.android.tradefed.testtype.suite.params.SecondaryUserOnSecondaryDisplayHandlerTest;
import com.android.tradefed.testtype.suite.params.multiuser.RunOnSecondaryUserParameterHandlerTest;
import com.android.tradefed.testtype.suite.params.multiuser.RunOnWorkProfileParameterHandlerTest;
import com.android.tradefed.testtype.suite.retry.ResultsPlayerTest;
@@ -595,6 +598,7 @@
AtraceRunMetricCollectorTest.class,
AutoLogCollectorTest.class,
BaseDeviceMetricCollectorTest.class,
+ BluetoothHciSnoopLogCollectorTest.class,
BugreportzOnTestCaseFailureCollectorTest.class,
BugreportzOnFailureCollectorTest.class,
ClangCodeCoverageCollectorTest.class,
@@ -742,6 +746,7 @@
AoaTargetPreparerTest.class,
AppSetupTest.class,
BaseTargetPreparerTest.class,
+ VisibleBackgroundUserPreparerTest.class,
CreateUserPreparerTest.class,
DefaultTestsZipInstallerTest.class,
DeviceFlashPreparerTest.class,
@@ -928,6 +933,7 @@
RunOnSecondaryUserParameterHandlerTest.class,
RunOnWorkProfileParameterHandlerTest.class,
SecondaryUserHandlerTest.class,
+ SecondaryUserOnSecondaryDisplayHandlerTest.class,
// testtype/suite/retry
ResultsPlayerTest.class,
diff --git a/javatests/com/android/tradefed/device/DeviceStateMonitorTest.java b/javatests/com/android/tradefed/device/DeviceStateMonitorTest.java
index 04ae37d..c521d0c 100644
--- a/javatests/com/android/tradefed/device/DeviceStateMonitorTest.java
+++ b/javatests/com/android/tradefed/device/DeviceStateMonitorTest.java
@@ -197,7 +197,7 @@
return new CollectingOutputReceiver() {
@Override
public String getOutput() {
- return "/system/bin/adb";
+ return "uid=0(root)";
}
};
}
@@ -231,7 +231,7 @@
@Override
public String getOutput() {
if (mAtomicBoolean.get()) {
- return "/system/bin/adb";
+ return "uid=0(root)";
}
return "not found";
}
diff --git a/javatests/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java b/javatests/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
index b41b484..55d683f 100644
--- a/javatests/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
+++ b/javatests/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
@@ -96,7 +96,7 @@
}
@Override
- IRunUtil createRunUtil() {
+ protected IRunUtil createRunUtil() {
Assert.assertNotNull("Unexpected method call to createRunUtil.", currentRunUtil);
IRunUtil returnValue = currentRunUtil;
currentRunUtil = null;
diff --git a/javatests/com/android/tradefed/device/TestDeviceFuncTest.java b/javatests/com/android/tradefed/device/TestDeviceFuncTest.java
index 2c7f4b8..854af8e 100644
--- a/javatests/com/android/tradefed/device/TestDeviceFuncTest.java
+++ b/javatests/com/android/tradefed/device/TestDeviceFuncTest.java
@@ -44,7 +44,6 @@
import com.android.tradefed.util.ProcessInfo;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.StreamUtil;
-import android.platform.test.annotations.FlakyTest;
import org.junit.Assume;
import org.junit.Before;
@@ -665,7 +664,6 @@
}
/** Test device soft-restart detection API. */
- @FlakyTest
@Test
public void testDeviceSoftRestart() throws DeviceNotAvailableException {
CLog.i("testDeviceSoftRestartSince");
diff --git a/javatests/com/android/tradefed/device/cloud/GceAvdInfoTest.java b/javatests/com/android/tradefed/device/cloud/GceAvdInfoTest.java
index f4dc517..18624ac 100644
--- a/javatests/com/android/tradefed/device/cloud/GceAvdInfoTest.java
+++ b/javatests/com/android/tradefed/device/cloud/GceAvdInfoTest.java
@@ -488,6 +488,7 @@
+ " random_key:\"this-is-12345678\"\n"
+ " leased_device_spec:{type:TESTTYPE build_artifacts:{build_id:\"P1234567\""
+ " build_target:\"target\" build_branch:\"testBranch\"}}"
+ + " oxygen_version:\"v20220509-0008-rc01-cl447382102\" "
+ " debug_info:{reserved_cores:1 region:\"test-region\" environment:\"test\"}";
CommandResult res = Mockito.mock(CommandResult.class);
Mockito.doAnswer(
diff --git a/javatests/com/android/tradefed/device/cloud/OxygenUtilTest.java b/javatests/com/android/tradefed/device/cloud/OxygenUtilTest.java
index 80784d8..f62a1b2 100644
--- a/javatests/com/android/tradefed/device/cloud/OxygenUtilTest.java
+++ b/javatests/com/android/tradefed/device/cloud/OxygenUtilTest.java
@@ -34,6 +34,7 @@
import org.mockito.Mockito;
import java.io.File;
+import java.util.List;
/** Unit tests for {@link OxygenUtil}. */
@RunWith(JUnit4.class)
@@ -88,4 +89,51 @@
OxygenUtil.getDefaultLogType("invocation_started_bugreport_123456.txt"),
LogDataType.BUGREPORT);
}
+
+ /** Test collectErrorSignatures. */
+ @Test
+ public void testCollectErrorSignatures() throws Exception {
+ File tmpDir = null;
+ try {
+ tmpDir = FileUtil.createTempDir("logs");
+ File file1 = FileUtil.createTempFile("launcher.log", ".randomstring", tmpDir);
+ String content =
+ "some content\n"
+ + "some Address already in use\n"
+ + "some vcpu hw run failure: 0x7.\n"
+ + "tailing string";
+ FileUtil.writeToFile(content, file1);
+ List<String> signatures = OxygenUtil.collectErrorSignatures(tmpDir);
+ assertEquals("crosvm_vcpu_hw_run_failure_7", signatures.get(0));
+ assertEquals("launch_cvd_port_collision", signatures.get(1));
+ } finally {
+ FileUtil.recursiveDelete(tmpDir);
+ }
+ }
+
+ /** Test collectDeviceLaunchMetrics. */
+ @Test
+ public void testCollectDeviceLaunchMetrics() throws Exception {
+ File tmpDir = null;
+ try {
+ tmpDir = FileUtil.createTempDir("logs");
+ File file1 = FileUtil.createTempFile("vdl_stdout.txt", ".randomstring", tmpDir);
+ String content =
+ "some content\n2023/02/09 21:25:25 launch_cvd exited."
+ + "2023/02/09 21:25:30 Ended At | Duration | Event Name\n"
+ + "2023/02/09 21:25:30 62.21 | 0.00 | SetupDependencies\n"
+ + "2023/02/09 21:25:30 62.55 | 0.33 | CuttlefishCommon\n"
+ + "2023/02/09 21:25:30 186.84 | 124.63 | LaunchDevice\n"
+ + "2023/02/09 21:25:30 186.84 | 186.84 |"
+ + " CuttlefishLauncherMainstart\n"
+ + "tailing string";
+ FileUtil.writeToFile(content, file1);
+ long[] metrics = OxygenUtil.collectDeviceLaunchMetrics(tmpDir);
+ assertEquals(61880, metrics[0]);
+ assertEquals(124630, metrics[1]);
+
+ } finally {
+ FileUtil.recursiveDelete(tmpDir);
+ }
+ }
}
diff --git a/javatests/com/android/tradefed/device/metric/BluetoothHciSnoopLogCollectorTest.java b/javatests/com/android/tradefed/device/metric/BluetoothHciSnoopLogCollectorTest.java
new file mode 100644
index 0000000..27a68db
--- /dev/null
+++ b/javatests/com/android/tradefed/device/metric/BluetoothHciSnoopLogCollectorTest.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2023 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.tradefed.device.metric;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.ddmlib.IDevice;
+import com.android.tradefed.config.ConfigurationDef;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.TestDeviceState;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.proto.TfMetricProtoUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.HashMap;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public final class BluetoothHciSnoopLogCollectorTest {
+ private BluetoothHciSnoopLogCollector mCollector;
+ @Mock ITestInvocationListener mMockListener;
+ private IInvocationContext mContext;
+ @Mock ITestDevice mMockDevice;
+ @Mock IDevice mMockIDevice;
+ private List<ITestDevice> mDevices;
+ private ITestInvocationListener listener;
+
+ private static final String TEST_RUN_NAME = "runName";
+ private static final int TEST_RUN_COUNT = 1;
+ private static final long TEST_START_TIME = 0;
+ private static final long TEST_END_TIME = 50;
+ private static final long TEST_RUN_END_TIME = 100;
+ private static final String PULL_DIRECTORY = "/data/misc/bluetooth/testreporting";
+ private static final String PULL_DIRECTORY_BASENAME = "testreporting";
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = Mockito.spy(new InvocationContext());
+ mMockDevice = Mockito.mock(ITestDevice.class);
+ mDevices = List.of(mMockDevice);
+ doReturn(mDevices).when(mContext).getDevices();
+
+ mContext.addAllocatedDevice(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockDevice);
+ when(mContext.getDevices()).thenReturn(mDevices);
+ mCollector = Mockito.spy(new BluetoothHciSnoopLogCollector());
+ doNothing()
+ .when(mCollector)
+ .executeShellCommand(Mockito.any(ITestDevice.class), Mockito.anyString());
+ doReturn("/data/misc/bluetooth/testreporting").when(mCollector).getReportingDir();
+ when(mMockDevice.getCurrentUser()).thenReturn(0);
+ when(mMockDevice.getIDevice()).thenReturn(mMockIDevice);
+ when(mMockDevice.setProperty(Mockito.anyString(), Mockito.anyString())).thenReturn(true);
+ when(mMockDevice.executeShellV2Command(Mockito.anyString()))
+ .thenReturn(new CommandResult(CommandStatus.SUCCESS));
+ when(mMockDevice.getDeviceState()).thenReturn(TestDeviceState.ONLINE);
+
+ listener = mCollector.init(mContext, mMockListener);
+ }
+
+ /**
+ * Test that if the pattern of a metric match the requested pattern we attempt to pull it as a
+ * log file.
+ */
+ @Test
+ public void testPullFileAndLog() throws Exception {
+ // Pattern of file(s)/dir(s) to pull.
+ String pullPatternKey = "log1";
+ String pullPatternPath = "/data/local/tmp/log1.txt";
+
+ // Set up the metric collector's param.
+ OptionSetter setter = new OptionSetter(mCollector);
+ setter.setOptionValue("pull-pattern-keys", pullPatternKey);
+ setter.setOptionValue("clean-up", "true");
+ setter.setOptionValue("collect-on-run-ended-only", "false");
+
+ HashMap<String, Metric> metrics = new HashMap<>();
+ metrics.put(pullPatternKey, TfMetricProtoUtil.stringToMetric(pullPatternPath));
+ metrics.put("another_metrics", TfMetricProtoUtil.stringToMetric("57"));
+
+ ArgumentCaptor<HashMap<String, Metric>> capture = ArgumentCaptor.forClass(HashMap.class);
+
+ when(mMockDevice.pullFile(Mockito.eq(pullPatternPath), Mockito.eq(0)))
+ .thenReturn(new File("file"));
+
+ TestDescription test = new TestDescription("class", "test");
+ listener.testRunStarted(TEST_RUN_NAME, TEST_RUN_COUNT);
+ listener.testStarted(test, TEST_START_TIME);
+ listener.testEnded(test, TEST_END_TIME, metrics);
+ listener.testRunEnded(TEST_RUN_END_TIME, metrics);
+
+ verify(mMockListener)
+ .testRunStarted(
+ Mockito.eq(TEST_RUN_NAME),
+ Mockito.eq(TEST_RUN_COUNT),
+ Mockito.eq(0),
+ Mockito.anyLong());
+ verify(mMockListener).testStarted(test, TEST_START_TIME);
+ verify(mMockDevice).deleteFile(pullPatternPath);
+ verify(mMockDevice).pullFile(Mockito.eq(pullPatternPath), Mockito.anyInt());
+ verify(mCollector).retrieveFile(Mockito.any(), Mockito.anyString(), Mockito.anyInt());
+ verify(mMockListener)
+ .testEnded(Mockito.eq(test), Mockito.eq(TEST_END_TIME), capture.capture());
+ verify(mMockListener).testRunEnded(TEST_RUN_END_TIME, metrics);
+ HashMap<String, Metric> metricCaptured = capture.getValue();
+ assertEquals(
+ "57", metricCaptured.get("another_metrics").getMeasurements().getSingleString());
+ assertEquals(
+ pullPatternPath,
+ metricCaptured.get(pullPatternKey).getMeasurements().getSingleString());
+ }
+
+ /** Test that we attempt to pull metrics (in the watched directory) as a log file. */
+ @Test
+ public void testPullDirMultipleSnoopLogs() throws Exception {
+ // Dir to pull.
+ String pullDirectoryPath = "/tmp/my/dir";
+
+ // Set up the metric collector's param.
+ OptionSetter setter = new OptionSetter(mCollector);
+ setter.setOptionValue("directory-keys", pullDirectoryPath);
+ setter.setOptionValue("clean-up", "true");
+ setter.setOptionValue("collect-on-run-ended-only", "false");
+
+ HashMap<String, Metric> metrics = new HashMap<>();
+ metrics.put("another_metrics", TfMetricProtoUtil.stringToMetric("57"));
+
+ ArgumentCaptor<HashMap<String, Metric>> capture = ArgumentCaptor.forClass(HashMap.class);
+
+ // Inject a file into the temp destination directory on the host, to simulate the behaviour
+ // in FilePullerDeviceMetricCollector.pullMetricDirectory().
+ doAnswer(
+ invocation -> {
+ String keyDirectory = (String) invocation.getArgument(0);
+ File tmpDestDir = (File) invocation.getArgument(1);
+ Path logFileInTmpDestDir =
+ Files.createTempFile(
+ tmpDestDir.toPath(), "class-test-", ".log");
+
+ return true;
+ })
+ .when(mMockDevice)
+ .pullDir(Mockito.anyString(), Mockito.any(File.class));
+
+ listener.testRunStarted(TEST_RUN_NAME, TEST_RUN_COUNT);
+
+ TestDescription test1 = new TestDescription("class", "test1");
+ listener.testStarted(test1, TEST_START_TIME);
+ listener.testEnded(test1, TEST_END_TIME, metrics);
+
+ TestDescription test2 = new TestDescription("class", "test2");
+ listener.testStarted(test2, TEST_START_TIME);
+ listener.testEnded(test2, TEST_END_TIME, metrics);
+
+ TestDescription test3 = new TestDescription("class", "test3");
+ listener.testStarted(test3, TEST_START_TIME);
+ listener.testEnded(test3, TEST_END_TIME, metrics);
+
+ listener.testRunEnded(TEST_RUN_END_TIME, metrics);
+
+ verify(mMockListener)
+ .testRunStarted(
+ Mockito.eq(TEST_RUN_NAME),
+ Mockito.eq(TEST_RUN_COUNT),
+ Mockito.eq(0),
+ Mockito.anyLong());
+
+ verify(mMockListener).testStarted(test1, TEST_START_TIME);
+ verify(mMockListener).testStarted(test2, TEST_START_TIME);
+ verify(mMockListener).testStarted(test3, TEST_START_TIME);
+ verify(mMockListener, times(3))
+ .testLog(Mockito.anyString(), Mockito.eq(LogDataType.BT_SNOOP_LOG), Mockito.any());
+ verify(mMockListener)
+ .testEnded(Mockito.eq(test1), Mockito.eq(TEST_END_TIME), capture.capture());
+ verify(mMockListener)
+ .testEnded(Mockito.eq(test2), Mockito.eq(TEST_END_TIME), capture.capture());
+ verify(mMockListener)
+ .testEnded(Mockito.eq(test3), Mockito.eq(TEST_END_TIME), capture.capture());
+ verify(mMockListener).testRunEnded(TEST_RUN_END_TIME, metrics);
+ HashMap<String, Metric> metricCaptured = capture.getValue();
+ assertEquals(
+ "57", metricCaptured.get("another_metrics").getMeasurements().getSingleString());
+ }
+}
diff --git a/javatests/com/android/tradefed/device/metric/ClangCodeCoverageCollectorTest.java b/javatests/com/android/tradefed/device/metric/ClangCodeCoverageCollectorTest.java
index 42afdee..5b7b951 100644
--- a/javatests/com/android/tradefed/device/metric/ClangCodeCoverageCollectorTest.java
+++ b/javatests/com/android/tradefed/device/metric/ClangCodeCoverageCollectorTest.java
@@ -22,6 +22,7 @@
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.times;
@@ -64,6 +65,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
@@ -197,6 +199,7 @@
public void testRun_logsCoverageFile() throws Exception {
mCoverageOptionsSetter.setOptionValue("coverage", "true");
mCoverageOptionsSetter.setOptionValue("coverage-toolchain", "CLANG");
+ mCoverageOptionsSetter.setOptionValue("pull-timeout", "314159");
// Setup mocks.
doReturn(true).when(mMockDevice).isAdbRoot();
@@ -216,6 +219,15 @@
mListener.testRunEnded(ELAPSED_TIME, mMetrics);
mListener.invocationEnded(ELAPSED_TIME);
+ // Verify the timeout is set.
+ verify(mMockDevice, times(1))
+ .executeShellV2Command(
+ eq("find /data/misc/trace -name '*.profraw' | tar -czf - -T - 2>/dev/null"),
+ any(),
+ any(),
+ eq(314159L),
+ eq(TimeUnit.MILLISECONDS),
+ eq(1));
// Verify that the command line contains the files above.
List<String> command = mCommandArgumentCaptor.getCommand();
checkListContainsSuffixes(
diff --git a/javatests/com/android/tradefed/device/metric/EmulatorMemoryCpuCapturerTest.java b/javatests/com/android/tradefed/device/metric/EmulatorMemoryCpuCapturerTest.java
index a347335..babc54e 100644
--- a/javatests/com/android/tradefed/device/metric/EmulatorMemoryCpuCapturerTest.java
+++ b/javatests/com/android/tradefed/device/metric/EmulatorMemoryCpuCapturerTest.java
@@ -18,20 +18,37 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.Mockito.doReturn;
+
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.IRunUtil;
+
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
@RunWith(JUnit4.class)
public class EmulatorMemoryCpuCapturerTest {
private EmulatorMemoryCpuCapturer mEmulatorMemoryCpuCapturer;
+ @Mock IRunUtil mMockRunUtil;
@Before
public void setUp() {
+ MockitoAnnotations.initMocks(this);
// just capture the current process'es id
- mEmulatorMemoryCpuCapturer = new EmulatorMemoryCpuCapturer(ProcessHandle.current().pid());
+ mEmulatorMemoryCpuCapturer =
+ new EmulatorMemoryCpuCapturer(ProcessHandle.current().pid()) {
+ @Override
+ protected IRunUtil getRunUtil() {
+ return mMockRunUtil;
+ }
+ };
}
@Test
@@ -67,10 +84,18 @@
/** functional test for getting cpu usage using the current java processes' pid. */
@Test
public void getCpuUsage() {
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ result.setStdout("%CPU\n35.4");
+ doReturn(result)
+ .when(mMockRunUtil)
+ .runTimedCmd(
+ Mockito.anyLong(),
+ Mockito.eq("ps"),
+ Mockito.eq("-o"),
+ Mockito.eq("%cpu"),
+ Mockito.eq("-p"),
+ Mockito.any());
float cpu = mEmulatorMemoryCpuCapturer.getCpuUsage();
- // arbitrarily check bounds to make sure returned value is reasonable
- assertThat(cpu).isGreaterThan(1);
- // ensure less than 2 GB memory
- assertThat(cpu).isLessThan(1000);
+ assertThat(cpu).isEqualTo(35.4F);
}
}
diff --git a/javatests/com/android/tradefed/device/metric/GcovKernelCodeCoverageCollectorTest.java b/javatests/com/android/tradefed/device/metric/GcovKernelCodeCoverageCollectorTest.java
index 0b1e2fd..987335e 100644
--- a/javatests/com/android/tradefed/device/metric/GcovKernelCodeCoverageCollectorTest.java
+++ b/javatests/com/android/tradefed/device/metric/GcovKernelCodeCoverageCollectorTest.java
@@ -20,9 +20,7 @@
import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.doReturn;
@@ -70,7 +68,6 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
-import java.util.concurrent.TimeUnit;
/** Unit tests for {@link GcovKernelCodeCoverageCollector}. */
@RunWith(JUnit4.class)
@@ -152,9 +149,25 @@
GcovKernelCodeCoverageCollector.MAKE_TEMP_DIR_COMMAND))
.thenReturn(successResultWithDir);
- // collectGcovDebugfsCoverage() gather coverage happy path: success
+ // collectGcovDebugfsCoverage() make gcda temp dir happy path: success
when(mMockDevice.executeShellV2Command(
- startsWith("find /d/gcov"), anyLong(), any(TimeUnit.class)))
+ startsWith(
+ GcovKernelCodeCoverageCollector.MAKE_GCDA_TEMP_DIR_COMMAND_FMT
+ .substring(0, 8))))
+ .thenReturn(mSuccessResult);
+
+ // collectGcovDebugfsCoverage() copy gcov data happy path: success
+ when(mMockDevice.executeShellV2Command(
+ startsWith(
+ GcovKernelCodeCoverageCollector.COPY_GCOV_DATA_COMMAND_FMT
+ .substring(0, 6))))
+ .thenReturn(mSuccessResult);
+
+ // collectGcovDebugfsCoverage() tar gcov data happy path: success
+ when(mMockDevice.executeShellV2Command(
+ startsWith(
+ GcovKernelCodeCoverageCollector.TAR_GCOV_DATA_COMMAND_FMT.substring(
+ 0, 8))))
.thenReturn(mSuccessResult);
// device.pullFile() happy path: always return a file with the given name
@@ -266,7 +279,7 @@
public void resetGcovCountsFail_noTar() throws Exception {
var moduleName = name.getMethodName();
- // Set mount command to fail
+ // Set reset command to fail
when(mMockDevice.executeShellV2Command(
GcovKernelCodeCoverageCollector.RESET_GCOV_COUNTS_COMMAND))
.thenReturn(mFailedResult);
@@ -279,7 +292,7 @@
public void makeTempDirFail_noTar() throws Exception {
var moduleName = name.getMethodName();
- // Set mount command to fail
+ // Set make temp dir command to fail
when(mMockDevice.executeShellV2Command(
GcovKernelCodeCoverageCollector.MAKE_TEMP_DIR_COMMAND))
.thenReturn(mFailedResult);
@@ -289,12 +302,44 @@
}
@Test
- public void gatherCoverageFail_noTar() throws Exception {
+ public void makeGcdaTempDirFail_noTar() throws Exception {
var moduleName = name.getMethodName();
- // Set mount command to fail
+ // Set make gcda temp dir command to fail
when(mMockDevice.executeShellV2Command(
- startsWith("find /d/gcov"), anyLong(), any(TimeUnit.class)))
+ startsWith(
+ GcovKernelCodeCoverageCollector.MAKE_GCDA_TEMP_DIR_COMMAND_FMT
+ .substring(0, 8))))
+ .thenReturn(mFailedResult);
+
+ configuredRun(List.of(moduleName), 1, false);
+ assertThat(mFakeListener.getLogs()).hasSize(0);
+ }
+
+ @Test
+ public void copyGcovDataFail_noTar() throws Exception {
+ var moduleName = name.getMethodName();
+
+ // Set copy gcov data command to fail
+ when(mMockDevice.executeShellV2Command(
+ startsWith(
+ GcovKernelCodeCoverageCollector.COPY_GCOV_DATA_COMMAND_FMT
+ .substring(0, 6))))
+ .thenReturn(mFailedResult);
+
+ configuredRun(List.of(moduleName), 1, false);
+ assertThat(mFakeListener.getLogs()).hasSize(0);
+ }
+
+ @Test
+ public void tarGcovDataFail_noTar() throws Exception {
+ var moduleName = name.getMethodName();
+
+ // Set tar gcov data command to fail
+ when(mMockDevice.executeShellV2Command(
+ startsWith(
+ GcovKernelCodeCoverageCollector.TAR_GCOV_DATA_COMMAND_FMT.substring(
+ 0, 8))))
.thenReturn(mFailedResult);
configuredRun(List.of(moduleName), 1, false);
diff --git a/javatests/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java b/javatests/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
index 0a6e3b6..77546bc 100644
--- a/javatests/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
+++ b/javatests/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
@@ -28,6 +28,7 @@
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;
@@ -168,6 +169,7 @@
@Test
public void testRunEnded_rootEnabled_logsCoverageMeasurement() throws Exception {
enableJavaCoverage();
+ mCoverageOptionsSetter.setOptionValue("pull-timeout", "314159");
// Setup mocks.
HashMap<String, Metric> runMetrics = createMetricsWithCoverageMeasurement(DEVICE_PATH);
@@ -181,6 +183,16 @@
mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, runMetrics);
+ // Verify timeout is set.
+ verify(mMockDevice, times(1))
+ .executeShellV2Command(
+ eq("find /data/misc/trace -name '*.ec' | tar -czf - -T - 2>/dev/null"),
+ any(),
+ any(),
+ eq(314159L),
+ eq(TimeUnit.MILLISECONDS),
+ eq(1));
+
// Verify testLog(..) was called with the coverage file.
verify(mFakeListener)
.testLog(anyString(), eq(LogDataType.COVERAGE), eq(COVERAGE_MEASUREMENT));
diff --git a/javatests/com/android/tradefed/device/metric/ShowmapPullerMetricCollectorTest.java b/javatests/com/android/tradefed/device/metric/ShowmapPullerMetricCollectorTest.java
index 17d3868..d19b728 100644
--- a/javatests/com/android/tradefed/device/metric/ShowmapPullerMetricCollectorTest.java
+++ b/javatests/com/android/tradefed/device/metric/ShowmapPullerMetricCollectorTest.java
@@ -280,6 +280,44 @@
}
@Test
+ public void testNoProcessFlow() throws Exception {
+ OptionSetter setter = new OptionSetter(mShowmapMetricCollector);
+ setter.setOptionValue("collect-on-run-ended-only", "false");
+ setter.setOptionValue("pull-pattern-keys", "showmap_output_file");
+ setter.setOptionValue("showmap-process-name", "");
+ FileWriter writer = new FileWriter(mTmpFile);
+ String log =
+ String.join(
+ "\n",
+ ">>> system_server (6910) <<<",
+ "size RSS PSS clean dirty clean dirty",
+ "-------- -------- --------",
+ "10 20 30 40 50 60 70 80 90 100 110 120 130 140 15 rw- obj1",
+ "-------- -------- --------",
+ " >>> netd (7038) <<< ",
+ "size RSS PSS clean dirty clean dirty",
+ "-------- -------- --------",
+ "100 2021 3033 4092 500 6 7 8 9 100 110 120 130 140 15 rw- obj123",
+ "-------- -------- --------");
+ writer.write(log);
+ writer.close();
+ TestDescription testDesc = new TestDescription("xyz", "abc");
+ mShowmapMetricCollector.testStarted(testDesc);
+
+ HashMap<String, Metric> currentMetrics = new HashMap<>();
+ currentMetrics.put(
+ "showmap_output_file",
+ TfMetricProtoUtil.stringToMetric("/sdcard/test_results/showmap.txt"));
+ Mockito.when(mMockDevice.pullFile(Mockito.eq("/sdcard/test_results/showmap.txt")))
+ .thenReturn(mTmpFile);
+
+ mShowmapMetricCollector.testEnded(testDesc, currentMetrics);
+ Assert.assertEquals(1, currentMetrics.size());
+ Assert.assertEquals(
+ null, currentMetrics.get("showmap_granular_system_server_total_object_count"));
+ }
+
+ @Test
public void testErrorFlow() throws Exception {
OptionSetter setter = new OptionSetter(mShowmapMetricCollector);
setter.setOptionValue("collect-on-run-ended-only", "false");
diff --git a/javatests/com/android/tradefed/invoker/InvocationExecutionTest.java b/javatests/com/android/tradefed/invoker/InvocationExecutionTest.java
index 20f17f8..2c4c016 100644
--- a/javatests/com/android/tradefed/invoker/InvocationExecutionTest.java
+++ b/javatests/com/android/tradefed/invoker/InvocationExecutionTest.java
@@ -184,8 +184,7 @@
private boolean mFirstInit = true;
@Override
- public ITestInvocationListener init(
- IInvocationContext context, ITestInvocationListener listener)
+ public void extraInit(IInvocationContext context, ITestInvocationListener listener)
throws DeviceNotAvailableException {
if (mFirstInit) {
sTotalInit++;
@@ -193,7 +192,6 @@
} else {
fail("Init should only be called once per instance.");
}
- return super.init(context, listener);
}
}
diff --git a/javatests/com/android/tradefed/lite/Android.bp b/javatests/com/android/tradefed/lite/Android.bp
index dfb390d..19b7070 100644
--- a/javatests/com/android/tradefed/lite/Android.bp
+++ b/javatests/com/android/tradefed/lite/Android.bp
@@ -21,6 +21,8 @@
name: "IsolatedSampleTests",
defaults: ["tradefed_defaults"],
srcs: ["./SampleTests.java"],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
static_libs: [
"junit"
]
diff --git a/javatests/com/android/tradefed/presubmit/TestMappingsValidation.java b/javatests/com/android/tradefed/presubmit/TestMappingsValidation.java
index 547daab..f5dce86 100644
--- a/javatests/com/android/tradefed/presubmit/TestMappingsValidation.java
+++ b/javatests/com/android/tradefed/presubmit/TestMappingsValidation.java
@@ -15,9 +15,10 @@
*/
package com.android.tradefed.presubmit;
-import static java.lang.String.format;
import static org.junit.Assert.fail;
+import static java.lang.String.format;
+
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.ConfigurationException;
@@ -27,6 +28,8 @@
import com.android.tradefed.config.IConfigurationFactory;
import com.android.tradefed.config.Option;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.PushFilePreparer;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.suite.ITestSuite;
@@ -38,10 +41,16 @@
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
-
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
+
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -51,14 +60,8 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
-
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.junit.Assume;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
/**
* Validation tests to run against the TEST_MAPPING files in tests_mappings.zip to ensure they
@@ -191,8 +194,9 @@
* targets.
*/
@Test
- public void testValidateTestEntry() {
+ public void testValidateTestEntry() throws Exception {
List<String> errors = new ArrayList<>();
+ Set<String> checkedModule = new HashSet<>();
for (String testGroup : allTests.keySet()) {
if (!mTestGroupToValidate.contains(testGroup)) {
CLog.d("Skip checking tests with group: %s", testGroup);
@@ -215,6 +219,7 @@
testInfo.getName(), testInfo.getSources()));
}
}
+ checkedModule.add(moduleName);
}
}
if (!errors.isEmpty()) {
@@ -235,6 +240,7 @@
fail(error);
}
}
+ validateConfigsOfSharedPool(checkedModule);
}
/**
@@ -531,4 +537,60 @@
}
return errors;
}
+
+ private void validateConfigsOfSharedPool(Set<String> checkedModule) throws Exception {
+ File configZip = deviceBuildInfo.getFile("general-tests_configs.zip");
+ File deviceConfigZip = deviceBuildInfo.getFile("device-tests_configs.zip");
+ Assume.assumeTrue(configZip != null);
+ List<String> testConfigs = new ArrayList<>();
+ List<File> dirToLoad = new ArrayList<>();
+ File testConfigDir = ZipUtil2.extractZipToTemp(configZip, "general-tests_configs");
+ File deviceTestConfigDir = null;
+ dirToLoad.add(testConfigDir);
+ if (deviceConfigZip != null) {
+ deviceTestConfigDir =
+ ZipUtil2.extractZipToTemp(deviceConfigZip, "device-tests_configs");
+ dirToLoad.add(deviceTestConfigDir);
+ }
+ try {
+ testConfigs.addAll(ConfigurationUtil.getConfigNamesFromDirs(null, dirToLoad));
+ CLog.d("Checking modules: %s. And configs: %s", checkedModule, testConfigs);
+ List<String> errors = new ArrayList<>();
+ for (String configName : testConfigs) {
+ String fileName = FileUtil.getBaseName(new File(configName).getName());
+ if (!checkedModule.contains(fileName)) {
+ continue;
+ }
+ try {
+ IConfiguration config =
+ mConfigFactory.createConfigurationFromArgs(new String[] {configName});
+ for (ITargetPreparer prep : config.getTargetPreparers()) {
+ if (prep instanceof PushFilePreparer) {
+ PushFilePreparer pushPreparer = (PushFilePreparer) prep;
+ if (pushPreparer.shouldRemountSystem()
+ || pushPreparer.shouldRemountVendor()) {
+ // TODO: Throw exception instead
+ CLog.e(
+ // throw new ConfigurationException(
+ String.format(
+ "%s: Shouldn't use 'remount-system' or"
+ + " 'remount-vendor' in shared test mapping"
+ + " pools.",
+ fileName));
+ }
+ }
+ }
+ } catch (Exception e) {
+ errors.add(e.toString());
+ }
+ }
+
+ if (!errors.isEmpty()) {
+ fail(Joiner.on("\n").join(errors));
+ }
+ } finally {
+ FileUtil.recursiveDelete(testConfigDir);
+ FileUtil.recursiveDelete(deviceTestConfigDir);
+ }
+ }
}
diff --git a/javatests/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java b/javatests/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
index ff28bc3..bcaa8e6 100644
--- a/javatests/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
+++ b/javatests/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
@@ -277,6 +277,67 @@
assertFalse(result.isRunComplete());
}
+ @Test
+ public void testFailuresReporting_largeStackTrace() throws Exception {
+ mResultHolder.context = mContext;
+
+ List<TestRunResult> runResults = new ArrayList<>();
+ runResults.add(createFakeResult("module1", 2, 1, 0, 0, 1024 * 1024, false, false));
+ mResultHolder.runResults = runResults;
+
+ Map<String, IAbi> modulesAbi = new HashMap<>();
+ modulesAbi.put("module1", new Abi("armeabi-v7a", "32"));
+ mResultHolder.modulesAbi = modulesAbi;
+
+ mResultHolder.completeModules = 2;
+ mResultHolder.totalModules = 1;
+ mResultHolder.passedTests = 2;
+ mResultHolder.failedTests = 1;
+ mResultHolder.startTime = 0L;
+ mResultHolder.endTime = 10L;
+ File res = mFormatter.writeResults(mResultHolder, mResultDir);
+ String content = FileUtil.readStringFromFile(res);
+
+ assertXmlContainsNode(content, "Result/Module");
+ assertXmlContainsAttribute(content, "Result/Module/TestCase", "name", "com.class.module1");
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test", "name", "module1.method0");
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test", "name", "module1.method1");
+ // Check that failures are showing in the xml for the test cases with error identifiers
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test", "name", "module1.failed0");
+ assertXmlContainsAttribute(content, "Result/Module/TestCase/Test", "result", "fail");
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test/Failure", "message", "module1 failed.");
+ assertXmlContainsAttribute(
+ content,
+ "Result/Module/TestCase/Test/Failure",
+ "error_name",
+ TestErrorIdentifier.TEST_ABORTED.name());
+ assertXmlContainsAttribute(
+ content,
+ "Result/Module/TestCase/Test/Failure",
+ "error_code",
+ Long.toString(TestErrorIdentifier.TEST_ABORTED.code()));
+ assertXmlContainsValue(
+ content,
+ "Result/Module/TestCase/Test/Failure/StackTrace",
+ mFormatter.sanitizeXmlContent("module1 failed." + "\nstack".repeat(174760) + "\n"));
+ // Test that we can read back the informations
+ SuiteResultHolder holder = mFormatter.parseResults(mResultDir, false);
+ assertEquals(holder.completeModules, mResultHolder.completeModules);
+ assertEquals(holder.totalModules, mResultHolder.totalModules);
+ assertEquals(holder.passedTests, mResultHolder.passedTests);
+ assertEquals(holder.failedTests, mResultHolder.failedTests);
+ assertEquals(holder.startTime, mResultHolder.startTime);
+ assertEquals(holder.endTime, mResultHolder.endTime);
+ assertEquals(
+ holder.modulesAbi.get("armeabi-v7a module1"),
+ mResultHolder.modulesAbi.get("module1"));
+ assertEquals(holder.runResults.size(), mResultHolder.runResults.size());
+ }
+
/** Test that assumption failures and ignored tests are correctly reported in the xml. */
@Test
public void testAssumptionFailures_Ignore_Reporting() throws Exception {
@@ -731,6 +792,26 @@
int testIgnored,
boolean withMetrics,
boolean withBadKey) {
+ return createFakeResult(
+ runName,
+ passed,
+ failed,
+ assumptionFailures,
+ testIgnored,
+ 2,
+ withMetrics,
+ withBadKey);
+ }
+
+ private TestRunResult createFakeResult(
+ String runName,
+ int passed,
+ int failed,
+ int assumptionFailures,
+ int testIgnored,
+ int stackDepth,
+ boolean withMetrics,
+ boolean withBadKey) {
TestRunResult fakeRes = new TestRunResult();
fakeRes.testRunStarted(runName, passed + failed);
for (int i = 0; i < passed; i++) {
@@ -745,7 +826,8 @@
fakeRes.testStarted(description);
// Include a null character \0 that is not XML supported
FailureDescription failureDescription =
- FailureDescription.create(runName + " failed.\nstack\nstack\0")
+ FailureDescription.create(
+ runName + " failed." + "\nstack".repeat(stackDepth) + "\0")
.setErrorIdentifier(TestErrorIdentifier.TEST_ABORTED);
fakeRes.testFailed(description, failureDescription);
HashMap<String, Metric> metrics = new HashMap<String, Metric>();
@@ -763,7 +845,8 @@
TestDescription description =
new TestDescription("com.class." + runName, runName + ".assumpFail" + i);
fakeRes.testStarted(description);
- fakeRes.testAssumptionFailure(description, runName + " failed.\nstack\nstack");
+ fakeRes.testAssumptionFailure(
+ description, runName + " failed." + "\nstack".repeat(stackDepth));
fakeRes.testEnded(description, new HashMap<String, Metric>());
}
for (int i = 0; i < testIgnored; i++) {
diff --git a/javatests/com/android/tradefed/retry/BaseRetryDecisionTest.java b/javatests/com/android/tradefed/retry/BaseRetryDecisionTest.java
index 285e67b..df3c8d6 100644
--- a/javatests/com/android/tradefed/retry/BaseRetryDecisionTest.java
+++ b/javatests/com/android/tradefed/retry/BaseRetryDecisionTest.java
@@ -258,6 +258,15 @@
verify(mMockDevice).reboot();
}
+ @Test
+ public void shouldRetryPreparation_NOT_ISOLATED() throws Exception {
+ ModuleDefinition module1 = Mockito.mock(ModuleDefinition.class);
+ OptionSetter setter = new OptionSetter(mRetryDecision);
+ RetryPreparationDecision res = mRetryDecision.shouldRetryPreparation(module1, 0, 3);
+ assertFalse(res.shouldRetry());
+ assertTrue(res.shouldFailRun());
+ }
+
private TestRunResult createResult(FailureDescription failure1, FailureDescription failure2) {
return createResult(failure1, failure2, null);
}
diff --git a/javatests/com/android/tradefed/targetprep/CreateUserPreparerTest.java b/javatests/com/android/tradefed/targetprep/CreateUserPreparerTest.java
index 69f1549..7c78328 100644
--- a/javatests/com/android/tradefed/targetprep/CreateUserPreparerTest.java
+++ b/javatests/com/android/tradefed/targetprep/CreateUserPreparerTest.java
@@ -15,12 +15,11 @@
*/
package com.android.tradefed.targetprep;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.NativeDevice;
@@ -28,27 +27,35 @@
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.ITestDeviceMockHelper;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
import java.util.Map;
/** Unit tests for {@link CreateUserPreparer}. */
-@RunWith(JUnit4.class)
-public class CreateUserPreparerTest {
+@RunWith(MockitoJUnitRunner.class)
+public final class CreateUserPreparerTest {
+ @Mock private ITestDevice mMockDevice;
+
+ private OptionSetter mSetter;
+
+ private ITestDeviceMockHelper mTestDeviceMockHelper;
private CreateUserPreparer mPreparer;
- private ITestDevice mMockDevice;
private TestInformation mTestInfo;
@Before
- public void setUp() {
- mMockDevice = Mockito.mock(ITestDevice.class);
+ public void setFixtures() throws Exception {
+ mTestDeviceMockHelper = new ITestDeviceMockHelper(mMockDevice);
mPreparer = new CreateUserPreparer();
+ mSetter = new OptionSetter(mPreparer);
+
IInvocationContext context = new InvocationContext();
context.addAllocatedDevice("device", mMockDevice);
mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build();
@@ -56,23 +63,23 @@
@Test
public void testSetUp_tearDown() throws Exception {
- doReturn(10).when(mMockDevice).getCurrentUser();
- doReturn(5).when(mMockDevice).createUser(Mockito.any());
- doReturn(true).when(mMockDevice).switchUser(5);
- doReturn(true).when(mMockDevice).startUser(5, true);
+ mockGetCurrentUser(10);
+ mockCreateUser(5);
+ mockSwitchUser(5);
+ mockStartUser(5);
mPreparer.setUp(mTestInfo);
+ verifyUserCreated();
+ verifyUserSwitched(5);
- doReturn(true).when(mMockDevice).removeUser(5);
- doReturn(true).when(mMockDevice).switchUser(10);
- mPreparer.tearDown(mTestInfo, null);
+ mockSwitchUser(10);
+ mPreparer.tearDown(mTestInfo, /* e= */ null);
+ verifyUserRemoved(5);
+ verifyUserSwitched(10);
}
@Test
public void testSetUp_tearDown_reuseTestUser() throws Exception {
- // Set the reuse-test-user to true.
- CreateUserPreparer preparer = new CreateUserPreparer();
- OptionSetter setter = new OptionSetter(preparer);
- setter.setOptionValue("reuse-test-user", "true");
+ setParam("reuse-test-user", "true");
Map<Integer, UserInfo> existingUsers = Map.of(
0, new UserInfo(
@@ -86,29 +93,26 @@
/* flags= */ 0,
/* isRunning= */ false));
- doReturn(existingUsers).when(mMockDevice).getUserInfos();
- doReturn(0).when(mMockDevice).getCurrentUser();
- doReturn(true).when(mMockDevice).switchUser(13);
- doReturn(true).when(mMockDevice).startUser(13, true);
- doReturn(true).when(mMockDevice).switchUser(0);
+ mockGetUserInfos(existingUsers);
+ mockGetCurrentUser(0);
+ mockSwitchUser(13);
+ mockStartUser(13);
+ mockSwitchUser(0);
- preparer.setUp(mTestInfo);
+ mPreparer.setUp(mTestInfo);
// We should reuse the existing, not create a new user.
- verify(mMockDevice, never()).createUser(Mockito.any());
- verify(mMockDevice).switchUser(13);
+ verifyNoUserCreated();
+ verifyUserSwitched(13);
- preparer.tearDown(mTestInfo, null);
+ mPreparer.tearDown(mTestInfo, /* e= */ null);
// We should keep the user for the next module to reuse.
- verify(mMockDevice, never()).removeUser(13);
- verify(mMockDevice).switchUser(0);
+ verifyUserNotRemoved(13);
+ verifyUserSwitched(0);
}
@Test
public void testSetUp_tearDown_reuseTestUser_noExistingTestUser() throws Exception {
- // Set the reuse-test-user to true.
- CreateUserPreparer preparer = new CreateUserPreparer();
- OptionSetter setter = new OptionSetter(preparer);
- setter.setOptionValue("reuse-test-user", "true");
+ setParam("reuse-test-user", "true");
Map<Integer, UserInfo> existingUsers = Map.of(
0, new UserInfo(
@@ -117,36 +121,32 @@
/* flags= */ 0x00000013,
/* isRunning= */ true));
- doReturn(existingUsers).when(mMockDevice).getUserInfos();
- doReturn(0).when(mMockDevice).getCurrentUser();
- doReturn(12).when(mMockDevice).createUser(Mockito.any());
- doReturn(true).when(mMockDevice).switchUser(12);
- doReturn(true).when(mMockDevice).startUser(12, true);
- doReturn(true).when(mMockDevice).switchUser(0);
+ mockGetUserInfos(existingUsers);
+ mockGetCurrentUser(0);
+ mockCreateUser(12);
+ mockSwitchUser(12);
+ mockStartUser(12);
+ mockSwitchUser(0);
- preparer.setUp(mTestInfo);
- verify(mMockDevice).createUser(Mockito.any());
- verify(mMockDevice).switchUser(12);
+ mPreparer.setUp(mTestInfo);
+ verifyUserCreated();
+ verifyUserSwitched(12);
- preparer.tearDown(mTestInfo, null);
+ mPreparer.tearDown(mTestInfo, /* e= */ null);
// Newly created user is kept to reuse it in the next run.
- verify(mMockDevice, never()).removeUser(12);
- verify(mMockDevice).switchUser(0);
+ verifyUserNotRemoved(12);
+ verifyUserSwitched(0);
}
@Test
public void testSetUp_tearDown_noCurrent() throws Exception {
- doReturn(NativeDevice.INVALID_USER_ID).when(mMockDevice).getCurrentUser();
- try {
- mPreparer.setUp(mTestInfo);
- fail("Should have thrown an exception.");
- } catch (TargetSetupError expected) {
- // Expected
- }
+ mockGetCurrentUser(NativeDevice.INVALID_USER_ID);
+
+ assertThrows(TargetSetupError.class, () -> mPreparer.setUp(mTestInfo));
mPreparer.tearDown(mTestInfo, null);
- verify(mMockDevice, never()).removeUser(Mockito.anyInt());
- verify(mMockDevice, never()).switchUser(Mockito.anyInt());
+ verifyNoUserRemoved();
+ verifyNoUserSwitched();
}
@Test
@@ -168,41 +168,92 @@
/* flags= */ 0,
/* isRunning= */ false));
- doReturn(3).when(mMockDevice).getMaxNumberOfUsersSupported();
- doReturn(existingUsers).when(mMockDevice).getUserInfos();
- doThrow(new IllegalStateException("failed to create"))
- .when(mMockDevice)
- .createUser(Mockito.any());
+ mockGetMaxNumberOfUsersSupported(3);
+ mockGetUserInfos(existingUsers);
+ Exception cause = mockCreateUserFailure("D'OH!");
- try {
- mPreparer.setUp(mTestInfo);
- fail("Should have thrown an exception.");
- } catch (TargetSetupError expected) {
- // Expected
- }
+ TargetSetupError e = assertThrows(TargetSetupError.class, () -> mPreparer.setUp(mTestInfo));
+ assertThat(e).hasCauseThat().isSameInstanceAs(cause);
+
// verify that it removed the existing tradefed users.
- verify(mMockDevice).removeUser(11);
- verify(mMockDevice).removeUser(13);
+ verifyUserRemoved(11);
+ verifyUserRemoved(13);
}
@Test
- public void testSetUp_failed() throws Exception {
- doThrow(new IllegalStateException("failed to create"))
- .when(mMockDevice)
- .createUser(Mockito.any());
+ public void testSetUp_createUserfailed() throws Exception {
+ Exception cause = mockCreateUserFailure("D'OH!");
- try {
- mPreparer.setUp(mTestInfo);
- fail("Should have thrown an exception.");
- } catch (TargetSetupError expected) {
- // Expected
- }
+ TargetSetupError e = assertThrows(TargetSetupError.class, () -> mPreparer.setUp(mTestInfo));
+
+ assertThat(e).hasCauseThat().isSameInstanceAs(cause);
}
@Test
public void testTearDown_only() throws Exception {
- mPreparer.tearDown(mTestInfo, null);
+ mPreparer.tearDown(mTestInfo, /* e= */ null);
- verify(mMockDevice, never()).removeUser(Mockito.anyInt());
+ verifyNoUserRemoved();
+ }
+
+ private void setParam(String key, String value) throws ConfigurationException {
+ CLog.i("Setting param: '%s'='%s'", key, value);
+ mSetter.setOptionValue(key, value);
+ }
+
+ private void mockGetCurrentUser(int userId) throws Exception {
+ mTestDeviceMockHelper.mockGetCurrentUser(userId);
+ }
+
+ private void mockGetUserInfos(Map<Integer, UserInfo> existingUsers) throws Exception {
+ mTestDeviceMockHelper.mockGetUserInfos(existingUsers);
+ }
+
+ private void mockGetMaxNumberOfUsersSupported(int max) throws Exception {
+ mTestDeviceMockHelper.mockGetMaxNumberOfUsersSupported(max);
+ }
+
+ private void mockSwitchUser(int userId) throws Exception {
+ mTestDeviceMockHelper.mockSwitchUser(userId);
+ }
+
+ private void mockStartUser(int userId) throws Exception {
+ mTestDeviceMockHelper.mockStartUser(userId);
+ }
+
+ private void mockCreateUser(int userId) throws Exception {
+ mTestDeviceMockHelper.mockCreateUser(userId);
+ }
+
+ private IllegalStateException mockCreateUserFailure(String message) throws Exception {
+ return mTestDeviceMockHelper.mockCreateUserFailure(message);
+ }
+
+ private void verifyNoUserCreated() throws Exception {
+ mTestDeviceMockHelper.verifyNoUserCreated();
+ }
+
+ private void verifyUserCreated() throws Exception {
+ mTestDeviceMockHelper.verifyUserCreated();
+ }
+
+ private void verifyNoUserSwitched() throws Exception {
+ mTestDeviceMockHelper.verifyNoUserSwitched();
+ }
+
+ private void verifyUserSwitched(int userId) throws Exception {
+ mTestDeviceMockHelper.verifyUserSwitched(userId);
+ }
+
+ private void verifyNoUserRemoved() throws Exception {
+ mTestDeviceMockHelper.verifyNoUserRemoved();
+ }
+
+ private void verifyUserRemoved(int userId) throws Exception {
+ mTestDeviceMockHelper.verifyUserRemoved(userId);
+ }
+
+ private void verifyUserNotRemoved(int userId) throws Exception {
+ mTestDeviceMockHelper.verifyUserNotRemoved(userId);
}
}
diff --git a/javatests/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java b/javatests/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
index 518420f..5283284 100644
--- a/javatests/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
+++ b/javatests/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
@@ -234,6 +234,7 @@
mSetter = new OptionSetter(mInstallApexModuleTargetPreparer);
mSetter.setOptionValue("cleanup-apks", "true");
mSetter.setOptionValue("apex-staging-wait-time", APEX_STAGING_WAIT_TIME);
+ mSetter.setOptionValue("apex-rollback-wait-time", APEX_STAGING_WAIT_TIME);
}
@After
@@ -815,7 +816,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
verifySuccessfulInstallPackages(Arrays.asList(mFakeApex));
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(1);
verify(mMockDevice, times(2)).reboot();
verify(mMockDevice, times(3)).getActiveApexes();
}
@@ -851,7 +852,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
verifySuccessfulInstallPackages(Arrays.asList(mFakeApex));
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(1);
verify(mMockDevice, times(2)).reboot();
verify(mMockDevice, times(3)).getActiveApexes();
}
@@ -869,7 +870,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
verifySuccessfulInstallPackages(Arrays.asList(mFakeApex));
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(1);
verify(mMockDevice, times(2)).reboot();
verify(mMockDevice, times(3)).getActiveApexes();
}
@@ -883,7 +884,7 @@
when(mMockDevice.getInstalledPackageNames()).thenReturn(new HashSet<>());
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(1);
verify(mMockDevice, times(1)).reboot();
verify(mMockDevice, times(2)).getActiveApexes();
}
@@ -1037,7 +1038,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
verifySuccessfulInstallPackages(Arrays.asList(mFakeApex));
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(1);
verify(mMockDevice, times(2)).reboot();
verify(mMockDevice, times(3)).getActiveApexes();
}
@@ -1065,7 +1066,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(1);
verify(mMockDevice, times(2)).reboot();
verify(mMockDevice, times(1)).uninstallPackage(APK_PACKAGE_NAME);
verify(mMockDevice, times(2)).getActiveApexes();
@@ -1094,7 +1095,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(1);
verify(mMockDevice, times(2)).reboot();
verify(mMockDevice, times(1)).uninstallPackage(APK_PACKAGE_NAME);
verify(mMockDevice, times(1)).uninstallPackage(APK2_PACKAGE_NAME);
@@ -1204,8 +1205,8 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
verifySuccessfulInstallPackages(Arrays.asList(mFakeApex));
- verifyCleanInstalledApexPackages();
- verify(mMockDevice, times(3)).reboot();
+ verifyCleanInstalledApexPackages(2);
+ verify(mMockDevice, times(4)).reboot();
verify(mMockDevice, times(3)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
verify(mMockDevice).waitForDeviceAvailable();
@@ -1237,9 +1238,9 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(2);
verifySuccessfulInstallMultiPackages();
- verify(mMockDevice, times(3)).reboot();
+ verify(mMockDevice, times(4)).reboot();
verify(mMockDevice, times(3)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
verify(mMockDevice, times(1)).getInstalledPackageNames();
@@ -1266,8 +1267,8 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
- verify(mMockDevice, times(3)).reboot();
+ verifyCleanInstalledApexPackages(2);
+ verify(mMockDevice, times(4)).reboot();
verify(mMockDevice, times(3)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
verify(mMockDevice, times(1)).getInstalledPackageNames();
@@ -1294,8 +1295,8 @@
.contains("Failed to push local"));
}
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
- verify(mMockDevice, times(2)).reboot();
+ verifyCleanInstalledApexPackages(2);
+ verify(mMockDevice, times(3)).reboot();
verify(mMockDevice, times(2)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
verify(mMockDevice, times(1)).getInstalledPackageNames();
@@ -1330,8 +1331,8 @@
parent_session_creation_res.getStdout())));
}
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
- verify(mMockDevice, times(2)).reboot();
+ verifyCleanInstalledApexPackages(2);
+ verify(mMockDevice, times(3)).reboot();
verify(mMockDevice, times(2)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
verify(mMockDevice, times(1)).getInstalledPackageNames();
@@ -1367,8 +1368,8 @@
.contains("Failed to create child session for"));
}
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
- verify(mMockDevice, times(2)).reboot();
+ verifyCleanInstalledApexPackages(2);
+ verify(mMockDevice, times(3)).reboot();
verify(mMockDevice, times(2)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
verify(mMockDevice, times(1)).getInstalledPackageNames();
@@ -1419,8 +1420,8 @@
write_to_session_res.getStdout())));
}
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
- verify(mMockDevice, times(2)).reboot();
+ verifyCleanInstalledApexPackages(2);
+ verify(mMockDevice, times(3)).reboot();
verify(mMockDevice, times(2)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
verify(mMockDevice, times(1)).getInstalledPackageNames();
@@ -1482,8 +1483,8 @@
add_to_session_res.getStderr(), add_to_session_res.getStdout())));
}
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
- verify(mMockDevice, times(2)).reboot();
+ verifyCleanInstalledApexPackages(2);
+ verify(mMockDevice, times(3)).reboot();
verify(mMockDevice, times(2)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
verify(mMockDevice, times(1)).getInstalledPackageNames();
@@ -1546,8 +1547,8 @@
commit_session_res.getStdout())));
}
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
- verify(mMockDevice, times(2)).reboot();
+ verifyCleanInstalledApexPackages(2);
+ verify(mMockDevice, times(3)).reboot();
verify(mMockDevice, times(2)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
verify(mMockDevice, times(1)).getInstalledPackageNames();
@@ -1571,7 +1572,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(1);
verifySuccessfulInstallMultiPackages();
verify(mMockDevice, times(3)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
@@ -1656,7 +1657,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(2);
Mockito.verify(mMockBundletoolUtil, times(1))
.generateDeviceSpecFile(Mockito.any(ITestDevice.class));
// Extract splits 1 time to get the package name for the module, and again during
@@ -1673,7 +1674,7 @@
anyString(),
Mockito.any(ITestDevice.class),
Mockito.any(IBuildInfo.class));
- verify(mMockDevice, times(3)).reboot();
+ verify(mMockDevice, times(4)).reboot();
verify(mMockDevice, times(1)).executeAdbCommand(trainInstallCmd.toArray(new String[0]));
verify(mMockDevice, times(1))
.executeShellCommand("pm rollback-app " + SPLIT_APEX_PACKAGE_NAME);
@@ -1768,7 +1769,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(2);
Mockito.verify(mMockBundletoolUtil, times(1))
.generateDeviceSpecFile(Mockito.any(ITestDevice.class));
// Extract splits 1 time to get the package name for the module, and again during
@@ -1785,7 +1786,7 @@
anyString(),
Mockito.any(ITestDevice.class),
Mockito.any(IBuildInfo.class));
- verify(mMockDevice, times(3)).reboot();
+ verify(mMockDevice, times(4)).reboot();
verify(mMockDevice, times(1)).executeAdbCommand(trainInstallCmd.toArray(new String[0]));
verify(mMockDevice, times(3)).getActiveApexes();
verify(mMockDevice, times(1))
@@ -1882,7 +1883,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(2);
Mockito.verify(mMockBundletoolUtil, times(1))
.generateDeviceSpecFile(Mockito.any(ITestDevice.class));
// Extract splits 1 time to get the package name for the module, and again during
@@ -1899,7 +1900,7 @@
anyString(),
Mockito.any(ITestDevice.class),
Mockito.any(IBuildInfo.class));
- verify(mMockDevice, times(3)).reboot();
+ verify(mMockDevice, times(4)).reboot();
verify(mMockDevice, times(1)).executeAdbCommand(trainInstallCmd.toArray(new String[0]));
verify(mMockDevice, times(3)).getActiveApexes();
verify(mMockDevice, times(1))
@@ -1996,7 +1997,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(2);
Mockito.verify(mMockBundletoolUtil, times(1))
.generateDeviceSpecFile(Mockito.any(ITestDevice.class));
// Extract splits 1 time to get the package name for the module, and again during
@@ -2013,7 +2014,7 @@
anyString(),
Mockito.any(ITestDevice.class),
Mockito.any(IBuildInfo.class));
- verify(mMockDevice, times(3)).reboot();
+ verify(mMockDevice, times(4)).reboot();
verify(mMockDevice, times(1)).executeAdbCommand(trainInstallCmd.toArray(new String[0]));
verify(mMockDevice, times(3)).getActiveApexes();
verify(mMockDevice, times(1))
@@ -2283,7 +2284,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
- verifyCleanInstalledApexPackages();
+ verifyCleanInstalledApexPackages(1);
verify(mMockDevice, times(1)).reboot();
verify(mMockDevice, times(2)).getActiveApexes();
}
@@ -2309,7 +2310,7 @@
mInstallApexModuleTargetPreparer.setUp(mTestInfo);
mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
verifySuccessfulInstallMultiPackages();
- verify(mMockDevice, times(3)).reboot();
+ verify(mMockDevice, times(4)).reboot();
verify(mMockDevice, times(3)).getActiveApexes();
verify(mMockDevice, times(1)).executeShellCommand("pm rollback-app " + APEX_PACKAGE_NAME);
verify(mMockDevice, times(1)).getInstalledPackageNames();
@@ -2483,10 +2484,10 @@
when(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).thenReturn(res);
}
- private void verifyCleanInstalledApexPackages() throws DeviceNotAvailableException {
- verify(mMockDevice, times(1)).deleteFile(APEX_DATA_DIR + "*");
- verify(mMockDevice, times(1)).deleteFile(SESSION_DATA_DIR + "*");
- verify(mMockDevice, times(1)).deleteFile(STAGING_DATA_DIR + "*");
+ private void verifyCleanInstalledApexPackages(int count) throws DeviceNotAvailableException {
+ verify(mMockDevice, times(count)).deleteFile(APEX_DATA_DIR + "*");
+ verify(mMockDevice, times(count)).deleteFile(SESSION_DATA_DIR + "*");
+ verify(mMockDevice, times(count)).deleteFile(STAGING_DATA_DIR + "*");
}
private void setActivatedApex() throws DeviceNotAvailableException {
diff --git a/javatests/com/android/tradefed/targetprep/RunCommandTargetPreparerTest.java b/javatests/com/android/tradefed/targetprep/RunCommandTargetPreparerTest.java
index d77b5a1..80d38fa 100644
--- a/javatests/com/android/tradefed/targetprep/RunCommandTargetPreparerTest.java
+++ b/javatests/com/android/tradefed/targetprep/RunCommandTargetPreparerTest.java
@@ -16,6 +16,8 @@
package com.android.tradefed.targetprep;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
@@ -83,6 +85,8 @@
when(mMockDevice.executeShellV2Command(Mockito.eq(command))).thenReturn(res);
mPreparer.setUp(mTestInfo);
+
+ assertThat(mPreparer.getCommands()).containsExactly(command);
}
/**
diff --git a/javatests/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java b/javatests/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java
index 4ff162c..d85d201 100644
--- a/javatests/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java
+++ b/javatests/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java
@@ -16,9 +16,8 @@
package com.android.tradefed.targetprep;
-import static com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer.SKIP_TESTS_REASON_KEY;
-import static com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer.TEST_PACKAGE_NAME_OPTION;
import static com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer.RUN_TESTS_AS_USER_KEY;
+import static com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparer.TEST_PACKAGE_NAME_OPTION;
import static com.google.common.truth.Truth.assertThat;
@@ -29,7 +28,6 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.UserInfo;
import com.android.tradefed.invoker.TestInformation;
@@ -57,9 +55,6 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private TestInformation mTestInfo;
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private IConfiguration mConfiguration;
-
private RunOnSecondaryUserTargetPreparer mPreparer;
private OptionSetter mOptionSetter;
@@ -67,7 +62,6 @@
public void setUp() throws Exception {
mPreparer = new RunOnSecondaryUserTargetPreparer();
mOptionSetter = new OptionSetter(mPreparer);
- mPreparer.setConfiguration(mConfiguration);
ArrayList<Integer> userIds = new ArrayList<>();
userIds.add(0);
@@ -278,17 +272,8 @@
mPreparer.setUp(mTestInfo);
- verify(mConfiguration)
- .injectOptionValue(eq("instrumentation-arg"), eq(SKIP_TESTS_REASON_KEY), any());
- }
-
- @Test
- public void setUp_doesNotSupportAdditionalUsers_disablesTearDown() throws Exception {
- when(mTestInfo.getDevice().getMaxNumberOfUsersSupported()).thenReturn(1);
-
- mPreparer.setUp(mTestInfo);
-
- assertThat(mPreparer.isTearDownDisabled()).isTrue();
+ verify(mTestInfo.properties())
+ .put(eq(RunOnSecondaryUserTargetPreparer.SKIP_TESTS_REASON_KEY), any());
}
@Test
@@ -317,7 +302,7 @@
mPreparer.setUp(mTestInfo);
- verify(mConfiguration, never())
- .injectOptionValue(eq("instrumentation-arg"), eq(SKIP_TESTS_REASON_KEY), any());
+ verify(mTestInfo.properties(), never())
+ .put(eq(RunOnSecondaryUserTargetPreparer.SKIP_TESTS_REASON_KEY), any());
}
}
diff --git a/javatests/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java b/javatests/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java
index 2545359..441232a 100644
--- a/javatests/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java
+++ b/javatests/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java
@@ -22,15 +22,13 @@
import static com.google.common.truth.Truth.assertThat;
-import static junit.framework.Assert.fail;
-
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.UserInfo;
import com.android.tradefed.invoker.TestInformation;
@@ -58,9 +56,6 @@
@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private TestInformation mTestInfo;
- @Mock(answer = Answers.RETURNS_DEEP_STUBS)
- private IConfiguration mConfiguration;
-
private RunOnWorkProfileTargetPreparer mPreparer;
private OptionSetter mOptionSetter;
@@ -88,7 +83,6 @@
public void setUp() throws Exception {
mPreparer = new RunOnWorkProfileTargetPreparer();
mOptionSetter = new OptionSetter(mPreparer);
- mPreparer.setConfiguration(mConfiguration);
ArrayList<Integer> userIds = new ArrayList<>();
userIds.add(0);
@@ -161,9 +155,9 @@
try {
mPreparer.setUp(mTestInfo);
- fail();
+ fail("Should have thrown exception");
} catch (IllegalStateException expected) {
-
+ // Expected
}
}
@@ -367,17 +361,7 @@
mPreparer.setUp(mTestInfo);
- verify(mConfiguration)
- .injectOptionValue(eq("instrumentation-arg"), eq(SKIP_TESTS_REASON_KEY), any());
- }
-
- @Test
- public void setUp_doesNotSupportManagedUsers_disablesTearDown() throws Exception {
- when(mTestInfo.getDevice().hasFeature("android.software.managed_users")).thenReturn(false);
-
- mPreparer.setUp(mTestInfo);
-
- assertThat(mPreparer.isTearDownDisabled()).isTrue();
+ verify(mTestInfo.properties()).put(eq(SKIP_TESTS_REASON_KEY), any());
}
@Test
@@ -387,25 +371,7 @@
mPreparer.setUp(mTestInfo);
verify(mTestInfo.properties(), never()).put(eq(RUN_TESTS_AS_USER_KEY), any());
- }
-
- @Test
- public void setUp_doesNotSupportAdditionalUsers_setsArgumentToSkipTests() throws Exception {
- when(mTestInfo.getDevice().getMaxNumberOfUsersSupported()).thenReturn(1);
-
- mPreparer.setUp(mTestInfo);
-
- verify(mConfiguration)
- .injectOptionValue(eq("instrumentation-arg"), eq(SKIP_TESTS_REASON_KEY), any());
- }
-
- @Test
- public void setUp_doesNotSupportAdditionalUsers_disablesTearDown() throws Exception {
- when(mTestInfo.getDevice().getMaxNumberOfUsersSupported()).thenReturn(1);
-
- mPreparer.setUp(mTestInfo);
-
- assertThat(mPreparer.isTearDownDisabled()).isTrue();
+ verify(mTestInfo.properties()).put(eq(SKIP_TESTS_REASON_KEY), any());
}
@Test
@@ -446,7 +412,6 @@
mPreparer.setUp(mTestInfo);
- verify(mConfiguration, never())
- .injectOptionValue(eq("instrumentation-arg"), eq(SKIP_TESTS_REASON_KEY), any());
+ verify(mTestInfo.properties(), never()).put(eq(SKIP_TESTS_REASON_KEY), any());
}
}
diff --git a/javatests/com/android/tradefed/targetprep/VisibleBackgroundUserPreparerTest.java b/javatests/com/android/tradefed/targetprep/VisibleBackgroundUserPreparerTest.java
new file mode 100644
index 0000000..d0d0447
--- /dev/null
+++ b/javatests/com/android/tradefed/targetprep/VisibleBackgroundUserPreparerTest.java
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2023 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.tradefed.targetprep;
+
+import static com.android.tradefed.targetprep.VisibleBackgroundUserPreparer.RUN_TESTS_AS_USER_KEY;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.UserInfo;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.ITestDeviceMockHelper;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/** Unit tests for {@link VisibleBackgroundUserPreparer} */
+@RunWith(MockitoJUnitRunner.class)
+public final class VisibleBackgroundUserPreparerTest {
+
+ @Mock private ITestDevice mMockDevice;
+
+ private OptionSetter mSetter;
+
+ private ITestDeviceMockHelper mTestDeviceMockHelper;
+ private VisibleBackgroundUserPreparer mPreparer;
+ private TestInformation mTestInfo;
+
+ @Before
+ public void setFixtures() throws Exception {
+ mTestDeviceMockHelper = new ITestDeviceMockHelper(mMockDevice);
+ mPreparer = new VisibleBackgroundUserPreparer();
+ mSetter = new OptionSetter(mPreparer);
+
+ IInvocationContext context = new InvocationContext();
+ context.addAllocatedDevice("device", mMockDevice);
+ mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build();
+
+ mockIsVisibleBackgroundUsersSupported(true);
+ }
+
+ @Test
+ public void testSetUp_featureNotSupported() throws Exception {
+ mockIsVisibleBackgroundUsersSupported(false);
+
+ TargetSetupError e = assertThrows(TargetSetupError.class, () -> mPreparer.setUp(mTestInfo));
+ assertThat(e).hasMessageThat().contains("not supported");
+
+ verifyNoUserCreated();
+ verifyNoUserRemoved();
+ verifyNoUserSwitched();
+ }
+
+ @Test
+ public void testSetUp_tearDown_noDisplayAvailable() throws Exception {
+ mockListDisplayIdsForStartingVisibleBackgroundUsers(Collections.emptySet());
+ mockCreateUser(42);
+
+ TargetSetupError e = assertThrows(TargetSetupError.class, () -> mPreparer.setUp(mTestInfo));
+ assertThat(e).hasMessageThat().containsMatch("No display.*available.* .*42.*");
+
+ mPreparer.tearDown(mTestInfo, /* e= */ null);
+ verifyUserRemoved(42);
+ verifyNoUserSwitched();
+ }
+
+ @Test
+ public void testSetUp_tearDown() throws Exception {
+ mockListDisplayIdsForStartingVisibleBackgroundUsers(orderedSetOf(108));
+ mockCreateUser(42);
+ mockStartUserVisibleOnBackground(42, 108);
+
+ mPreparer.setUp(mTestInfo);
+ verifyUserCreated();
+ verifyUserStartedVisibleOnBackground(42, 108);
+ verifyTestInfoProperty(RUN_TESTS_AS_USER_KEY, "42");
+ verifyNoUserSwitched();
+
+ mPreparer.tearDown(mTestInfo, /* e= */ null);
+ verifyUserStopped(42);
+ verifyUserRemoved(42);
+ verifyNoUserSwitched();
+ }
+
+ @Test
+ public void testSetUp_specificDisplayByOption() throws Exception {
+ setParam("display-id", "108");
+ mockCreateUser(42);
+ mockStartUserVisibleOnBackground(42, 108);
+
+ mPreparer.setUp(mTestInfo);
+ verifyUserCreated();
+ verifyUserStartedVisibleOnBackground(42, 108);
+ verifyTestInfoProperty(RUN_TESTS_AS_USER_KEY, "42");
+ verifyNoUserSwitched();
+ }
+
+ @Test
+ public void testSetUp_specificDisplayBySetter() throws Exception {
+ setParam("display-id", "666");
+ mPreparer.setDisplayId(108);
+ mockCreateUser(42);
+ mockStartUserVisibleOnBackground(42, 108);
+
+ mPreparer.setUp(mTestInfo);
+ verifyUserCreated();
+ verifyUserStartedVisibleOnBackground(42, 108);
+ verifyTestInfoProperty(RUN_TESTS_AS_USER_KEY, "42");
+ verifyNoUserSwitched();
+ }
+
+ @Test
+ public void
+ testSetUp_useDefaultDisplayWhenVisibleBackgroundUsersOnDefaultDisplayIsNotSupported()
+ throws Exception {
+ mockIsVisibleBackgroundUsersOnDefaultDisplaySupported(false);
+ mockListDisplayIdsForStartingVisibleBackgroundUsers(orderedSetOf(0, 108));
+ mockCreateUser(42);
+ mockStartUserVisibleOnBackground(42, 0);
+
+ mPreparer.setUp(mTestInfo);
+ verifyUserCreated();
+ verifyUserStartedVisibleOnBackground(42, 0);
+ verifyTestInfoProperty(RUN_TESTS_AS_USER_KEY, "42");
+ verifyNoUserSwitched();
+ }
+
+ @Test
+ public void
+ testSetUp_ignoreDefaultDisplayWhenVisibleBackgroundUsersOnDefaultDisplayIsSupported()
+ throws Exception {
+ mockIsVisibleBackgroundUsersOnDefaultDisplaySupported(true);
+ mockListDisplayIdsForStartingVisibleBackgroundUsers(orderedSetOf(0, 108));
+ mockCreateUser(42);
+ mockStartUserVisibleOnBackground(42, 108);
+
+ mPreparer.setUp(mTestInfo);
+ verifyUserCreated();
+ verifyUserStartedVisibleOnBackground(42, 108);
+ verifyTestInfoProperty(RUN_TESTS_AS_USER_KEY, "42");
+ verifyNoUserSwitched();
+ }
+
+ @Test
+ public void testSetUp_onlyDefaultDisplayWhenVisibleBackgroundUsersOnDefaultDisplayIsSupported()
+ throws Exception {
+ mockIsVisibleBackgroundUsersOnDefaultDisplaySupported(true);
+ mockListDisplayIdsForStartingVisibleBackgroundUsers(orderedSetOf(0));
+ mockCreateUser(42);
+ mockStartUserVisibleOnBackground(42, 108);
+
+ assertThrows(TargetSetupError.class, () -> mPreparer.setUp(mTestInfo));
+
+ verifyNoUserStartedVisibleOnBackground();
+ }
+
+ @Test
+ public void testSetDisplay_invalidId() throws Exception {
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> mPreparer.setDisplayId(VisibleBackgroundUserPreparer.INVALID_DISPLAY));
+ }
+
+ @Test
+ public void testSetUp_tearDown_reuseTestUser_invisible() throws Exception {
+ setParam("reuse-test-user", "true");
+ mockListDisplayIdsForStartingVisibleBackgroundUsers(orderedSetOf(108));
+ Map<Integer, UserInfo> existingUsers =
+ Map.of(
+ 0,
+ new UserInfo(
+ /* id= */ 0,
+ /* userName= */ null,
+ /* flags= */ 0x00000013,
+ /* isRunning= */ true),
+ 42,
+ new UserInfo(
+ /* id= */ 42,
+ "tf_created_user",
+ /* flags= */ 0,
+ /* isRunning= */ false));
+ mockGetUserInfos(existingUsers);
+ mockStartUserVisibleOnBackground(42, 108);
+
+ mPreparer.setUp(mTestInfo);
+ // We should reuse the existing, not create a new user.
+ verifyNoUserCreated();
+ verifyUserStartedVisibleOnBackground(42, 108);
+ verifyNoUserSwitched();
+ verifyTestInfoProperty(RUN_TESTS_AS_USER_KEY, "42");
+
+ mPreparer.tearDown(mTestInfo, /* e= */ null);
+ // We should keep the user for the next module to reuse.
+ verifyUserStopped(42);
+ verifyNoUserRemoved();
+ verifyNoUserSwitched();
+ }
+
+ @Test
+ public void testSetUp_tearDown_reuseTestUser_alreadyVisible() throws Exception {
+ setParam("reuse-test-user", "true");
+ mockListDisplayIdsForStartingVisibleBackgroundUsers(orderedSetOf(108));
+ Map<Integer, UserInfo> existingUsers =
+ Map.of(
+ 0,
+ new UserInfo(
+ /* id= */ 0,
+ /* userName= */ null,
+ /* flags= */ 0x00000013,
+ /* isRunning= */ true),
+ 42,
+ new UserInfo(
+ /* id= */ 42,
+ "tf_created_user",
+ /* flags= */ 0,
+ /* isRunning= */ false));
+ mockGetUserInfos(existingUsers);
+ mockIsUserVisibleOnDisplay(42, 108);
+
+ mPreparer.setUp(mTestInfo);
+ // We should reuse the existing, not create a new user.
+ verifyNoUserCreated();
+ verifyNoUserStartedVisibleOnBackground();
+ verifyNoUserStarted();
+ verifyNoUserSwitched();
+ verifyTestInfoProperty(RUN_TESTS_AS_USER_KEY, "42");
+
+ mPreparer.tearDown(mTestInfo, /* e= */ null);
+ // We should keep the user for the next module to reuse.
+ verifyNoUserRemoved();
+ verifyNoUserSwitched();
+ verifyNoUserStopped();
+ }
+
+ @Test
+ public void testSetUp_tearDown_reuseTestUser_noExistingTestUser() throws Exception {
+ setParam("reuse-test-user", "true");
+ mockListDisplayIdsForStartingVisibleBackgroundUsers(orderedSetOf(108));
+ Map<Integer, UserInfo> existingUsers =
+ Map.of(
+ 0,
+ new UserInfo(
+ /* id= */ 0,
+ /* userName= */ null,
+ /* flags= */ 0x00000013,
+ /* isRunning= */ true));
+ mockGetUserInfos(existingUsers);
+ mockCreateUser(42);
+ mockStartUserVisibleOnBackground(42, 108);
+
+ mPreparer.setUp(mTestInfo);
+ verifyUserCreated();
+ verifyUserStartedVisibleOnBackground(42, 108);
+ verifyTestInfoProperty(RUN_TESTS_AS_USER_KEY, "42");
+ verifyNoUserStarted();
+ verifyNoUserSwitched();
+
+ mPreparer.tearDown(mTestInfo, /* e= */ null);
+ // Newly created user is kept to reuse it in the next run.
+ verifyUserNotRemoved(42);
+ verifyUserStopped(42);
+ verifyNoUserSwitched();
+ }
+
+ @Test
+ public void testSetUp_maxUsersReached() throws Exception {
+ Map<Integer, UserInfo> existingUsers =
+ Map.of(
+ 0,
+ new UserInfo(
+ /* id= */ 0,
+ /* userName= */ null,
+ /* flags= */ 0x00000013,
+ /* isRunning= */ true),
+ 11,
+ new UserInfo(
+ /* id= */ 11,
+ "tf_created_user",
+ /* flags= */ 0,
+ /* isRunning= */ true),
+ 13,
+ new UserInfo(
+ /* id= */ 13,
+ "tf_created_user",
+ /* flags= */ 0,
+ /* isRunning= */ false));
+
+ mockGetMaxNumberOfUsersSupported(3);
+ mockGetUserInfos(existingUsers);
+ Exception cause = mockCreateUserFailure("D'OH!");
+
+ TargetSetupError e = assertThrows(TargetSetupError.class, () -> mPreparer.setUp(mTestInfo));
+ assertThat(e).hasCauseThat().isSameInstanceAs(cause);
+
+ // verify that it removed the existing tradefed users.
+ verifyUserRemoved(11);
+ verifyUserRemoved(13);
+ }
+
+ @Test
+ public void testSetUp_createUserfailed() throws Exception {
+ Exception cause = mockCreateUserFailure("D'OH!");
+
+ TargetSetupError e = assertThrows(TargetSetupError.class, () -> mPreparer.setUp(mTestInfo));
+
+ assertThat(e).hasCauseThat().isSameInstanceAs(cause);
+ }
+
+ @Test
+ public void testSetUp_starUserFailed() throws Exception {
+ mockListDisplayIdsForStartingVisibleBackgroundUsers(orderedSetOf(108));
+ mockCreateUser(12);
+ mockStartUserVisibleOnBackground(42, 108, /*result= */ false);
+
+ TargetSetupError e = assertThrows(TargetSetupError.class, () -> mPreparer.setUp(mTestInfo));
+
+ assertThat(e).hasMessageThat().containsMatch(".ailed.*start.*12.*108");
+ }
+
+ @Test
+ public void testTearDown_only() throws Exception {
+ mPreparer.tearDown(mTestInfo, /* e= */ null);
+
+ verifyNoUserRemoved();
+ verifyNoUserStopped();
+ }
+
+ private <T> Set<T> orderedSetOf(@SuppressWarnings("unchecked") T... elements) {
+ return new LinkedHashSet<>(Arrays.asList(elements));
+ }
+
+ private void setParam(String key, String value) throws ConfigurationException {
+ CLog.i("Setting param: '%s'='%s'", key, value);
+ mSetter.setOptionValue(key, value);
+ }
+
+ private void mockGetUserInfos(Map<Integer, UserInfo> existingUsers) throws Exception {
+ mTestDeviceMockHelper.mockGetUserInfos(existingUsers);
+ }
+
+ private void mockGetMaxNumberOfUsersSupported(int max) throws Exception {
+ mTestDeviceMockHelper.mockGetMaxNumberOfUsersSupported(max);
+ }
+
+ private void mockStartUserVisibleOnBackground(int userId, int displayId) throws Exception {
+ mTestDeviceMockHelper.mockStartUserVisibleOnBackground(userId, displayId);
+ }
+
+ private void mockStartUserVisibleOnBackground(int userId, int displayId, boolean result)
+ throws Exception {
+ mTestDeviceMockHelper.mockStartUserVisibleOnBackground(userId, displayId, result);
+ }
+
+ private void mockCreateUser(int userId) throws Exception {
+ mTestDeviceMockHelper.mockCreateUser(userId);
+ }
+
+ private IllegalStateException mockCreateUserFailure(String message) throws Exception {
+ return mTestDeviceMockHelper.mockCreateUserFailure(message);
+ }
+
+ private void mockIsVisibleBackgroundUsersSupported(boolean supported) throws Exception {
+ mTestDeviceMockHelper.mockIsVisibleBackgroundUsersSupported(supported);
+ }
+
+ private void mockIsVisibleBackgroundUsersOnDefaultDisplaySupported(boolean supported)
+ throws Exception {
+ mTestDeviceMockHelper.mockIsVisibleBackgroundUsersOnDefaultDisplaySupported(supported);
+ }
+
+ private void mockIsUserVisibleOnDisplay(int userId, int displayId) throws Exception {
+ mTestDeviceMockHelper.mockIsUserVisibleOnDisplay(userId, displayId);
+ }
+
+ private void mockListDisplayIdsForStartingVisibleBackgroundUsers(Set<Integer> displays)
+ throws Exception {
+ mTestDeviceMockHelper.mockListDisplayIdsForStartingVisibleBackgroundUsers(displays);
+ }
+
+ private void verifyNoUserCreated() throws Exception {
+ mTestDeviceMockHelper.verifyNoUserCreated();
+ }
+
+ private void verifyUserCreated() throws Exception {
+ mTestDeviceMockHelper.verifyUserCreated();
+ }
+
+ private void verifyNoUserSwitched() throws Exception {
+ mTestDeviceMockHelper.verifyNoUserSwitched();
+ }
+
+ private void verifyNoUserStarted() throws Exception {
+ mTestDeviceMockHelper.verifyNoUserStarted();
+ }
+
+ private void verifyNoUserStartedVisibleOnBackground() throws Exception {
+ mTestDeviceMockHelper.verifyNoUserStartedVisibleOnBackground();
+ }
+
+ private void verifyUserStartedVisibleOnBackground(int userId, int displayId) throws Exception {
+ mTestDeviceMockHelper.verifyUserStartedVisibleOnBackground(userId, displayId);
+ }
+
+ private void verifyUserStopped(int userId) throws Exception {
+ mTestDeviceMockHelper.verifyUserStopped(userId);
+ }
+
+ private void verifyNoUserStopped() throws Exception {
+ mTestDeviceMockHelper.verifyNoUserStopped();
+ }
+
+ private void verifyNoUserRemoved() throws Exception {
+ mTestDeviceMockHelper.verifyNoUserRemoved();
+ }
+
+ private void verifyUserRemoved(int userId) throws Exception {
+ mTestDeviceMockHelper.verifyUserRemoved(userId);
+ }
+
+ private void verifyUserNotRemoved(int userId) throws Exception {
+ mTestDeviceMockHelper.verifyUserNotRemoved(userId);
+ }
+
+ private void verifyTestInfoProperty(String key, String expectedValue) {
+ String actualValue = mTestInfo.properties().get(key);
+ assertWithMessage("value of property %s (all properties: %s)", key, mTestInfo.properties())
+ .that(actualValue)
+ .isEqualTo(expectedValue);
+ }
+}
diff --git a/javatests/com/android/tradefed/targetprep/sync/DeviceSyncHelperFuncTest.java b/javatests/com/android/tradefed/targetprep/sync/DeviceSyncHelperFuncTest.java
index fd2fe83..59412eb 100644
--- a/javatests/com/android/tradefed/targetprep/sync/DeviceSyncHelperFuncTest.java
+++ b/javatests/com/android/tradefed/targetprep/sync/DeviceSyncHelperFuncTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertTrue;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
@@ -37,6 +38,7 @@
public void testSyncDevice() throws DeviceNotAvailableException {
String buildId = getDevice().getProperty("ro.system.build.version.incremental");
CLog.d("%s / buildid: %s", getDevice().getProperty("ro.build.fingerprint"), buildId);
+ printBuildProp(getDevice());
File targetFiles = getBuild().getFile("target_files");
if (targetFiles == null || !targetFiles.exists() || !targetFiles.isDirectory()) {
@@ -50,7 +52,14 @@
String afterBuildId = getDevice().getProperty("ro.system.build.version.incremental");
CLog.d("Initial build: %s. Final build: %s", buildId, afterBuildId);
CLog.d("%s", getDevice().getProperty("ro.build.fingerprint"));
+ printBuildProp(getDevice());
Truth.assertThat(buildId).isNotEqualTo(afterBuildId);
}
+
+ private void printBuildProp(ITestDevice device) throws DeviceNotAvailableException {
+ String output = device.executeAdbCommand("shell", "cat", "/system/build.prop");
+ CLog.e("================ build.prop");
+ CLog.e(output);
+ }
}
diff --git a/javatests/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java b/javatests/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
index 0dfb652..827be8f 100644
--- a/javatests/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
+++ b/javatests/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -64,7 +65,9 @@
import java.io.Writer;
import java.util.Arrays;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
/** Unit tests for {@link MoblyBinaryHostTest}. */
@RunWith(JUnit4.class)
@@ -88,6 +91,7 @@
private File mVenvDir;
private DeviceBuildInfo mMockBuildInfo;
private TestInformation mTestInfo;
+ private Set<String> mIncludeFilters = new LinkedHashSet<>();
@Before
public void setUp() throws Exception {
@@ -145,7 +149,7 @@
mSpyTest.run(mTestInfo, Mockito.mock(ITestInvocationListener.class));
verify(mSpyTest.getRunUtil()).runTimedCmd(anyLong(), any());
- assertFalse(new File(mSpyTest.getLogDirAbsolutePath()).exists());
+ assertNull(mSpyTest.getLogDirFile());
}
@Test
@@ -184,7 +188,7 @@
mSpyTest.run(mTestInfo, Mockito.mock(ITestInvocationListener.class));
verify(mSpyTest.getRunUtil()).runTimedCmd(anyLong(), any());
- assertFalse(new File(mSpyTest.getLogDirAbsolutePath()).exists());
+ assertNull(mSpyTest.getLogDirFile());
}
@Test
@@ -227,7 +231,7 @@
assertThat(e)
.hasMessageThat()
.contains("Fail to find test summary file test_summary.yaml under directory");
- assertFalse(new File(mSpyTest.getLogDirAbsolutePath()).exists());
+ assertNull(mSpyTest.getLogDirFile());
}
}
@@ -384,6 +388,28 @@
}
@Test
+ public void testBuildCommandLineArrayWithIncludeFilter() throws Exception {
+ Mockito.doReturn(DEVICE_SERIAL).when(mMockDevice).getSerialNumber();
+ Mockito.doReturn(LOG_PATH).when(mSpyTest).getLogDirAbsolutePath();
+ mIncludeFilters.addAll(
+ Arrays.asList("ExampleTest#test_print_addresses", "ExampleTest#test_le_connect"));
+ mSpyTest.addAllIncludeFilters(mIncludeFilters);
+ String[] cmdArray = mSpyTest.buildCommandLineArray(BINARY_PATH, "path");
+ Truth.assertThat(cmdArray)
+ .isEqualTo(
+ new String[] {
+ BINARY_PATH,
+ "--",
+ "--config=path",
+ "--device_serial=" + DEVICE_SERIAL,
+ "--log_path=" + LOG_PATH,
+ "--tests",
+ "ExampleTest.test_print_addresses",
+ "ExampleTest.test_le_connect"
+ });
+ }
+
+ @Test
public void testProcessYamlTestResultsSuccess() throws Exception {
Mockito.doNothing().when(mSpyTest).reportLogs(any(), any());
mMockSummaryInputStream = Mockito.mock(InputStream.class);
diff --git a/javatests/com/android/tradefed/testtype/suite/ModuleListenerTest.java b/javatests/com/android/tradefed/testtype/suite/ModuleListenerTest.java
index dc8c882..b37f1bb 100644
--- a/javatests/com/android/tradefed/testtype/suite/ModuleListenerTest.java
+++ b/javatests/com/android/tradefed/testtype/suite/ModuleListenerTest.java
@@ -20,6 +20,8 @@
import static org.junit.Assert.assertTrue;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
@@ -43,7 +45,8 @@
@Before
public void setUp() {
mStubListener = new ITestInvocationListener() {};
- mListener = new ModuleListener(mStubListener);
+ IInvocationContext context = new InvocationContext();
+ mListener = new ModuleListener(mStubListener, context);
}
/** Test that a regular execution yield the proper number of tests. */
diff --git a/javatests/com/android/tradefed/testtype/suite/RemoteTestTimeOutEnforcerTest.java b/javatests/com/android/tradefed/testtype/suite/RemoteTestTimeOutEnforcerTest.java
index 46e929e..f79fe09 100644
--- a/javatests/com/android/tradefed/testtype/suite/RemoteTestTimeOutEnforcerTest.java
+++ b/javatests/com/android/tradefed/testtype/suite/RemoteTestTimeOutEnforcerTest.java
@@ -53,7 +53,6 @@
@Before
public void setUp() {
- mListener = new ModuleListener(mock(ITestInvocationListener.class));
mIRemoteTest = new StubTest();
mConfigurationDescriptor = new ConfigurationDescriptor();
mModuleDefinition = Mockito.mock(ModuleDefinition.class);
@@ -64,6 +63,8 @@
Mockito.when(mModuleDefinition.getId()).thenReturn(mModuleName);
Mockito.when(mModuleDefinition.getModuleInvocationContext())
.thenReturn(mModuleInvocationContext);
+ mListener =
+ new ModuleListener(mock(ITestInvocationListener.class), mModuleInvocationContext);
mEnforcer =
new RemoteTestTimeOutEnforcer(mListener, mModuleDefinition, mIRemoteTest, mTimeout);
}
diff --git a/javatests/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java b/javatests/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
index 02fb490..9335673 100644
--- a/javatests/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
+++ b/javatests/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
@@ -775,20 +775,34 @@
}
/**
- * Test for {@link TestMappingSuiteRunner#dedupTestInfos(Set)} that tests with the same test
- * options would be filtered out.
+ * Test for {@link TestMappingSuiteRunner#dedupTestInfos(File, Set)} that tests with the same
+ * test options would be filtered out.
*/
@Test
public void testDedupTestInfos() throws Exception {
Set<TestInfo> testInfos = new HashSet<>();
testInfos.add(createTestInfo("test", "path"));
testInfos.add(createTestInfo("test", "path2"));
- assertEquals(1, mRunner.dedupTestInfos(testInfos).size());
+ assertEquals(1, mRunner.dedupTestInfos(new File("anything"), testInfos).size());
TestInfo anotherInfo = new TestInfo("test", "folder3", false);
anotherInfo.addOption(new TestOption("include-filter", "value1"));
testInfos.add(anotherInfo);
- assertEquals(2, mRunner.dedupTestInfos(testInfos).size());
+ assertEquals(2, mRunner.dedupTestInfos(new File("anything"), testInfos).size());
+
+ // Aggregate the test-mapping sources with the same test options.
+ TestInfo anotherInfo2 = new TestInfo("test", "folder4", false);
+ anotherInfo2.addOption(new TestOption("include-filter", "value1"));
+ TestInfo anotherInfo3 = new TestInfo("test", "folder5", false);
+ anotherInfo3.addOption(new TestOption("include-filter", "value1"));
+ testInfos.clear();
+ testInfos = new HashSet<>(Arrays.asList(anotherInfo, anotherInfo2, anotherInfo3));
+ Set<TestInfo> dedupTestInfos = mRunner.dedupTestInfos(new File("anything"), testInfos);
+ assertEquals(1, dedupTestInfos.size());
+ TestInfo dedupTestInfo = dedupTestInfos.iterator().next();
+ Set<String> expected_sources =
+ new HashSet<>(Arrays.asList("folder3", "folder4", "folder5"));
+ assertEquals(expected_sources, dedupTestInfo.getSources());
}
/**
diff --git a/javatests/com/android/tradefed/testtype/suite/params/SecondaryUserHandlerTest.java b/javatests/com/android/tradefed/testtype/suite/params/SecondaryUserHandlerTest.java
index bb793a5..fcf0bd6 100644
--- a/javatests/com/android/tradefed/testtype/suite/params/SecondaryUserHandlerTest.java
+++ b/javatests/com/android/tradefed/testtype/suite/params/SecondaryUserHandlerTest.java
@@ -15,12 +15,13 @@
*/
package com.android.tradefed.testtype.suite.params;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
+import static com.google.common.truth.Truth.assertThat;
import com.android.tradefed.config.Configuration;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.targetprep.CreateUserPreparer;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.RunCommandTargetPreparer;
import org.junit.Before;
import org.junit.Test;
@@ -29,7 +30,7 @@
/** Unit tests for {@link SecondaryUserHandler}. */
@RunWith(JUnit4.class)
-public class SecondaryUserHandlerTest {
+public final class SecondaryUserHandlerTest {
private SecondaryUserHandler mHandler;
private IConfiguration mModuleConfig;
@@ -44,15 +45,14 @@
@Test
public void testApplySetup() {
TestFilterable test = new TestFilterable();
- assertEquals(0, test.getExcludeAnnotations().size());
+ assertThat(test.getExcludeAnnotations()).isEmpty();
mModuleConfig.setTest(test);
mHandler.applySetup(mModuleConfig);
// User zero is filtered
- assertEquals(1, test.getExcludeAnnotations().size());
- assertEquals(
- "android.platform.test.annotations.SystemUserOnly",
- test.getExcludeAnnotations().iterator().next());
+ assertThat(test.getExcludeAnnotations()).hasSize(1);
+ assertThat(test.getExcludeAnnotations().iterator().next())
+ .isEqualTo("android.platform.test.annotations.SystemUserOnly");
}
/**
@@ -62,6 +62,14 @@
@Test
public void testAddParameterSpecificConfig() {
mHandler.addParameterSpecificConfig(mModuleConfig);
- assertTrue(mModuleConfig.getTargetPreparers().get(0) instanceof CreateUserPreparer);
+ assertThat(mModuleConfig.getTargetPreparers()).hasSize(2);
+
+ ITargetPreparer preparer1 = mModuleConfig.getTargetPreparers().get(0);
+ assertThat(preparer1).isInstanceOf(CreateUserPreparer.class);
+ ITargetPreparer preparer2 = mModuleConfig.getTargetPreparers().get(1);
+ assertThat(preparer2).isInstanceOf(RunCommandTargetPreparer.class);
+ assertThat(((RunCommandTargetPreparer) preparer2).getCommands())
+ .containsExactlyElementsIn(
+ SecondaryUserOnSecondaryDisplayHandler.LOCATION_COMMANDS);
}
}
diff --git a/javatests/com/android/tradefed/testtype/suite/params/SecondaryUserOnDefaultDisplayHandlerTest.java b/javatests/com/android/tradefed/testtype/suite/params/SecondaryUserOnDefaultDisplayHandlerTest.java
new file mode 100644
index 0000000..8fdc70f
--- /dev/null
+++ b/javatests/com/android/tradefed/testtype/suite/params/SecondaryUserOnDefaultDisplayHandlerTest.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2023 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.tradefed.testtype.suite.params;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.targetprep.CreateUserPreparer;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.RunCommandTargetPreparer;
+import com.android.tradefed.targetprep.VisibleBackgroundUserPreparer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link SecondaryUserOnSecondaryDisplayHandler}. */
+@RunWith(JUnit4.class)
+public final class SecondaryUserOnDefaultDisplayHandlerTest {
+
+ private SecondaryUserOnSecondaryDisplayHandler mHandler;
+ private IConfiguration mModuleConfig;
+
+ @Before
+ public void setUp() {
+ mHandler = new SecondaryUserOnSecondaryDisplayHandler();
+ mModuleConfig = new Configuration("test", "test");
+ }
+
+ /** Test that when a module configuration go through the handler it gets tuned properly. */
+ @Test
+ public void testApplySetup() {
+ TestFilterable test = new TestFilterable();
+ assertThat(test.getExcludeAnnotations()).isEmpty();
+ mModuleConfig.setTest(test);
+ mHandler.applySetup(mModuleConfig);
+
+ // User zero is filtered
+ assertThat(test.getExcludeAnnotations()).hasSize(1);
+ assertThat(test.getExcludeAnnotations().iterator().next())
+ .isEqualTo("android.platform.test.annotations.SystemUserOnly");
+ }
+
+ @Test
+ public void testGetParameterIdentifier() {
+ assertThat(mHandler.getParameterIdentifier())
+ .isEqualTo("secondary_user_on_secondary_display");
+ }
+
+ /**
+ * Test that when a module configuration goes through the handler's
+ * addParameterSpecificConfiguration, {@link CreateUserPreparer} is added correctly.
+ */
+ @Test
+ public void testAddParameterSpecificConfig() {
+ mHandler.addParameterSpecificConfig(mModuleConfig);
+ assertThat(mModuleConfig.getTargetPreparers()).hasSize(2);
+
+ ITargetPreparer preparer1 = mModuleConfig.getTargetPreparers().get(0);
+ assertThat(preparer1).isInstanceOf(VisibleBackgroundUserPreparer.class);
+ VisibleBackgroundUserPreparer userPreparer = (VisibleBackgroundUserPreparer) preparer1;
+ assertThat(userPreparer.getDisplayId())
+ .isEqualTo(VisibleBackgroundUserPreparer.INVALID_DISPLAY);
+ ITargetPreparer preparer2 = mModuleConfig.getTargetPreparers().get(1);
+ assertThat(preparer2).isInstanceOf(RunCommandTargetPreparer.class);
+ assertThat(((RunCommandTargetPreparer) preparer2).getCommands())
+ .containsExactlyElementsIn(
+ SecondaryUserOnSecondaryDisplayHandler.LOCATION_COMMANDS);
+ }
+}
diff --git a/javatests/com/android/tradefed/testtype/suite/params/SecondaryUserOnSecondaryDisplayHandlerTest.java b/javatests/com/android/tradefed/testtype/suite/params/SecondaryUserOnSecondaryDisplayHandlerTest.java
new file mode 100644
index 0000000..9bc8c31e
--- /dev/null
+++ b/javatests/com/android/tradefed/testtype/suite/params/SecondaryUserOnSecondaryDisplayHandlerTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 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.tradefed.testtype.suite.params;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.android.tradefed.config.Configuration;
+import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.targetprep.CreateUserPreparer;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.RunCommandTargetPreparer;
+import com.android.tradefed.targetprep.VisibleBackgroundUserPreparer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link SecondaryUserOnSecondaryDisplayHandler}. */
+@RunWith(JUnit4.class)
+public final class SecondaryUserOnSecondaryDisplayHandlerTest {
+
+ private SecondaryUserOnDefaultDisplayHandler mHandler;
+ private IConfiguration mModuleConfig;
+
+ @Before
+ public void setUp() {
+ mHandler = new SecondaryUserOnDefaultDisplayHandler();
+ mModuleConfig = new Configuration("test", "test");
+ }
+
+ /** Test that when a module configuration go through the handler it gets tuned properly. */
+ @Test
+ public void testApplySetup() {
+ TestFilterable test = new TestFilterable();
+ assertThat(test.getExcludeAnnotations()).isEmpty();
+ mModuleConfig.setTest(test);
+ mHandler.applySetup(mModuleConfig);
+
+ // User zero is filtered
+ assertThat(test.getExcludeAnnotations()).hasSize(1);
+ assertThat(test.getExcludeAnnotations().iterator().next())
+ .isEqualTo("android.platform.test.annotations.SystemUserOnly");
+ }
+
+ @Test
+ public void testGetParameterIdentifier() {
+ assertThat(mHandler.getParameterIdentifier())
+ .isEqualTo("secondary_user_on_default_display");
+ }
+
+ /**
+ * Test that when a module configuration goes through the handler's
+ * addParameterSpecificConfiguration, {@link CreateUserPreparer} is added correctly.
+ */
+ @Test
+ public void testAddParameterSpecificConfig() {
+ mHandler.addParameterSpecificConfig(mModuleConfig);
+ assertThat(mModuleConfig.getTargetPreparers()).hasSize(2);
+
+ ITargetPreparer preparer1 = mModuleConfig.getTargetPreparers().get(0);
+ assertThat(preparer1).isInstanceOf(VisibleBackgroundUserPreparer.class);
+ ITargetPreparer preparer2 = mModuleConfig.getTargetPreparers().get(1);
+ assertThat(preparer2).isInstanceOf(RunCommandTargetPreparer.class);
+ assertThat(((RunCommandTargetPreparer) preparer2).getCommands())
+ .containsExactlyElementsIn(
+ SecondaryUserOnSecondaryDisplayHandler.LOCATION_COMMANDS);
+ }
+}
diff --git a/javatests/com/android/tradefed/util/ITestDeviceMockHelper.java b/javatests/com/android/tradefed/util/ITestDeviceMockHelper.java
new file mode 100644
index 0000000..adcf57a
--- /dev/null
+++ b/javatests/com/android/tradefed/util/ITestDeviceMockHelper.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2023 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.tradefed.util;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.UserInfo;
+
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/** Helper class for mocking {@link ITestDevice} methods. */
+public final class ITestDeviceMockHelper {
+
+ private final ITestDevice mMockDevice;
+
+ public ITestDeviceMockHelper(ITestDevice mockDevice) {
+ mMockDevice = Objects.requireNonNull(mockDevice);
+ }
+
+ public void mockGetCurrentUser(int userId) throws Exception {
+ when(mMockDevice.getCurrentUser()).thenReturn(userId);
+ }
+
+ public void mockGetUserInfos(Map<Integer, UserInfo> existingUsers) throws Exception {
+ when(mMockDevice.getUserInfos()).thenReturn(existingUsers);
+ }
+
+ public void mockGetMaxNumberOfUsersSupported(int max) throws Exception {
+ when(mMockDevice.getMaxNumberOfUsersSupported()).thenReturn(max);
+ }
+
+ public void mockSwitchUser(int userId) throws Exception {
+ when(mMockDevice.switchUser(userId)).thenReturn(true);
+ }
+
+ public void mockStartUser(int userId) throws Exception {
+ when(mMockDevice.startUser(userId, /* waitFlag= */ true)).thenReturn(true);
+ }
+
+ public void mockStartUserVisibleOnBackground(int userId, int displayId) throws Exception {
+ mockStartUserVisibleOnBackground(userId, displayId, /* result= */ true);
+ }
+
+ public void mockStartUserVisibleOnBackground(int userId, int displayId, boolean result)
+ throws Exception {
+ when(mMockDevice.startVisibleBackgroundUser(userId, displayId, /* waitFlag= */ true))
+ .thenReturn(result);
+ }
+
+ public void mockCreateUser(int userId) throws Exception {
+ when(mMockDevice.createUser(any())).thenReturn(userId);
+ }
+
+ public IllegalStateException mockCreateUserFailure(String message) throws Exception {
+ IllegalStateException e = new IllegalStateException(message);
+ when(mMockDevice.createUser(any())).thenThrow(e);
+ return e;
+ }
+
+ public void mockIsVisibleBackgroundUsersSupported(boolean supported) throws Exception {
+ when(mMockDevice.isVisibleBackgroundUsersSupported()).thenReturn(supported);
+ }
+
+ public void mockIsVisibleBackgroundUsersOnDefaultDisplaySupported(boolean supported)
+ throws Exception {
+ when(mMockDevice.isVisibleBackgroundUsersOnDefaultDisplaySupported()).thenReturn(supported);
+ }
+
+ public void mockIsUserVisibleOnDisplay(int userId, int displayId) throws Exception {
+ when(mMockDevice.isUserVisibleOnDisplay(userId, displayId)).thenReturn(true);
+ }
+
+ public void mockListDisplayIdsForStartingVisibleBackgroundUsers(Set<Integer> displays)
+ throws Exception {
+ when(mMockDevice.listDisplayIdsForStartingVisibleBackgroundUsers()).thenReturn(displays);
+ }
+
+ public void verifyNoUserCreated() throws Exception {
+ verify(mMockDevice, never()).createUser(any());
+ }
+
+ public void verifyUserCreated() throws Exception {
+ verify(mMockDevice).createUser(any());
+ }
+
+ public void verifyNoUserSwitched() throws Exception {
+ verify(mMockDevice, never()).switchUser(anyInt());
+ }
+
+ public void verifyUserSwitched(int userId) throws Exception {
+ verify(mMockDevice).switchUser(userId);
+ }
+
+ public void verifyNoUserStarted() throws Exception {
+ verify(mMockDevice, never()).startUser(anyInt());
+ verify(mMockDevice, never()).startUser(anyInt(), anyBoolean());
+ }
+
+ public void verifyNoUserStartedVisibleOnBackground() throws Exception {
+ verify(mMockDevice, never()).startVisibleBackgroundUser(anyInt(), anyInt(), anyBoolean());
+ }
+
+ public void verifyUserStartedVisibleOnBackground(int userId, int displayId) throws Exception {
+ verify(mMockDevice).startVisibleBackgroundUser(userId, displayId, /* waitFlag= */ true);
+ }
+
+ public void verifyUserStopped(int userId) throws Exception {
+ verify(mMockDevice).stopUser(userId, /* waitFlag= */ true, /* forceFlag= */ true);
+ }
+
+ public void verifyNoUserStopped() throws Exception {
+ verify(mMockDevice, never()).stopUser(anyInt(), anyBoolean(), anyBoolean());
+ }
+
+ public void verifyNoUserRemoved() throws Exception {
+ verify(mMockDevice, never()).removeUser(anyInt());
+ }
+
+ public void verifyUserRemoved(int userId) throws Exception {
+ verify(mMockDevice).removeUser(userId);
+ }
+
+ public void verifyUserNotRemoved(int userId) throws Exception {
+ verify(mMockDevice, never()).removeUser(userId);
+ }
+
+ public void verifyListDisplayIdsForStartingVisibleBackgroundUsersNeverCalled()
+ throws Exception {
+ verify(mMockDevice, never()).listDisplayIdsForStartingVisibleBackgroundUsers();
+ }
+}
diff --git a/javatests/com/android/tradefed/util/testmapping/TestInfoTest.java b/javatests/com/android/tradefed/util/testmapping/TestInfoTest.java
index dbc97a5..591742b 100644
--- a/javatests/com/android/tradefed/util/testmapping/TestInfoTest.java
+++ b/javatests/com/android/tradefed/util/testmapping/TestInfoTest.java
@@ -50,5 +50,6 @@
"Host: false",
info.toString());
assertEquals("test1 - false", info.getNameAndHostOnly());
+ assertEquals("test1[option1:value1, option2:value2]", info.getNameOption());
}
}
diff --git a/lite/Android.bp b/lite/Android.bp
index a67af7d..8d6b795 100644
--- a/lite/Android.bp
+++ b/lite/Android.bp
@@ -34,4 +34,6 @@
static_libs: [
"junit",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
}
diff --git a/reference_tests/Android.bp b/reference_tests/Android.bp
index 0041a72..87adbcd 100644
--- a/reference_tests/Android.bp
+++ b/reference_tests/Android.bp
@@ -21,6 +21,8 @@
srcs: [
"src/java/com/android/tradefed/referencetests/SimpleFailingTest.java",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
static_libs: [
"junit",
],
@@ -34,6 +36,8 @@
srcs: [
"src/java/com/android/tradefed/referencetests/OnePassOneFailParamTest.java",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
static_libs: [
"junit",
],
@@ -47,6 +51,8 @@
srcs: [
"src/java/com/android/tradefed/referencetests/OnePassingOneFailingTest.java",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
static_libs: [
"junit",
],
@@ -60,6 +66,8 @@
srcs: [
"src/java/com/android/tradefed/referencetests/SimplePassingTest.java",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
static_libs: [
"junit",
],
@@ -73,6 +81,8 @@
srcs: [
"src/java/com/android/tradefed/referencetests/PassIgnoreAssumeTest.java",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
static_libs: [
"junit",
],
@@ -88,6 +98,8 @@
"src/java/com/android/tradefed/referencetests/OnePassingOneFailingTest.java",
"src/java/com/android/tradefed/referencetests/SimpleFailingTest.java",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
libs: [
"junit",
],
diff --git a/remote/Android.bp b/remote/Android.bp
index 5dabc9e..693de88 100644
--- a/remote/Android.bp
+++ b/remote/Android.bp
@@ -36,4 +36,6 @@
"ddmlib-prebuilt",
"devtools-annotations-prebuilt",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
}
diff --git a/res/perfetto/trace_config.textproto b/res/perfetto/trace_config.textproto
index 0c42c0d..bc63e5a 100644
--- a/res/perfetto/trace_config.textproto
+++ b/res/perfetto/trace_config.textproto
@@ -13,63 +13,126 @@
# limitations under the License.
# proto-message: TraceConfig
-# Enable periodic flushing of the trace buffer into the output file.
+# Trace config originally supplied in b/230177578.
+
+# Periodically writes the central tracing buffers (defined below) to disk.
write_into_file: true
+file_write_period_ms: 2000
-# Writes the userspace buffer into the file every 1s.
-file_write_period_ms: 1000
+# Ensure worst-case reordering of events in the central tracing buffers.
+flush_period_ms: 8000
-# See b/126487238 - we need to guarantee ordering of events.
-flush_period_ms: 30000
-
-# The trace buffers needs to be big enough to hold |file_write_period_ms| of
+# The trace buffers need to be big enough to hold |file_write_period_ms| of
# trace data. The trace buffer sizing depends on the number of trace categories
# enabled and the device activity.
-
-# RSS events
-buffers {
- size_kb: 16384
- fill_policy: RING_BUFFER
+buffers: {
+ size_kb: 20480
+ fill_policy: DISCARD
}
-# procfs polling
-buffers {
- size_kb: 8192
- fill_policy: RING_BUFFER
-}
-
-data_sources {
- config {
+data_sources: {
+ config: {
name: "linux.ftrace"
target_buffer: 0
- ftrace_config {
+ ftrace_config: {
throttle_rss_stat: true
- # These parameters affect only the kernel trace buffer size and how
- # frequently it gets moved into the userspace buffer defined above.
- buffer_size_kb: 16384
- drain_period_ms: 250
+ compact_sched: {
+ enabled: true
+ }
- # We need to do process tracking to ensure kernel ftrace events targeted at short-lived
- # threads are associated correctly
- ftrace_events: "task/task_newtask"
- ftrace_events: "task/task_rename"
+ # core: scheduling
+ ftrace_events: "sched/sched_switch"
+ ftrace_events: "sched/sched_waking"
+
+ # core: process lifetime events
+ ftrace_events: "sched/sched_wakeup_new"
ftrace_events: "sched/sched_process_exit"
ftrace_events: "sched/sched_process_free"
+ ftrace_events: "task/task_newtask"
+ ftrace_events: "task/task_rename"
- # Old (kernel) LMK
- ftrace_events: "lowmemorykiller/lowmemory_kill"
+ # scheduling: why any given thread is blocked
+ ftrace_events: "sched/sched_blocked_reason"
+ # device suspend/resume events
+ ftrace_events: "power/suspend_resume"
- # New (userspace) LMK
+ # RSS and ION buffer events
+ ftrace_events: "dmabuf_heap/dma_heap_stat"
+ ftrace_events: "fastrpc/fastrpc_dma_stat"
+ ftrace_events: "gpu_mem/gpu_mem_total"
+ ftrace_events: "ion/ion_stat"
+ ftrace_events: "kmem/ion_heap_grow"
+ ftrace_events: "kmem/ion_heap_shrink"
+ ftrace_events: "kmem/rss_stat"
+
+ # optional: LMK
atrace_apps: "lmkd"
+ ftrace_events: "lowmemorykiller/lowmemory_kill"
+ ftrace_events: "oom/oom_score_adj_update"
+ ftrace_events: "oom/mark_victim"
+
+ # userspace events from system_server
+ atrace_categories: "ss"
+ atrace_apps: "system_server"
+ # userspace events from activity and window managers
+ atrace_categories: "am"
+ atrace_categories: "wm"
+ # userspace events from java and c runtimes
+ atrace_categories: "dalvik"
+ atrace_categories: "bionic"
+ # userspace events from systemui
+ atrace_apps: "com.android.systemui"
+
+ # groups that expand into power events (mix of userspace and ftrace)
+ atrace_categories: "freq"
+ # "thermal" removed for APIs <= 30, temporarily.
+ atrace_categories: "power"
+
+ # binder & HALs
+ atrace_categories: "aidl"
+ atrace_categories: "hal"
+ atrace_categories: "binder_driver"
+
+ # Other userspace event groups, see
+ # frameworks/native/cmds/atrace/atrace.cpp in the Android tree for
+ # available categories. The encoding of userspace events is very verbose
+ # so keep the list focused or you will need to readjust the buffer sizes
+ # to avoid data loss.
+ atrace_categories: "disk"
+ atrace_categories: "gfx"
+ atrace_categories: "res"
+ atrace_categories: "view"
+ atrace_categories: "idle"
+ atrace_categories: "webview"
+
+ # Default to enabling userspace events from all apps.
atrace_apps: "*"
# Following line will be used to inject additional configs. Do not remove or modify.
# {injected_config}
-
- atrace_categories: "am"
- atrace_categories: "dalvik"
- atrace_categories: "binder_driver"
}
}
}
+data_sources {
+ config {
+ name: "linux.process_stats"
+ target_buffer: 0
+ # polled per-process memory counters and process/thread names.
+ # If you don't want the polled counters, remove the "process_stats_config"
+ # section, but keep the data source itself as it still provides on-demand
+ # thread/process naming for ftrace data below.
+ process_stats_config {
+ proc_stats_poll_ms: 500
+ scan_all_processes_on_start: true
+ }
+ }
+}
+
+data_sources: {
+ config {
+ # rendering: expected vs actual frame timeline tracks
+ name: "android.surfaceflinger.frametimeline"
+ target_buffer: 0
+ }
+}
diff --git a/src/com/android/tradefed/build/BuildInfo.java b/src/com/android/tradefed/build/BuildInfo.java
index 3f81bd2..af77b2c 100644
--- a/src/com/android/tradefed/build/BuildInfo.java
+++ b/src/com/android/tradefed/build/BuildInfo.java
@@ -23,6 +23,7 @@
import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.service.TradefedFeatureClient;
@@ -711,7 +712,8 @@
InvocationMetricKey.STAGE_TESTS_INDIVIDUAL_DOWNLOADS, fileName);
List<String> includeFilters = Arrays.asList(String.format("/%s?($|/)", fileName));
- try (TradefedFeatureClient client = new TradefedFeatureClient()) {
+ try (CloseableTraceScope stage = new CloseableTraceScope("stageRemoteFile:" + fileName);
+ TradefedFeatureClient client = new TradefedFeatureClient()) {
Map<String, String> args = new HashMap<>();
args.put(ResolvePartialDownload.DESTINATION_DIR, workingDir.getAbsolutePath());
args.put(ResolvePartialDownload.INCLUDE_FILTERS, String.join(";", includeFilters));
@@ -723,9 +725,12 @@
.map(p -> p.toString())
.collect(Collectors.joining(";"));
args.put(ResolvePartialDownload.REMOTE_PATHS, remotePaths);
+ long startTime = System.currentTimeMillis();
FeatureResponse rep =
client.triggerFeature(
ResolvePartialDownload.RESOLVE_PARTIAL_DOWNLOAD_FEATURE_NAME, args);
+ InvocationMetricLogger.addInvocationPairMetrics(
+ InvocationMetricKey.STAGE_REMOTE_TIME, startTime, System.currentTimeMillis());
if (rep.hasErrorInfo()) {
throw new HarnessRuntimeException(
rep.getErrorInfo().getErrorTrace(),
diff --git a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
index 2075100..845c577 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
@@ -835,7 +835,12 @@
*/
protected boolean dryRunCommand(final InvocationEventHandler handler, String[] args)
throws ConfigurationException {
- IConfiguration config = createConfiguration(args);
+ IConfiguration config = null;
+ try {
+ config = createConfiguration(args);
+ } catch (Throwable e) {
+ throw new ConfigurationException("Failed to create dry-run config", e);
+ }
if (config.getCommandOptions().isDryRunMode()) {
IInvocationContext context = new InvocationContext();
context.addDeviceBuildInfo("stub", new BuildInfo());
diff --git a/src/com/android/tradefed/command/CommandOptions.java b/src/com/android/tradefed/command/CommandOptions.java
index a991c5b..b74e732 100644
--- a/src/com/android/tradefed/command/CommandOptions.java
+++ b/src/com/android/tradefed/command/CommandOptions.java
@@ -224,6 +224,16 @@
)
private Set<AutoLogCollector> mAutoCollectors = new LinkedHashSet<>();
+ @Option(
+ name = "experiment-enabled",
+ description = "A feature flag used to enable experimental flags.")
+ private boolean mExperimentEnabled = false;
+
+ @Option(
+ name = "experimental-flags",
+ description = "Map of experimental flags that can be used for feature gating projects.")
+ private Map<String, String> mExperimentalFlags = new LinkedHashMap<>();
+
@Deprecated
@Option(
name = "logcat-on-failure",
@@ -597,6 +607,18 @@
/** {@inheritDoc} */
@Override
+ public boolean isExperimentEnabled() {
+ return mExperimentEnabled;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Map<String, String> getExperimentalFlags() {
+ return mExperimentalFlags;
+ }
+
+ /** {@inheritDoc} */
+ @Override
public boolean captureScreenshotOnFailure() {
return mScreenshotOnFailure;
}
diff --git a/src/com/android/tradefed/command/CommandScheduler.java b/src/com/android/tradefed/command/CommandScheduler.java
index a64c9ed..c169ccb 100644
--- a/src/com/android/tradefed/command/CommandScheduler.java
+++ b/src/com/android/tradefed/command/CommandScheduler.java
@@ -20,6 +20,7 @@
import com.android.ddmlib.Log;
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.build.BuildRetrievalError;
+import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.clearcut.ClearcutClient;
import com.android.tradefed.command.CommandFileParser.CommandLine;
import com.android.tradefed.command.CommandFileWatcher.ICommandFileListener;
@@ -37,6 +38,7 @@
import com.android.tradefed.config.IDeviceConfiguration;
import com.android.tradefed.config.IGlobalConfiguration;
import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.config.RetryConfigurationFactory;
import com.android.tradefed.config.SandboxConfigurationFactory;
import com.android.tradefed.config.proxy.ProxyConfiguration;
@@ -553,6 +555,7 @@
private static final String INVOC_END_EVENT_ID_KEY = "id";
private static final String INVOC_END_EVENT_ELAPSED_KEY = "elapsed-time";
private static final String INVOC_END_EVENT_TAG_KEY = "test-tag";
+ private static final String PRESUBMIT_BUILD_REGEX = "^P[0-9]+";
private final IScheduledInvocationListener[] mListeners;
private final IInvocationContext mInvocationContext;
@@ -598,6 +601,20 @@
.getInvocationData()
.containsKey(SubprocessTfLauncher.SUBPROCESS_TAG_NAME));
}
+ // Set experimental flags for non-presubmit builds
+ if (config.getCommandOptions().isExperimentEnabled()
+ && !isPresubmitBuild(mInvocationContext)) {
+ try {
+ OptionSetter setter = new OptionSetter(config.getCommandOptions());
+ for (Map.Entry<String, String> entry :
+ config.getCommandOptions().getExperimentalFlags().entrySet()) {
+ setter.setOptionValue(entry.getKey(), entry.getValue());
+ }
+ } catch (ConfigurationException e) {
+ CLog.e("Configuration Exception caught while setting experimental flags.");
+ CLog.e(e);
+ }
+ }
mStartTime = System.currentTimeMillis();
ITestInvocation instance = getInvocation();
try (CloseableTraceScope ignore = new CloseableTraceScope("init")) {
@@ -900,6 +917,18 @@
}
}
}
+
+ /**
+ * Checks if the current Invocation is for a pre-submit build or not.
+ *
+ * @param context {@link IInvocationContext} for the current test.
+ * @return returns true if invocation is for a pre-submit build, false otherwise.
+ */
+ private boolean isPresubmitBuild(IInvocationContext context) {
+ IBuildInfo build = context.getBuildInfo(context.getDevices().get(0));
+ Pattern pattern = Pattern.compile(PRESUBMIT_BUILD_REGEX);
+ return pattern.matcher(build.getBuildId()).matches();
+ }
}
/** Create a map of the devices state so they can be released appropriately. */
diff --git a/src/com/android/tradefed/command/ICommandOptions.java b/src/com/android/tradefed/command/ICommandOptions.java
index 804eb30..d9c950a 100644
--- a/src/com/android/tradefed/command/ICommandOptions.java
+++ b/src/com/android/tradefed/command/ICommandOptions.java
@@ -186,6 +186,12 @@
/** Sets the set of auto log collectors that should be added to an invocation. */
public void setAutoLogCollectors(Set<AutoLogCollector> autoLogCollectors);
+ /** Whether or not to enable experiments through experimental flags. */
+ public boolean isExperimentEnabled();
+
+ /** Returns the experimental flags map, that can be used to feature gate projects. */
+ public Map<String, String> getExperimentalFlags();
+
/** Whether or not to capture a screenshot on test case failure */
public boolean captureScreenshotOnFailure();
diff --git a/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java b/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
index 0dee110..f3e324b 100644
--- a/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
+++ b/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
@@ -34,7 +34,6 @@
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.MultiMap;
-import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.TarUtil;
import com.android.tradefed.util.ZipUtil;
@@ -595,9 +594,4 @@
}
}
}
-
- @VisibleForTesting
- IRunUtil createRunUtil() {
- return new RunUtil();
- }
}
diff --git a/src/com/android/tradefed/device/ManagedDeviceList.java b/src/com/android/tradefed/device/ManagedDeviceList.java
index f0912e9..bcd15d9 100644
--- a/src/com/android/tradefed/device/ManagedDeviceList.java
+++ b/src/com/android/tradefed/device/ManagedDeviceList.java
@@ -22,6 +22,9 @@
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.ConditionPriorityBlockingQueue.IMatcher;
+import com.google.api.client.util.Strings;
+import com.google.common.collect.ImmutableSet;
+
import java.util.ArrayList;
import java.util.ConcurrentModificationException;
import java.util.Iterator;
@@ -29,6 +32,8 @@
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.ReentrantLock;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import javax.annotation.concurrent.GuardedBy;
@@ -42,6 +47,13 @@
*/
class ManagedDeviceList implements Iterable<IManagedTestDevice> {
+ private static final Pattern IP_PATTERN =
+ Pattern.compile(ManagedTestDeviceFactory.IPADDRESS_PATTERN);
+ // Ip that are associated with special use cases and shouldn't look up the
+ // serial number. Usually because those are virtual devices and not wifi
+ // connected devices
+ private static final Set<String> RESERVED_IP = ImmutableSet.of("127.0.0.1", "0.0.0.0", "localhost");
+
/**
* A {@link IMatcher} for finding a {@link IManagedTestDevice} that can be allocated.
* Will change the device state to ALLOCATED upon finding a successful match.
@@ -154,6 +166,28 @@
return serial.length() > 1 && !serial.contains("?");
}
+ private boolean isTcpDeviceSerial(String serialString) {
+ String[] serial = serialString.split(":");
+ if (serial.length == 2) {
+ if (RESERVED_IP.contains(serial[0])) {
+ return false;
+ }
+ // Check first part is an IP
+ Matcher match = IP_PATTERN.matcher(serial[0]);
+ if (!match.find()) {
+ return false;
+ }
+ // Check second part if a port
+ try {
+ Integer.parseInt(serial[1]);
+ return true;
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ }
+ return false;
+ }
+
/**
* Update the {@link TestDevice#getDeviceState()} of devices as appropriate.
*
@@ -237,12 +271,24 @@
* @return the {@link IManagedTestDevice}.
*/
public IManagedTestDevice findOrCreate(IDevice idevice) {
- if (!isValidDeviceSerial(idevice.getSerialNumber())) {
+ String serial = idevice.getSerialNumber();
+ if (!isValidDeviceSerial(serial)) {
return null;
}
+ if (isTcpDeviceSerial(serial)) {
+ // Override serial for tcp devices into their real one
+ try {
+ String realSerial = idevice.getProperty("ro.serialno");
+ if (!Strings.isNullOrEmpty(realSerial)) {
+ serial = realSerial.trim();
+ }
+ } catch (RuntimeException e) {
+ CLog.e(e);
+ }
+ }
mListLock.lock();
try {
- IManagedTestDevice d = find(idevice.getSerialNumber());
+ IManagedTestDevice d = find(serial);
if (d == null || DeviceAllocationState.Unavailable.equals(d.getAllocationState())) {
mList.remove(d);
d = mDeviceFactory.createDevice(idevice);
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index 03bec0c..fe05eff 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -168,8 +168,6 @@
/** The time in ms to wait for a device to become unavailable. Should usually be short */
private static final int DEFAULT_UNAVAILABLE_TIMEOUT = 20 * 1000;
- /** The time in ms to wait for a recovery that we skip because of the NONE mode */
- static final int NONE_RECOVERY_MODE_DELAY = 1000;
private static final String SIM_STATE_PROP = "gsm.sim.state";
private static final String SIM_OPERATOR_PROP = "gsm.operator.alpha";
@@ -273,11 +271,13 @@
private String[] mCmd;
private long mTimeout;
private boolean mIsShellCommand;
+ private Map<String, String> mEnvMap;
- AdbAction(long timeout, String[] cmd, boolean isShell) {
+ AdbAction(long timeout, String[] cmd, boolean isShell, Map<String, String> envMap) {
mTimeout = timeout;
mCmd = cmd;
mIsShellCommand = isShell;
+ mEnvMap = envMap;
}
private void logExceptionAndOutput(CommandResult result) {
@@ -288,7 +288,14 @@
@Override
public boolean run() throws TimeoutException, IOException {
- CommandResult result = getRunUtil().runTimedCmd(mTimeout, mCmd);
+ IRunUtil runUtil = getRunUtil();
+ if (!mEnvMap.isEmpty()) {
+ runUtil = createRunUtil();
+ }
+ for (String key : mEnvMap.keySet()) {
+ runUtil.setEnvVariable(key, mEnvMap.get(key));
+ }
+ CommandResult result = runUtil.runTimedCmd(mTimeout, mCmd);
// TODO: how to determine device not present with command failing for other reasons
if (result.getStatus() == CommandStatus.EXCEPTION) {
logExceptionAndOutput(result);
@@ -406,6 +413,10 @@
return RunUtil.getDefault();
}
+ protected IRunUtil createRunUtil() {
+ return new RunUtil();
+ }
+
/** Set the Clock instance to use. */
@VisibleForTesting
protected void setClock(Clock clock) {
@@ -2202,8 +2213,15 @@
@Override
public String executeAdbCommand(long timeout, String... cmdArgs)
throws DeviceNotAvailableException {
+ return executeAdbCommand(getCommandTimeout(), new HashMap<>(), cmdArgs);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String executeAdbCommand(long timeout, Map<String, String> envMap, String... cmdArgs)
+ throws DeviceNotAvailableException {
final String[] fullCmd = buildAdbCommand(cmdArgs);
- AdbAction adbAction = new AdbAction(timeout, fullCmd, "shell".equals(cmdArgs[0]));
+ AdbAction adbAction = new AdbAction(timeout, fullCmd, "shell".equals(cmdArgs[0]), envMap);
performDeviceAction(String.format("adb %s", cmdArgs[0]), adbAction, MAX_RETRY_ATTEMPTS);
return adbAction.mOutput;
}
@@ -2546,7 +2564,6 @@
public boolean recoverDevice() throws DeviceNotAvailableException {
if (mRecoveryMode.equals(RecoveryMode.NONE)) {
CLog.i("Skipping recovery on %s", getSerialNumber());
- getRunUtil().sleep(NONE_RECOVERY_MODE_DELAY);
return false;
}
CLog.i("Attempting recovery on %s", getSerialNumber());
@@ -3080,6 +3097,10 @@
CLog.d("No ANRs at %s", ANRS_PATH);
return true;
}
+ boolean root = enableAdbRoot();
+ if (!root) {
+ CLog.d("Skipping logAnrs, need to be root.");
+ }
File localDir = null;
long startTime = System.currentTimeMillis();
try {
@@ -3448,7 +3469,14 @@
enableAdbRoot();
prePostBootSetup();
for (String command : mOptions.getPostBootCommands()) {
- executeShellCommand(command);
+ long start = System.currentTimeMillis();
+ try (CloseableTraceScope cmdTrace = new CloseableTraceScope(command)) {
+ executeShellCommand(command);
+ }
+ if (command.startsWith("sleep")) {
+ InvocationMetricLogger.addInvocationPairMetrics(
+ InvocationMetricKey.host_sleep, start, System.currentTimeMillis());
+ }
}
} finally {
long elapsed = System.currentTimeMillis() - startTime;
@@ -3912,7 +3940,7 @@
}
InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.ADB_ROOT_ROUTINE_COUNT, 1);
long startTime = System.currentTimeMillis();
- try {
+ try (CloseableTraceScope ignored = new CloseableTraceScope("adb_root")) {
CLog.i("adb root on device %s", getSerialNumber());
int attempts = MAX_RETRY_ATTEMPTS + 1;
for (int i = 1; i <= attempts; i++) {
diff --git a/src/com/android/tradefed/device/NativeDeviceStateMonitor.java b/src/com/android/tradefed/device/NativeDeviceStateMonitor.java
index 5fd6127..a4d90e1 100644
--- a/src/com/android/tradefed/device/NativeDeviceStateMonitor.java
+++ b/src/com/android/tradefed/device/NativeDeviceStateMonitor.java
@@ -216,13 +216,14 @@
Callable<BUSY_WAIT_STATUS> bootComplete =
() -> {
final CollectingOutputReceiver receiver = createOutputReceiver();
- final String cmd = "ls /system/bin/adb";
+ final String cmd = "id";
try {
getIDevice()
.executeShellCommand(
cmd, receiver, MAX_OP_TIME, TimeUnit.MILLISECONDS);
String output = receiver.getOutput();
- if (output.contains("/system/bin/adb")) {
+ if (output.contains("uid=")) {
+ CLog.i("shell ready. id output: %s", output);
return BUSY_WAIT_STATUS.SUCCESS;
}
} catch (IOException
diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java
index 1ee927e..ee79e07 100644
--- a/src/com/android/tradefed/device/TestDevice.java
+++ b/src/com/android/tradefed/device/TestDevice.java
@@ -162,7 +162,7 @@
// Then there is time to run the actual task. Set the maximum timeout value big enough.
private static final long MICRODROID_MAX_LIFETIME_MINUTES = 20;
- private static final long MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES = 5;
+ private static final long MICRODROID_DEFAULT_ADB_CONNECT_TIMEOUT_MINUTES = 5;
private static final String EARLY_REBOOT = "Too early to call shutdown() or reboot()";
@@ -196,69 +196,81 @@
private String internalInstallPackage(
final File packageFile, final boolean reinstall, final List<String> extraArgs)
throws DeviceNotAvailableException {
- List<String> args = new ArrayList<>(extraArgs);
- if (packageFile.getName().endsWith(APEX_SUFFIX)) {
- args.add(APEX_ARG);
- }
- // use array to store response, so it can be returned to caller
- final String[] response = new String[1];
- DeviceAction installAction =
- new DeviceAction() {
- @Override
- public boolean run() throws InstallException {
- try {
- InstallReceiver receiver = createInstallReceiver();
- getIDevice()
- .installPackage(
- packageFile.getAbsolutePath(),
- reinstall,
- receiver,
- INSTALL_TIMEOUT_MINUTES,
- INSTALL_TIMEOUT_TO_OUTPUT_MINUTES,
- TimeUnit.MINUTES,
- args.toArray(new String[] {}));
- if (receiver.isSuccessfullyCompleted()) {
- response[0] = null;
- } else if (receiver.getErrorMessage() == null) {
- response[0] =
- String.format(
- "Installation of %s timed out",
- packageFile.getAbsolutePath());
- } else {
- response[0] = receiver.getErrorMessage();
- if (response[0].contains("cmd: Failure calling service package")) {
- String message =
+ long startTime = System.currentTimeMillis();
+ try {
+ List<String> args = new ArrayList<>(extraArgs);
+ if (packageFile.getName().endsWith(APEX_SUFFIX)) {
+ args.add(APEX_ARG);
+ }
+ // use array to store response, so it can be returned to caller
+ final String[] response = new String[1];
+ DeviceAction installAction =
+ new DeviceAction() {
+ @Override
+ public boolean run() throws InstallException {
+ try {
+ InstallReceiver receiver = createInstallReceiver();
+ getIDevice()
+ .installPackage(
+ packageFile.getAbsolutePath(),
+ reinstall,
+ receiver,
+ INSTALL_TIMEOUT_MINUTES,
+ INSTALL_TIMEOUT_TO_OUTPUT_MINUTES,
+ TimeUnit.MINUTES,
+ args.toArray(new String[] {}));
+ if (receiver.isSuccessfullyCompleted()) {
+ response[0] = null;
+ } else if (receiver.getErrorMessage() == null) {
+ response[0] =
String.format(
- "Failed to install '%s'. Device might have"
- + " crashed, it returned: %s",
- packageFile.getName(), response[0]);
- throw new DeviceRuntimeException(
- message, DeviceErrorIdentifier.DEVICE_CRASHED);
+ "Installation of %s timed out",
+ packageFile.getAbsolutePath());
+ } else {
+ response[0] = receiver.getErrorMessage();
+ if (response[0].contains(
+ "cmd: Failure calling service package")) {
+ String message =
+ String.format(
+ "Failed to install '%s'. Device might have"
+ + " crashed, it returned: %s",
+ packageFile.getName(), response[0]);
+ throw new DeviceRuntimeException(
+ message, DeviceErrorIdentifier.DEVICE_CRASHED);
+ }
}
+ } catch (InstallException e) {
+ String message = e.getMessage();
+ if (message == null) {
+ message =
+ String.format(
+ "InstallException during package installation. "
+ + "cause: %s",
+ StreamUtil.getStackTrace(e));
+ }
+ response[0] = message;
}
- } catch (InstallException e) {
- String message = e.getMessage();
- if (message == null) {
- message =
- String.format(
- "InstallException during package installation. "
- + "cause: %s",
- StreamUtil.getStackTrace(e));
- }
- response[0] = message;
+ return response[0] == null;
}
- return response[0] == null;
- }
- };
- CLog.v(
- "Installing package file %s with args %s on %s",
- packageFile.getAbsolutePath(), extraArgs.toString(), getSerialNumber());
- performDeviceAction(String.format("install %s", packageFile.getAbsolutePath()),
- installAction, MAX_RETRY_ATTEMPTS);
- List<File> packageFiles = new ArrayList<>();
- packageFiles.add(packageFile);
- allowLegacyStorageForApps(packageFiles);
- return response[0];
+ };
+ CLog.v(
+ "Installing package file %s with args %s on %s",
+ packageFile.getAbsolutePath(), extraArgs.toString(), getSerialNumber());
+ performDeviceAction(
+ String.format("install %s", packageFile.getAbsolutePath()),
+ installAction,
+ MAX_RETRY_ATTEMPTS);
+ List<File> packageFiles = new ArrayList<>();
+ packageFiles.add(packageFile);
+ allowLegacyStorageForApps(packageFiles);
+ return response[0];
+ } finally {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.PACKAGE_INSTALL_COUNT, 1);
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.PACKAGE_INSTALL_TIME,
+ System.currentTimeMillis() - startTime);
+ }
}
/**
@@ -301,71 +313,83 @@
public String installPackage(final File packageFile, final File certFile,
final boolean reinstall, final String... extraArgs) throws DeviceNotAvailableException {
- // use array to store response, so it can be returned to caller
- final String[] response = new String[1];
- DeviceAction installAction =
- new DeviceAction() {
- @Override
- public boolean run()
- throws InstallException, SyncException, IOException, TimeoutException,
- AdbCommandRejectedException {
- // TODO: create a getIDevice().installPackage(File, File...) method when the
- // dist cert functionality is ready to be open sourced
- String remotePackagePath =
- getIDevice().syncPackageToDevice(packageFile.getAbsolutePath());
- String remoteCertPath =
- getIDevice().syncPackageToDevice(certFile.getAbsolutePath());
- // trick installRemotePackage into issuing a 'pm install <apk> <cert>'
- // command, by adding apk path to extraArgs, and using cert as the
- // 'apk file'.
- String[] newExtraArgs = new String[extraArgs.length + 1];
- System.arraycopy(extraArgs, 0, newExtraArgs, 0, extraArgs.length);
- newExtraArgs[newExtraArgs.length - 1] =
- String.format("\"%s\"", remotePackagePath);
- try {
- InstallReceiver receiver = createInstallReceiver();
- getIDevice()
- .installRemotePackage(
- remoteCertPath,
- reinstall,
- receiver,
- INSTALL_TIMEOUT_MINUTES,
- INSTALL_TIMEOUT_TO_OUTPUT_MINUTES,
- TimeUnit.MINUTES,
- newExtraArgs);
- if (receiver.isSuccessfullyCompleted()) {
- response[0] = null;
- } else if (receiver.getErrorMessage() == null) {
- response[0] =
- String.format(
- "Installation of %s timed out.",
- packageFile.getAbsolutePath());
- } else {
- response[0] = receiver.getErrorMessage();
+ long startTime = System.currentTimeMillis();
+ try {
+ // use array to store response, so it can be returned to caller
+ final String[] response = new String[1];
+ DeviceAction installAction =
+ new DeviceAction() {
+ @Override
+ public boolean run()
+ throws InstallException, SyncException, IOException,
+ TimeoutException, AdbCommandRejectedException {
+ // TODO: create a getIDevice().installPackage(File, File...) method when
+ // the
+ // dist cert functionality is ready to be open sourced
+ String remotePackagePath =
+ getIDevice().syncPackageToDevice(packageFile.getAbsolutePath());
+ String remoteCertPath =
+ getIDevice().syncPackageToDevice(certFile.getAbsolutePath());
+ // trick installRemotePackage into issuing a 'pm install <apk> <cert>'
+ // command, by adding apk path to extraArgs, and using cert as the
+ // 'apk file'.
+ String[] newExtraArgs = new String[extraArgs.length + 1];
+ System.arraycopy(extraArgs, 0, newExtraArgs, 0, extraArgs.length);
+ newExtraArgs[newExtraArgs.length - 1] =
+ String.format("\"%s\"", remotePackagePath);
+ try {
+ InstallReceiver receiver = createInstallReceiver();
+ getIDevice()
+ .installRemotePackage(
+ remoteCertPath,
+ reinstall,
+ receiver,
+ INSTALL_TIMEOUT_MINUTES,
+ INSTALL_TIMEOUT_TO_OUTPUT_MINUTES,
+ TimeUnit.MINUTES,
+ newExtraArgs);
+ if (receiver.isSuccessfullyCompleted()) {
+ response[0] = null;
+ } else if (receiver.getErrorMessage() == null) {
+ response[0] =
+ String.format(
+ "Installation of %s timed out.",
+ packageFile.getAbsolutePath());
+ } else {
+ response[0] = receiver.getErrorMessage();
+ }
+ } catch (InstallException e) {
+ String message = e.getMessage();
+ if (message == null) {
+ message =
+ String.format(
+ "InstallException during package installation. "
+ + "cause: %s",
+ StreamUtil.getStackTrace(e));
+ }
+ response[0] = message;
+ } finally {
+ getIDevice().removeRemotePackage(remotePackagePath);
+ getIDevice().removeRemotePackage(remoteCertPath);
}
- } catch (InstallException e) {
- String message = e.getMessage();
- if (message == null) {
- message =
- String.format(
- "InstallException during package installation. "
- + "cause: %s",
- StreamUtil.getStackTrace(e));
- }
- response[0] = message;
- } finally {
- getIDevice().removeRemotePackage(remotePackagePath);
- getIDevice().removeRemotePackage(remoteCertPath);
+ return true;
}
- return true;
- }
- };
- performDeviceAction(String.format("install %s", packageFile.getAbsolutePath()),
- installAction, MAX_RETRY_ATTEMPTS);
- List<File> packageFiles = new ArrayList<>();
- packageFiles.add(packageFile);
- allowLegacyStorageForApps(packageFiles);
- return response[0];
+ };
+ performDeviceAction(
+ String.format("install %s", packageFile.getAbsolutePath()),
+ installAction,
+ MAX_RETRY_ATTEMPTS);
+ List<File> packageFiles = new ArrayList<>();
+ packageFiles.add(packageFile);
+ allowLegacyStorageForApps(packageFiles);
+ return response[0];
+ } finally {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.PACKAGE_INSTALL_COUNT, 1);
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.PACKAGE_INSTALL_TIME,
+ System.currentTimeMillis() - startTime);
+ }
}
/**
@@ -2337,6 +2361,7 @@
} catch (IOException e) {
throw new DeviceRuntimeException(
"Unable to get an unused port for Microdroid.",
+ e,
DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
@@ -2354,6 +2379,11 @@
"mkdir -p " + TEST_ROOT + " has failed: " + result,
DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
}
+ for (File localFile : builder.mBootFiles.keySet()) {
+ String remoteFileName = builder.mBootFiles.get(localFile);
+ pushFile(localFile, TEST_ROOT + remoteFileName);
+ }
+
// Push the apk file to the test directory
if (builder.mApkFile != null) {
pushFile(builder.mApkFile, TEST_ROOT + builder.mApkFile.getName());
@@ -2374,10 +2404,15 @@
final String logPath = TEST_ROOT + "log.txt";
final String debugFlag =
Strings.isNullOrEmpty(builder.mDebugLevel) ? "" : "--debug " + builder.mDebugLevel;
+ final String cpuFlag = builder.mNumCpus == null ? "" : "--cpus " + builder.mNumCpus;
final String cpuAffinityFlag =
Strings.isNullOrEmpty(builder.mCpuAffinity)
? ""
: "--cpu-affinity " + builder.mCpuAffinity;
+ final String cpuTopologyFlag =
+ Strings.isNullOrEmpty(builder.mCpuTopology)
+ ? ""
+ : "--cpu-topology " + builder.mCpuTopology;
List<String> args =
new ArrayList<>(
@@ -2392,8 +2427,9 @@
"--log " + logPath,
"--mem " + builder.mMemoryMib,
debugFlag,
- "--cpus " + builder.mNumCpus,
+ cpuFlag,
cpuAffinityFlag,
+ cpuTopologyFlag,
builder.mApkPath,
outApkIdsigPath,
instanceImg,
@@ -2431,7 +2467,9 @@
}
} catch (IOException ex) {
throw new DeviceRuntimeException(
- "IOException trying to start a VM", DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
+ "IOException trying to start a VM",
+ ex,
+ DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
}
// Redirect log.txt to logd using logwrapper
@@ -2445,7 +2483,7 @@
forwardFileToLog(logPath, "MicrodroidLog");
});
- adbConnectToMicrodroid(cid, microdroidSerial, vmAdbPort);
+ adbConnectToMicrodroid(cid, microdroidSerial, vmAdbPort, builder.mAdbConnectTimeoutMs);
TestDevice microdroid = (TestDevice) deviceManager.forceAllocateDevice(microdroidSerial);
if (microdroid == null) {
process.destroy();
@@ -2469,12 +2507,13 @@
* Establish an adb connection to microdroid by letting Android forward the connection to
* microdroid. Wait until the connection is established and microdroid is booted.
*/
- private void adbConnectToMicrodroid(String cid, String microdroidSerial, int vmAdbPort) {
+ private void adbConnectToMicrodroid(
+ String cid, String microdroidSerial, int vmAdbPort, long adbConnectTimeoutMs) {
MicrodroidHelper microdroidHelper = new MicrodroidHelper();
IDeviceManager deviceManager = GlobalConfiguration.getDeviceManagerInstance();
long start = System.currentTimeMillis();
- long timeoutMillis = MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000;
+ long timeoutMillis = adbConnectTimeoutMs;
long elapsed = 0;
final String serial = getSerialNumber();
@@ -2625,11 +2664,14 @@
private String mConfigPath;
private String mDebugLevel;
private int mMemoryMib;
- private int mNumCpus;
+ private Integer mNumCpus;
private String mCpuAffinity;
+ private String mCpuTopology;
private List<String> mExtraIdsigPaths;
private boolean mProtectedVm;
private Map<String, String> mTestDeviceOptions;
+ private Map<File, String> mBootFiles;
+ private long mAdbConnectTimeoutMs;
/** Creates a builder for the given APK/apkPath and the payload config file in APK. */
private MicrodroidBuilder(File apkFile, String apkPath, @Nonnull String configPath) {
@@ -2638,11 +2680,13 @@
mConfigPath = configPath;
mDebugLevel = null;
mMemoryMib = 0;
- mNumCpus = 1;
+ mNumCpus = null;
mCpuAffinity = null;
mExtraIdsigPaths = new ArrayList<>();
mProtectedVm = false; // Vm is unprotected by default.
mTestDeviceOptions = new LinkedHashMap<>();
+ mBootFiles = new LinkedHashMap<>();
+ mAdbConnectTimeoutMs = MICRODROID_DEFAULT_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000;
}
/** Creates a Microdroid builder for the given APK and the payload config file in APK. */
@@ -2659,7 +2703,11 @@
return new MicrodroidBuilder(null, apkPath, configPath);
}
- /** Sets the debug level. Supported values: "none", "app_only", and "full". */
+ /**
+ * Sets the debug level.
+ *
+ * <p>Supported values: "none" and "full". Android T also supports "app_only".
+ */
public MicrodroidBuilder debugLevel(String debugLevel) {
mDebugLevel = debugLevel;
return this;
@@ -2674,7 +2722,11 @@
return this;
}
- /** Sets the number of vCPUs in the VM. Defaults to 1. */
+ /**
+ * Sets the number of vCPUs in the VM. Defaults to 1.
+ *
+ * <p>Only supported in Android T.
+ */
public MicrodroidBuilder numCpus(int num) {
mNumCpus = num;
return this;
@@ -2685,12 +2737,20 @@
* or CPU ranges to run vCPUs on. e.g. "0,1-3,5" to choose host CPUs 0, 1, 2, 3, and 5. Or
* this can be a colon-separated list of assignments of vCPU to host CPU assignments. e.g.
* "0=0:1=1:2=2" to map vCPU 0 to host CPU 0, and so on.
+ *
+ * <p>Only supported in Android T.
*/
public MicrodroidBuilder cpuAffinity(String affinity) {
mCpuAffinity = affinity;
return this;
}
+ /** Sets the CPU topology configuration. Supported values: "one_cpu" and "match_host". */
+ public MicrodroidBuilder cpuTopology(String cpuTopology) {
+ mCpuTopology = cpuTopology;
+ return this;
+ }
+
/** Sets whether the VM will be protected or not. */
public MicrodroidBuilder protectedVm(boolean isProtectedVm) {
mProtectedVm = isProtectedVm;
@@ -2717,17 +2777,57 @@
return this;
}
+ /**
+ * Adds a file for booting to be pushed to {@link #TEST_ROOT}.
+ *
+ * <p>Use this method if an file is required for booting microdroid. Otherwise use {@link
+ * TestDevice#pushFile}.
+ *
+ * @param localFile The local file on the host
+ * @param remoteFileName The remote file name on the device
+ * @param the microdroid builder.
+ */
+ public MicrodroidBuilder addBootFile(File localFile, String remoteFileName) {
+ mBootFiles.put(localFile, remoteFileName);
+ return this;
+ }
+
+ /**
+ * Sets the timeout for adb connect to microdroid TestDevice in millis.
+ *
+ * @param timeoutMs The timeout in millis
+ */
+ public MicrodroidBuilder setAdbConnectTimeoutMs(long timeoutMs) {
+ mAdbConnectTimeoutMs = timeoutMs;
+ return this;
+ }
+
/** Starts a Micrdroid TestDevice on the given TestDevice. */
public ITestDevice build(@Nonnull TestDevice device) throws DeviceNotAvailableException {
- if (mNumCpus < 1) {
- throw new IllegalArgumentException("Number of vCPUs can not be less than 1.");
+ if (mNumCpus != null) {
+ if (device.getApiLevel() != 33) {
+ throw new IllegalStateException(
+ "Setting number of CPUs only supported with API level 33");
+ }
+ if (mNumCpus < 1) {
+ throw new IllegalArgumentException("Number of vCPUs can not be less than 1.");
+ }
}
- if (mCpuAffinity != null
- && !Pattern.matches("[\\d]+(-[\\d]+)?(,[\\d]+(-[\\d]+)?)*", mCpuAffinity)
- && !Pattern.matches("[\\d]+=[\\d]+(:[\\d]+=[\\d]+)*", mCpuAffinity)) {
- throw new IllegalArgumentException(
- "CPU affinity [" + mCpuAffinity + "]" + " is invalid");
+ if (!Strings.isNullOrEmpty(mCpuTopology)) {
+ device.checkApiLevelAgainstNextRelease("vm-cpu-topology", 34);
+ }
+
+ if (mCpuAffinity != null) {
+ if (device.getApiLevel() != 33) {
+ throw new IllegalStateException(
+ "Setting CPU affinity only supported with API level 33");
+ }
+ if (!Pattern.matches("[\\d]+(-[\\d]+)?(,[\\d]+(-[\\d]+)?)*", mCpuAffinity)
+ && !Pattern.matches("[\\d]+=[\\d]+(:[\\d]+=[\\d]+)*", mCpuAffinity)) {
+ throw new IllegalArgumentException(
+ "CPU affinity [" + mCpuAffinity + "]" + " is invalid");
+ }
}
return device.startMicrodroid(this);
diff --git a/src/com/android/tradefed/device/cloud/GceAvdInfo.java b/src/com/android/tradefed/device/cloud/GceAvdInfo.java
index 20cfe28..659c1bf 100644
--- a/src/com/android/tradefed/device/cloud/GceAvdInfo.java
+++ b/src/com/android/tradefed/device/cloud/GceAvdInfo.java
@@ -397,7 +397,9 @@
CLog.d("Parsing oxygen client output: %s", output);
Pattern pattern =
- Pattern.compile("session_id:\"(.*?)\".*?server_url:\"(.*?)\"", Pattern.DOTALL);
+ Pattern.compile(
+ "session_id:\"(.*?)\".*?server_url:\"(.*?)\".*?oxygen_version:\"(.*?)\"",
+ Pattern.DOTALL);
Matcher matcher = pattern.matcher(output);
List<GceAvdInfo> gceAvdInfos = new ArrayList<>();
@@ -405,6 +407,7 @@
while (matcher.find()) {
String sessionId = matcher.group(1);
String serverUrl = matcher.group(2);
+ String oxygenVersion = matcher.group(3);
gceAvdInfos.add(
new GceAvdInfo(
sessionId,
@@ -413,6 +416,8 @@
null,
null,
GceStatus.SUCCESS));
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.CF_OXYGEN_VERSION, oxygenVersion);
deviceOffset++;
}
if (gceAvdInfos.isEmpty()) {
diff --git a/src/com/android/tradefed/device/cloud/GceManager.java b/src/com/android/tradefed/device/cloud/GceManager.java
index 5ffe6a1..990d407 100644
--- a/src/com/android/tradefed/device/cloud/GceManager.java
+++ b/src/com/android/tradefed/device/cloud/GceManager.java
@@ -23,6 +23,7 @@
import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ByteArrayInputStreamSource;
@@ -318,11 +319,14 @@
InfraErrorIdentifier.OXYGEN_DEVICE_LAUNCHER_FAILURE);
}
}
- InvocationMetricLogger.addInvocationMetrics(
- InvocationMetricKey.CF_OXYGEN_SESSION_ID, oxygenDeviceInfo.instanceName());
- InvocationMetricLogger.addInvocationMetrics(
- InvocationMetricKey.CF_OXYGEN_SERVER_URL,
- oxygenDeviceInfo.hostAndPort().getHost());
+ // Lease may timeout and skip logging metrics if host is not set.
+ if (oxygenDeviceInfo.hostAndPort() != null) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.CF_OXYGEN_SESSION_ID, oxygenDeviceInfo.instanceName());
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.CF_OXYGEN_SERVER_URL,
+ oxygenDeviceInfo.hostAndPort().getHost());
+ }
return oxygenDeviceInfo;
} finally {
InvocationMetricLogger.addInvocationMetrics(
@@ -953,6 +957,30 @@
remoteFile =
RemoteFileUtil.fetchRemoteDir(
gceAvd, options, runUtil, REMOTE_FILE_OP_TIMEOUT, remoteFilePath);
+
+ // Search log files for known failures for devices hosted by Oxygen
+ if (options.useOxygenProxy()) {
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope("avd:collectErrorSignature")) {
+ List<String> signatures = OxygenUtil.collectErrorSignatures(remoteFile);
+ if (signatures.size() > 0) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.DEVICE_ERROR_SIGNATURES,
+ String.join(",", signatures));
+ }
+ }
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope("avd:collectDeviceLaunchMetrics")) {
+ long[] launchMetrics = OxygenUtil.collectDeviceLaunchMetrics(remoteFile);
+ if (launchMetrics[0] > 0) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.CF_FETCH_ARTIFACT_TIME, launchMetrics[0]);
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.CF_LAUNCH_CVD_TIME, launchMetrics[1]);
+ }
+ }
+ }
+
// Default files under a directory to be CUTTLEFISH_LOG to avoid compression.
type = LogDataType.CUTTLEFISH_LOG;
if (remoteFile != null) {
diff --git a/src/com/android/tradefed/device/cloud/OxygenUtil.java b/src/com/android/tradefed/device/cloud/OxygenUtil.java
index 66e993f..9414ae7 100644
--- a/src/com/android/tradefed/device/cloud/OxygenUtil.java
+++ b/src/com/android/tradefed/device/cloud/OxygenUtil.java
@@ -23,10 +23,15 @@
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.GCSFileDownloader;
+import com.android.tradefed.util.Pair;
import java.io.File;
import java.util.AbstractMap;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import java.util.Map;
+import java.util.Scanner;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -54,6 +59,28 @@
LogDataType.TOMBSTONEZ))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+ private static final Map<Pattern, Pair<Pattern, String>>
+ REMOTE_LOG_NAME_PATTERN_TO_ERROR_SIGNATURE_MAP =
+ Stream.of(
+ new AbstractMap.SimpleEntry<>(
+ Pattern.compile("^launcher\\.log.*"),
+ Pair.create(
+ Pattern.compile(".*Address already in use.*"),
+ "launch_cvd_port_collision")),
+ new AbstractMap.SimpleEntry<>(
+ Pattern.compile("^launcher\\.log.*"),
+ Pair.create(
+ Pattern.compile(".*vcpu hw run failure: 0x7.*"),
+ "crosvm_vcpu_hw_run_failure_7")),
+ new AbstractMap.SimpleEntry<>(
+ Pattern.compile("^launcher\\.log.*"),
+ Pair.create(
+ Pattern.compile(
+ ".*Unable to connect to vsock"
+ + " server.*"),
+ "unable_to_connect_to_vsock_server")))
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
+
/** Default constructor of OxygenUtil */
public OxygenUtil() {
mDownloader = new GCSFileDownloader(true);
@@ -143,4 +170,125 @@
logFileName));
return LogDataType.UNKNOWN;
}
+
+ /**
+ * Collect error signatures from logs.
+ *
+ * @param logDir directory of logs pulled from remote host.
+ */
+ public static List<String> collectErrorSignatures(File logDir) {
+ CLog.d("Collect error signature from logs under: %s.", logDir);
+ List<String> signatures = new ArrayList<>();
+ try {
+ Set<String> files = FileUtil.findFiles(logDir, ".*");
+ for (String f : files) {
+ File file = new File(f);
+ if (file.isDirectory()) {
+ continue;
+ }
+ String fileName = file.getName();
+ List<Pair<Pattern, String>> pairs = new ArrayList<>();
+ for (Map.Entry<Pattern, Pair<Pattern, String>> entry :
+ REMOTE_LOG_NAME_PATTERN_TO_ERROR_SIGNATURE_MAP.entrySet()) {
+ Matcher matcher = entry.getKey().matcher(fileName);
+ if (matcher.find()) {
+ pairs.add(entry.getValue());
+ }
+ }
+ if (pairs.size() == 0) {
+ continue;
+ }
+ try (Scanner scanner = new Scanner(file)) {
+ List<Pair<Pattern, String>> pairsToRemove = new ArrayList<>();
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ for (Pair<Pattern, String> pair : pairs) {
+ if (pair.first.matcher(line).find()) {
+ pairsToRemove.add(pair);
+ signatures.add(pair.second);
+ }
+ }
+ if (pairsToRemove.size() > 0) {
+ pairs.removeAll(pairsToRemove);
+ if (pairs.size() == 0) {
+ break;
+ }
+ }
+ }
+ }
+ }
+ } catch (Exception e) {
+ CLog.e("Failed to collect error signature.");
+ CLog.e(e);
+ }
+ Collections.sort(signatures);
+ return signatures;
+ }
+
+ /**
+ * Collect device launcher metrics from vdl_stdout.
+ *
+ * @param logDir directory of logs pulled from remote host.
+ */
+ public static long[] collectDeviceLaunchMetrics(File logDir) {
+ CLog.d("Collect device launcher metrics from logs under: %s.", logDir);
+ long[] metrics = {-1, -1};
+ try {
+ Set<String> files = FileUtil.findFiles(logDir, "^vdl_stdout\\.txt.*");
+ if (files.size() == 0) {
+ CLog.d("There is no vdl_stdout.txt found.");
+ return metrics;
+ }
+ File vdlStdout = new File(files.iterator().next());
+ double cuttlefishCommon = 0;
+ double launchDevice = 0;
+ double mainstart = 0;
+ Pattern cuttlefishCommonPatteren =
+ Pattern.compile(".*\\|\\s*(\\d+\\.\\d+)\\s*\\|\\sCuttlefishCommon");
+ Pattern launchDevicePatteren =
+ Pattern.compile(".*\\|\\s*(\\d+\\.\\d+)\\s*\\|\\sLaunchDevice");
+ Pattern mainstartPatteren =
+ Pattern.compile(".*\\|\\s*(\\d+\\.\\d+)\\s*\\|\\sCuttlefishLauncherMainstart");
+ try (Scanner scanner = new Scanner(vdlStdout)) {
+ boolean metricsPending = false;
+ while (scanner.hasNextLine()) {
+ String line = scanner.nextLine();
+ if (!metricsPending) {
+ if (line.indexOf("launch_cvd exited") != -1) {
+ metricsPending = true;
+ } else {
+ continue;
+ }
+ }
+ Matcher matcher;
+ if (cuttlefishCommon == 0) {
+ matcher = cuttlefishCommonPatteren.matcher(line);
+ if (matcher.find()) {
+ cuttlefishCommon = Double.parseDouble(matcher.group(1));
+ }
+ }
+ if (launchDevice == 0) {
+ matcher = launchDevicePatteren.matcher(line);
+ if (matcher.find()) {
+ launchDevice = Double.parseDouble(matcher.group(1));
+ }
+ }
+ if (mainstart == 0) {
+ matcher = mainstartPatteren.matcher(line);
+ if (matcher.find()) {
+ mainstart = Double.parseDouble(matcher.group(1));
+ }
+ }
+ }
+ }
+ if (mainstart > 0) {
+ metrics[0] = (long) ((mainstart - launchDevice - cuttlefishCommon) * 1000);
+ metrics[1] = (long) (launchDevice * 1000);
+ }
+ } catch (Exception e) {
+ CLog.e("Failed to parse device launch time from vdl_stdout.txt.");
+ CLog.e(e);
+ }
+ return metrics;
+ }
}
diff --git a/src/com/android/tradefed/device/internal/DeviceResetFeature.java b/src/com/android/tradefed/device/internal/DeviceResetFeature.java
index f84d79a..3b42e2d 100644
--- a/src/com/android/tradefed/device/internal/DeviceResetFeature.java
+++ b/src/com/android/tradefed/device/internal/DeviceResetFeature.java
@@ -20,6 +20,7 @@
import com.android.tradefed.config.IDeviceConfiguration;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.RemoteAndroidDevice;
+import com.android.tradefed.device.cloud.GceAvdInfo;
import com.android.tradefed.device.cloud.NestedRemoteDevice;
import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
import com.android.tradefed.invoker.TestInformation;
@@ -93,14 +94,13 @@
try {
mTestInformation.setActiveDeviceIndex(index);
if (mTestInformation.getDevice() instanceof RemoteAndroidVirtualDevice) {
- Integer offset =
- ((RemoteAndroidVirtualDevice) mTestInformation.getDevice())
- .getAvdInfo()
- .getDeviceOffset();
- String user =
- ((RemoteAndroidVirtualDevice) mTestInformation.getDevice())
- .getAvdInfo()
- .getInstanceUser();
+ GceAvdInfo info =
+ ((RemoteAndroidVirtualDevice) mTestInformation.getDevice()).getAvdInfo();
+ if (info == null) {
+ throw new RuntimeException("GceAvdInfo was null. skipping");
+ }
+ Integer offset = info.getDeviceOffset();
+ String user = info.getInstanceUser();
CommandResult powerwashResult =
((RemoteAndroidVirtualDevice) mTestInformation.getDevice())
.powerwashGce(user, offset);
diff --git a/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java b/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
index 21c99e7..f8c1c7a 100644
--- a/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
+++ b/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
@@ -99,7 +99,7 @@
private boolean mDeviceNoAvailable = false;
@Override
- public ITestInvocationListener init(
+ public final ITestInvocationListener init(
IInvocationContext context, ITestInvocationListener listener)
throws DeviceNotAvailableException {
mContext = context;
@@ -110,9 +110,26 @@
}
mWasInitDone = true;
mDeviceNoAvailable = false;
+ long start = System.currentTimeMillis();
+ try {
+ extraInit(context, listener);
+ } finally {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start);
+ }
return this;
}
+ /**
+ * @param context
+ * @param listener
+ * @throws DeviceNotAvailableException
+ */
+ public void extraInit(IInvocationContext context, ITestInvocationListener listener)
+ throws DeviceNotAvailableException {
+ // Empty by default
+ }
+
@Override
public final List<ITestDevice> getDevices() {
return mContext.getDevices();
@@ -257,6 +274,7 @@
@Override
public final void testModuleStarted(IInvocationContext moduleContext) {
+ long start = System.currentTimeMillis();
try (CloseableTraceScope ignored =
new CloseableTraceScope("module_start_" + this.getClass().getSimpleName())) {
onTestModuleStarted();
@@ -266,12 +284,15 @@
} catch (Throwable t) {
CLog.e(t);
} finally {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start);
mForwarder.testModuleStarted(moduleContext);
}
}
@Override
public final void testModuleEnded() {
+ long start = System.currentTimeMillis();
try (CloseableTraceScope ignored =
new CloseableTraceScope("module_end_" + this.getClass().getSimpleName())) {
onTestModuleEnded();
@@ -280,6 +301,8 @@
} catch (Throwable t) {
CLog.e(t);
} finally {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start);
mForwarder.testModuleEnded();
}
}
diff --git a/src/com/android/tradefed/device/metric/ClangCodeCoverageCollector.java b/src/com/android/tradefed/device/metric/ClangCodeCoverageCollector.java
index be7c87b..47c9a33 100644
--- a/src/com/android/tradefed/device/metric/ClangCodeCoverageCollector.java
+++ b/src/com/android/tradefed/device/metric/ClangCodeCoverageCollector.java
@@ -34,6 +34,7 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.error.InfraErrorIdentifier;
+import com.android.tradefed.testtype.coverage.CoverageOptions;
import com.android.tradefed.util.AdbRootElevator;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
@@ -70,9 +71,6 @@
private static final String NATIVE_COVERAGE_DEVICE_PATH = "/data/misc/trace";
- // Timeout for pulling coverage measurements from the device, in minutes.
- private static final long TIMEOUT = 20;
-
// Maximum number of profile files before writing the list to a file. Beyond this value,
// llvm-profdata will use the -f option to read the list from a file to prevent exceeding
// the command line length limit.
@@ -91,17 +89,19 @@
private IConfiguration mConfiguration;
private IRunUtil mRunUtil = RunUtil.getDefault();
+ // Timeout for pulling coverage measurements from the device, in milliseconds.
+ private long mTimeoutMilli = 20 * 60 * 1000;
private File mLlvmProfileTool;
private NativeCodeCoverageFlusher mFlusher;
@Override
- public ITestInvocationListener init(
- IInvocationContext context, ITestInvocationListener listener)
+ public void extraInit(IInvocationContext context, ITestInvocationListener listener)
throws DeviceNotAvailableException {
- super.init(context, listener);
+ super.extraInit(context, listener);
verifyNotNull(mConfiguration);
+ setCoverageOptions(mConfiguration.getCoverageOptions());
if (isClangCoverageEnabled()
&& mConfiguration.getCoverageOptions().shouldResetCoverageBeforeTest()) {
@@ -112,8 +112,6 @@
}
}
}
-
- return this;
}
@Override
@@ -180,8 +178,8 @@
ZIP_CLANG_FILES_COMMAND, // Command
null, // File pipe as input
out, // OutputStream to write to
- TIMEOUT, // Timeout in minutes
- TimeUnit.MINUTES, // Timeout units
+ mTimeoutMilli, // Timeout in milliseconds
+ TimeUnit.MILLISECONDS, // Timeout units
1); // Retry count
}
@@ -318,4 +316,8 @@
FileUtil.deleteFile(profileToolZip);
}
}
+
+ private void setCoverageOptions(CoverageOptions coverageOptions) {
+ mTimeoutMilli = coverageOptions.getPullTimeout();
+ }
}
diff --git a/src/com/android/tradefed/device/metric/DeviceTraceCollector.java b/src/com/android/tradefed/device/metric/DeviceTraceCollector.java
index e3f5833..f42a6d0 100644
--- a/src/com/android/tradefed/device/metric/DeviceTraceCollector.java
+++ b/src/com/android/tradefed/device/metric/DeviceTraceCollector.java
@@ -40,10 +40,9 @@
private String mInstrumentationPkgName;
@Override
- public ITestInvocationListener init(
- IInvocationContext context, ITestInvocationListener listener)
+ public void extraInit(IInvocationContext context, ITestInvocationListener listener)
throws DeviceNotAvailableException {
- super.init(context, listener);
+ super.extraInit(context, listener);
for (ITestDevice device : getRealDevices()) {
try {
Map<String, String> extraConfigs = new LinkedHashMap<>();
@@ -58,7 +57,6 @@
device.getSerialNumber(), e.getMessage());
}
}
- return this;
}
@Override
diff --git a/src/com/android/tradefed/device/metric/EmulatorMemoryCpuCapturer.java b/src/com/android/tradefed/device/metric/EmulatorMemoryCpuCapturer.java
index c87cd5e..e5d43e7 100644
--- a/src/com/android/tradefed/device/metric/EmulatorMemoryCpuCapturer.java
+++ b/src/com/android/tradefed/device/metric/EmulatorMemoryCpuCapturer.java
@@ -22,6 +22,7 @@
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.google.common.annotations.VisibleForTesting;
@@ -77,8 +78,7 @@
public float getCpuUsage() {
CommandResult result =
- RunUtil.getDefault()
- .runTimedCmd(20000L, "ps", "-o", "%cpu", "-p", Long.toString(mPid));
+ getRunUtil().runTimedCmd(20000L, "ps", "-o", "%cpu", "-p", Long.toString(mPid));
if (result.getStatus() == CommandStatus.SUCCESS) {
return parseCpuUsage(result.getStdout());
} else {
@@ -87,6 +87,10 @@
}
}
+ protected IRunUtil getRunUtil() {
+ return RunUtil.getDefault();
+ }
+
/**
* Parse the cpu usage string.
*
diff --git a/src/com/android/tradefed/device/metric/GcovCodeCoverageCollector.java b/src/com/android/tradefed/device/metric/GcovCodeCoverageCollector.java
index 4a73e23..6ad0d53 100644
--- a/src/com/android/tradefed/device/metric/GcovCodeCoverageCollector.java
+++ b/src/com/android/tradefed/device/metric/GcovCodeCoverageCollector.java
@@ -72,10 +72,9 @@
private IRunUtil mRunUtil = RunUtil.getDefault();
@Override
- public ITestInvocationListener init(
- IInvocationContext context, ITestInvocationListener listener)
+ public void extraInit(IInvocationContext context, ITestInvocationListener listener)
throws DeviceNotAvailableException {
- super.init(context, listener);
+ super.extraInit(context, listener);
if (isGcovCoverageEnabled()) {
for (ITestDevice device : getRealDevices()) {
@@ -85,8 +84,6 @@
}
}
}
-
- return this;
}
@VisibleForTesting
diff --git a/src/com/android/tradefed/device/metric/GcovKernelCodeCoverageCollector.java b/src/com/android/tradefed/device/metric/GcovKernelCodeCoverageCollector.java
index 0a03071..741ace1 100644
--- a/src/com/android/tradefed/device/metric/GcovKernelCodeCoverageCollector.java
+++ b/src/com/android/tradefed/device/metric/GcovKernelCodeCoverageCollector.java
@@ -40,7 +40,6 @@
import java.io.File;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
/**
* A {@link com.android.tradefed.device.metric.BaseDeviceMetricCollector} that will pull gcov kernel
@@ -59,6 +58,9 @@
public static final String RESET_GCOV_COUNTS_COMMAND =
String.format("echo 1 > %s/gcov/reset", DEBUGFS_PATH);
public static final String MAKE_TEMP_DIR_COMMAND = "mktemp -d -p /data/local/tmp/";
+ public static final String MAKE_GCDA_TEMP_DIR_COMMAND_FMT = "mkdir -p %s";
+ public static final String COPY_GCOV_DATA_COMMAND_FMT = "cp -rf %s/* %s";
+ public static final String TAR_GCOV_DATA_COMMAND_FMT = "tar -czf %s -C %s %s";
private IConfiguration mConfiguration;
private boolean mTestRunStartFail;
@@ -185,9 +187,17 @@
}
/**
- * Gather overage data files off of the device. This logic is taken directly from the
- * `gather_on_test.sh` script detailed here:
+ * Gather overage data files off of the device. This logic is was originally taken directly from
+ * the `gather_on_test.sh` script detailed here:
* https://www.kernel.org/doc/html/v4.15/dev-tools/gcov.html#appendix-b-gather-on-test-sh
+ * However, in practice the `find` + `cat` approach ended up taking a lot of time. The reasoning
+ * given for this approach was because of issues with the `seq_file` interface. It turns out
+ * this issue no longer applies to the `cp` command (it still applies to the `tar`). Discussion
+ * on this can b e found here:
+ * https://github.com/linux-test-project/lcov/discussions/199#discussion-4895422
+ *
+ * <p>TODO: Revert this summary back to the original text, once upstream patch lands that
+ * updates `gather_on_test.sh` `cp` instead of `find` + `cat`.
*/
private void collectGcovDebugfsCoverage(INativeDevice device, String name)
throws DeviceNotAvailableException {
@@ -208,31 +218,40 @@
DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
}
String tempDir = result.getStdout().strip();
+
+ String gcda = "/d/gcov";
+ String gcdaTempDir = tempDir + gcda;
+ String makeGcdaTempDirCommand =
+ String.format(MAKE_GCDA_TEMP_DIR_COMMAND_FMT, gcdaTempDir);
+ result = device.executeShellV2Command(makeGcdaTempDirCommand);
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ CLog.e("Failed to create gcda temp directory %s. %s", gcdaTempDir, result);
+ throw new DeviceRuntimeException(
+ "'" + makeGcdaTempDirCommand + "' has failed: " + result,
+ DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
+ }
+
String tarName = String.format("%s.tar.gz", name);
String tarFullPath = String.format("%s/%s", tempDir, tarName);
- String gcda = "/d/gcov";
- String gatherCommand =
- String.format(
- "find %s -type d -exec sh -c 'mkdir -p %s/$0' {} \\;; find %s -name"
- + " '*.gcda' -exec sh -c 'cat < $0 > '%s'/$0' {} \\;; find %s -name"
- + " '*.gcno' -exec sh -c 'cp -d $0 '%s'/$0' {} \\;; tar -czf %s -C"
- + " %s %s",
- gcda,
- tempDir,
- gcda,
- tempDir,
- gcda,
- tempDir,
- tarFullPath,
- tempDir,
- gcda.substring(1));
-
- result = device.executeShellV2Command(gatherCommand, 10, TimeUnit.MINUTES);
+ String copyGcovDataCommand =
+ String.format(COPY_GCOV_DATA_COMMAND_FMT, gcda, gcdaTempDir);
+ result = device.executeShellV2Command(copyGcovDataCommand);
if (result.getStatus() != CommandStatus.SUCCESS) {
CLog.e("Failed to collect coverage files for %s. %s", name, result);
throw new DeviceRuntimeException(
- "'" + gatherCommand + "' has failed: " + result,
+ "'" + copyGcovDataCommand + "' has failed: " + result,
+ DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
+ }
+
+ String tarCommand =
+ String.format(
+ TAR_GCOV_DATA_COMMAND_FMT, tarFullPath, tempDir, gcda.substring(1));
+ result = device.executeShellV2Command(tarCommand);
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ CLog.e("Failed to tar collected files for %s. %s", name, result);
+ throw new DeviceRuntimeException(
+ "'" + tarCommand + "' has failed: " + result,
DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
}
diff --git a/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java b/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
index 1469016..c177036 100644
--- a/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
+++ b/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
@@ -70,21 +70,20 @@
public static final String COMPRESS_COVERAGE_FILES =
String.format("%s | tar -czf - -T - 2>/dev/null", FIND_COVERAGE_FILES);
- // Timeout for pulling coverage files from the device, in minutes.
- private static final long TIMEOUT_MINUTES = 20;
-
private ExecFileLoader mExecFileLoader;
private JavaCodeCoverageFlusher mFlusher;
private IConfiguration mConfiguration;
+ // Timeout for pulling coverage files from the device, in milliseconds.
+ private long mTimeoutMilli = 20 * 60 * 1000;
@Override
- public ITestInvocationListener init(
- IInvocationContext context, ITestInvocationListener listener)
+ public void extraInit(IInvocationContext context, ITestInvocationListener listener)
throws DeviceNotAvailableException {
- super.init(context, listener);
+ super.extraInit(context, listener);
verifyNotNull(mConfiguration);
+ setCoverageOptions(mConfiguration.getCoverageOptions());
if (isJavaCoverageEnabled()
&& mConfiguration.getCoverageOptions().shouldResetCoverageBeforeTest()) {
@@ -94,8 +93,6 @@
}
}
}
-
- return this;
}
@Override
@@ -170,8 +167,8 @@
COMPRESS_COVERAGE_FILES,
null,
out,
- TIMEOUT_MINUTES,
- TimeUnit.MINUTES,
+ mTimeoutMilli,
+ TimeUnit.MILLISECONDS,
1);
if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
CLog.e(
@@ -293,4 +290,8 @@
private boolean shouldMergeCoverage() {
return mConfiguration != null && mConfiguration.getCoverageOptions().shouldMergeCoverage();
}
+
+ private void setCoverageOptions(CoverageOptions coverageOptions) {
+ mTimeoutMilli = coverageOptions.getPullTimeout();
+ }
}
diff --git a/src/com/android/tradefed/invoker/InvocationExecution.java b/src/com/android/tradefed/invoker/InvocationExecution.java
index c0bb474..606e59b 100644
--- a/src/com/android/tradefed/invoker/InvocationExecution.java
+++ b/src/com/android/tradefed/invoker/InvocationExecution.java
@@ -1128,7 +1128,8 @@
continue;
}
CLog.d("Using RetryLogSaverResultForwarder to forward results.");
- ModuleListener mainGranularRunListener = new ModuleListener(null);
+ ModuleListener mainGranularRunListener =
+ new ModuleListener(null, info.getContext());
RetryLogSaverResultForwarder runListener =
initializeListeners(config, listener, mainGranularRunListener);
mainGranularRunListener.setAttemptIsolation(
diff --git a/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecution.java b/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecution.java
index dd2e08e..49ab1b4 100644
--- a/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecution.java
+++ b/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecution.java
@@ -130,13 +130,29 @@
IInvocationContext context, IConfiguration config, ITestLogger logger)
throws DeviceNotAvailableException, TargetSetupError {
if (shouldRunDeviceSpecificSetup(config)) {
- if (getSandboxOptions(config).shouldParallelSetup()
- && getSandboxOptions(config).shouldUseNewFlagOrder()) {
+ boolean parallelSetup =
+ getSandboxOptions(config).shouldParallelSetup()
+ && getSandboxOptions(config).shouldUseNewFlagOrder();
+ if (parallelSetup) {
setupThread =
new SandboxSetupThread(mTestInfo, config, (ITestInvocationListener) logger);
setupThread.start();
}
- super.runDevicePreInvocationSetup(context, config, logger);
+ try {
+ super.runDevicePreInvocationSetup(context, config, logger);
+ } catch (DeviceNotAvailableException | TargetSetupError | RuntimeException e) {
+ if (parallelSetup) {
+ // Join and clean up since run won't be called.
+ try {
+ setupThread.join();
+ } catch (InterruptedException ie) {
+ // Ignore
+ CLog.e(e);
+ }
+ SandboxInvocationRunner.teardownSandbox(config);
+ }
+ throw e;
+ }
if (!getSandboxOptions(config).shouldUseNewFlagOrder()) {
String commandLine = config.getCommandLine();
for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) {
diff --git a/src/com/android/tradefed/postprocessor/BasePostProcessor.java b/src/com/android/tradefed/postprocessor/BasePostProcessor.java
index f749d25..318c0c4 100644
--- a/src/com/android/tradefed/postprocessor/BasePostProcessor.java
+++ b/src/com/android/tradefed/postprocessor/BasePostProcessor.java
@@ -17,6 +17,8 @@
import com.android.tradefed.config.Option;
import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
@@ -93,7 +95,10 @@
/** {@inheritDoc} */
@Override
public final ITestInvocationListener init(ITestInvocationListener listener) {
+ long start = System.currentTimeMillis();
setUp();
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start);
mForwarder = listener;
return this;
}
@@ -211,6 +216,7 @@
@Override
public final void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
+ long start = System.currentTimeMillis();
mIsPostProcessing = true;
try (CloseableTraceScope ignored =
new CloseableTraceScope("run_processor_" + this.getClass().getSimpleName())) {
@@ -232,6 +238,8 @@
// Clear out the stored test and run logs.
mTestLogs.clear();
mRunLogs.clear();
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start);
}
mIsPostProcessing = false;
mForwarder.testRunEnded(elapsedTime, runMetrics);
@@ -284,6 +292,7 @@
public final void testEnded(
TestDescription test, long endTime, HashMap<String, Metric> testMetrics) {
mIsPostProcessing = true;
+ long start = System.currentTimeMillis();
try {
HashMap<String, Metric> rawValues = getRawMetricsOnly(testMetrics);
// Store the raw metrics from the test in storedTestMetrics for potential aggregation.
@@ -316,6 +325,9 @@
} catch (RuntimeException e) {
// Prevent exception from messing up the status reporting.
CLog.e(e);
+ } finally {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.COLLECTOR_TIME, System.currentTimeMillis() - start);
}
mIsPostProcessing = false;
mCurrentTest = null;
diff --git a/src/com/android/tradefed/result/JUnitToInvocationResultForwarder.java b/src/com/android/tradefed/result/JUnitToInvocationResultForwarder.java
index 9e70b6a..3040db6 100644
--- a/src/com/android/tradefed/result/JUnitToInvocationResultForwarder.java
+++ b/src/com/android/tradefed/result/JUnitToInvocationResultForwarder.java
@@ -16,6 +16,7 @@
package com.android.tradefed.result;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
@@ -40,6 +41,7 @@
public class JUnitToInvocationResultForwarder implements TestListener {
private final List<ITestInvocationListener> mInvocationListeners;
+ private CloseableTraceScope mMethodTrace = null;
public JUnitToInvocationResultForwarder(ITestInvocationListener invocationListener) {
mInvocationListeners = new ArrayList<ITestInvocationListener>(1);
@@ -77,9 +79,7 @@
@Override
public void endTest(Test test) {
HashMap<String, Metric> emptyMap = new HashMap<>();
- for (ITestInvocationListener listener : mInvocationListeners) {
- listener.testEnded(getTestId(test), emptyMap);
- }
+ endTest(test, emptyMap);
}
/**
@@ -89,8 +89,13 @@
* @param metrics The metrics in a Map format to be passed to the results callback.
*/
public void endTest(Test test, HashMap<String, Metric> metrics) {
+ TestDescription description = getTestId(test);
for (ITestInvocationListener listener : mInvocationListeners) {
- listener.testEnded(getTestId(test), metrics);
+ listener.testEnded(description, metrics);
+ }
+ if (mMethodTrace != null) {
+ mMethodTrace.close();
+ mMethodTrace = null;
}
}
@@ -115,8 +120,10 @@
/** {@inheritDoc} */
@Override
public void startTest(Test test) {
+ TestDescription description = getTestId(test);
+ mMethodTrace = new CloseableTraceScope(description.getTestName());
for (ITestInvocationListener listener : mInvocationListeners) {
- listener.testStarted(getTestId(test));
+ listener.testStarted(description);
}
}
diff --git a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
index 92d76f9..d73908d 100644
--- a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
+++ b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
@@ -60,6 +60,11 @@
private static final int MAX_CRASH_SIZE = 250000;
private static final String MAX_CRASH_SIZE_MESSAGE = "\n<Truncated>";
+ // Message from crash collector that reflect an issue
+ private static final String FILTER_NOT_FOUND =
+ "java.lang.IllegalArgumentException: testfile not found:";
+ private static final String FILTER_NOT_READ =
+ "java.lang.IllegalArgumentException: Could not read test file";
private Long mStartTime = null;
private Long mLastStartTime = null;
@@ -133,10 +138,19 @@
} else {
errorMessage = extractCrashAndAddToMessage(errorMessage, mLastStartTime);
}
- error.setErrorMessage(errorMessage.trim());
+
if (isCrash(errorMessage)) {
error.setErrorIdentifier(DeviceErrorIdentifier.INSTRUMENTATION_CRASH);
+ // Special failure due to permission issue.
+ if (errorMessage.contains(FILTER_NOT_FOUND) || errorMessage.contains(FILTER_NOT_READ)) {
+ CLog.d("Detected a permission error with filters.");
+ // First stop retrying, it won't work
+ error.setRetriable(false);
+ error.setErrorIdentifier(TestErrorIdentifier.TEST_FILTER_NEEDS_UPDATE);
+ errorMessage = "See go/iae-testfile-not-found \n" + errorMessage;
+ }
}
+ error.setErrorMessage(errorMessage.trim());
// Add metrics for assessing uncaught IntrumentationTest crash failures.
InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.CRASH_FAILURES, 1);
if (error.getFailureStatus() == null) {
diff --git a/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java b/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
index 757df86..e85c79e 100644
--- a/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
@@ -25,6 +25,7 @@
import java.io.IOException;
import java.net.Socket;
import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.atomic.AtomicBoolean;
/** An implementation of {@link ProtoResultReporter} */
public final class StreamProtoResultReporter extends ProtoResultReporter {
@@ -95,7 +96,7 @@
@Override
public void processFinalInvocationLogs(TestRecord invocationLogs) {
- if (mResultWriterThread.mCancelled) {
+ if (mResultWriterThread.mCancelled.get()) {
writeRecordToSocket(invocationLogs);
} else {
mToBeSent.add(invocationLogs);
@@ -105,7 +106,7 @@
@Override
public void processFinalProto(TestRecord finalRecord) {
try {
- if (mResultWriterThread.mCancelled) {
+ if (mResultWriterThread.mCancelled.get()) {
writeRecordToSocket(finalRecord);
} else {
mToBeSent.add(finalRecord);
@@ -114,7 +115,7 @@
// Upon invocation ended, trigger the end of the socket when the process finishes
SocketFinisher thread = new SocketFinisher();
Runtime.getRuntime().addShutdownHook(thread);
- mResultWriterThread.mCancelled = true;
+ mResultWriterThread.mCancelled.set(true);
try {
mResultWriterThread.join();
} catch (InterruptedException e) {
@@ -162,7 +163,7 @@
/** Send events from the event queue */
private class ResultWriterThread extends Thread {
- private boolean mCancelled = false;
+ private AtomicBoolean mCancelled = new AtomicBoolean(false);
public ResultWriterThread() {
super();
@@ -171,9 +172,9 @@
@Override
public void run() {
- while (!mCancelled) {
+ while (!mCancelled.get()) {
flushEvents();
- if (!mCancelled) {
+ if (!mCancelled.get()) {
RunUtil.getDefault().sleep(1000);
}
}
diff --git a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
index 5b640b6..d9a696d 100644
--- a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
+++ b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
@@ -73,6 +73,9 @@
*/
public class XmlSuiteResultFormatter implements IFormatterGenerator {
+ // The maximum size of a stack trace saved in the report.
+ private static final int STACK_TRACE_MAX_SIZE = 1024 * 1024;
+
private static final String ENCODING = "UTF-8";
private static final String TYPE = "org.kxml2.io.KXmlParser,org.kxml2.io.KXmlSerializer";
public static final String NS = null;
@@ -402,6 +405,7 @@
}
ErrorIdentifier errorIdentifier =
testResult.getValue().getFailure().getErrorIdentifier();
+ String truncatedStackTrace = getTruncatedStackTrace(fullStack, testResult.getKey());
serializer.startTag(NS, FAILURE_TAG);
serializer.attribute(NS, MESSAGE_ATTR, sanitizeXmlContent(message));
@@ -410,13 +414,31 @@
serializer.attribute(NS, ERROR_CODE_ATTR, Long.toString(errorIdentifier.code()));
}
serializer.startTag(NS, STACK_TAG);
- serializer.text(sanitizeXmlContent(fullStack));
+ serializer.text(sanitizeXmlContent(truncatedStackTrace));
serializer.endTag(NS, STACK_TAG);
serializer.endTag(NS, FAILURE_TAG);
}
}
+ /** Truncates the full stack trace with maximum {@link STACK_TRACE_MAX_SIZE} characters. */
+ private static String getTruncatedStackTrace(String fullStackTrace, String testCaseName) {
+ if (fullStackTrace == null) {
+ return null;
+ }
+ if (fullStackTrace.length() > STACK_TRACE_MAX_SIZE) {
+ CLog.i(
+ "The stack trace for test case %s contains %d characters, and has been"
+ + " truncated to %d characters in %s.",
+ testCaseName,
+ fullStackTrace.length(),
+ STACK_TRACE_MAX_SIZE,
+ TEST_RESULT_FILE_NAME);
+ return fullStackTrace.substring(0, STACK_TRACE_MAX_SIZE);
+ }
+ return fullStackTrace;
+ }
+
/** Add files captured by {@link TestFailureListener} on test failures. */
private static void HandleLoggedFiles(
XmlSerializer serializer, Entry<String, TestResult> testResult)
diff --git a/src/com/android/tradefed/retry/BaseRetryDecision.java b/src/com/android/tradefed/retry/BaseRetryDecision.java
index 8ee6419..e13e511 100644
--- a/src/com/android/tradefed/retry/BaseRetryDecision.java
+++ b/src/com/android/tradefed/retry/BaseRetryDecision.java
@@ -180,6 +180,14 @@
return decision;
}
+ // Resetting the device only happends when FULLY_ISOLATED is set, and that cleans up the
+ // device to pure state and re-run suite-level or module-level setup. Besides, it doesn't
+ // need to retry module for reboot isolation.
+ if (!IsolationGrade.FULLY_ISOLATED.equals(mRetryIsolationGrade)) {
+ CLog.i("Do not proceed on module retry because it's not set FULLY_ISOLATED.");
+ return decision;
+ }
+
try {
recoverStateOfDevices(getDevices(), attempt, module);
} catch (DeviceNotAvailableException e) {
@@ -259,25 +267,24 @@
return false;
}
- mStatistics.addResultsFromRun(previousResults);
+ boolean shouldRetry = false;
+ long retryStartTime = System.currentTimeMillis();
if (test instanceof ITestFilterReceiver) {
// TODO(b/77548917): Right now we only support ITestFilterReceiver. We should expect to
// support ITestFile*Filter*Receiver in the future.
ITestFilterReceiver filterableTest = (ITestFilterReceiver) test;
- boolean shouldRetry = handleRetryFailures(filterableTest, previousResults);
+ shouldRetry = handleRetryFailures(filterableTest, previousResults);
if (shouldRetry) {
// In case of retry, go through the recovery routine
recoverStateOfDevices(getDevices(), attemptJustExecuted, module);
}
- return shouldRetry;
} else if (test instanceof IAutoRetriableTest) {
// Routine for IRemoteTest that don't support filters but still needs retry.
IAutoRetriableTest autoRetryTest = (IAutoRetriableTest) test;
- boolean shouldRetry = autoRetryTest.shouldRetry(attemptJustExecuted, previousResults);
+ shouldRetry = autoRetryTest.shouldRetry(attemptJustExecuted, previousResults);
if (shouldRetry) {
recoverStateOfDevices(getDevices(), attemptJustExecuted, module);
}
- return shouldRetry;
} else {
CLog.d(
"%s does not implement ITestFilterReceiver or IAutoRetriableTest, thus "
@@ -285,6 +292,12 @@
test);
return false;
}
+ long retryCost = System.currentTimeMillis() - retryStartTime;
+ if (!shouldRetry) {
+ retryCost = 0L;
+ }
+ mStatistics.addResultsFromRun(previousResults, retryCost, attemptJustExecuted);
+ return shouldRetry;
}
@Override
diff --git a/src/com/android/tradefed/retry/RetryStatistics.java b/src/com/android/tradefed/retry/RetryStatistics.java
index be4afca..0d0b343 100644
--- a/src/com/android/tradefed/retry/RetryStatistics.java
+++ b/src/com/android/tradefed/retry/RetryStatistics.java
@@ -17,7 +17,9 @@
import com.android.tradefed.testtype.IRemoteTest;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
/**
* Structure holding the statistics for a retry session of one {@link IRemoteTest}. Not all fields
@@ -26,6 +28,7 @@
public class RetryStatistics {
// The time spent in retry. Always populated if retries or iterations occurred
public long mRetryTime = 0L;
+ public Map<Integer, Long> mAttemptIsolationCost = new HashMap<>();
// Success and failure counts. Populated for RETRY_ANY_FAILURE.
public long mRetrySuccess = 0L;
@@ -41,4 +44,15 @@
}
return aggregatedStats;
}
+
+ public static long isolationCostPerAttempt(int attempt, List<RetryStatistics> stats) {
+ long isolationCost = 0L;
+ for (RetryStatistics s : stats) {
+ Long attemptCost = s.mAttemptIsolationCost.get(attempt);
+ if (attemptCost != null) {
+ isolationCost += attemptCost;
+ }
+ }
+ return isolationCost;
+ }
}
diff --git a/src/com/android/tradefed/retry/RetryStatsHelper.java b/src/com/android/tradefed/retry/RetryStatsHelper.java
index f7f08e8..e4e17d3 100644
--- a/src/com/android/tradefed/retry/RetryStatsHelper.java
+++ b/src/com/android/tradefed/retry/RetryStatsHelper.java
@@ -32,9 +32,17 @@
/** Add the results from the latest run to be tracked for statistics purpose. */
public void addResultsFromRun(List<TestRunResult> mLatestResults) {
+ addResultsFromRun(mLatestResults, 0L, 0);
+ }
+
+ /** Add the results from the latest run to be tracked for statistics purpose. */
+ public void addResultsFromRun(List<TestRunResult> mLatestResults, long timeForIsolation, int attempt) {
if (!mResults.isEmpty()) {
updateSuccess(mResults.get(mResults.size() - 1), mLatestResults);
}
+ if (timeForIsolation != 0L) {
+ mStats.mAttemptIsolationCost.put(attempt, timeForIsolation);
+ }
mResults.add(mLatestResults);
}
diff --git a/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java b/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java
index ef080bc..9e453cd 100644
--- a/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java
+++ b/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java
@@ -39,6 +39,15 @@
return runSandbox(info, config, listener);
}
+ public static void teardownSandbox(IConfiguration config) {
+ ISandbox sandbox =
+ (ISandbox) config.getConfigurationObject(Configuration.SANDBOX_TYPE_NAME);
+ if (sandbox == null) {
+ throw new RuntimeException("Couldn't find the sandbox object.");
+ }
+ sandbox.tearDown();
+ }
+
/** Preparation step of the sandbox */
public static void prepareSandbox(
TestInformation info, IConfiguration config, ITestInvocationListener listener)
@@ -49,7 +58,13 @@
throw new RuntimeException("Couldn't find the sandbox object.");
}
PrettyPrintDelimiter.printStageDelimiter("Starting Sandbox Environment Setup");
- Exception res = sandbox.prepareEnvironment(info.getContext(), config, listener);
+ Exception res = null;
+ try {
+ res = sandbox.prepareEnvironment(info.getContext(), config, listener);
+ } catch (RuntimeException e) {
+ sandbox.tearDown();
+ throw e;
+ }
if (res != null) {
CLog.w("Sandbox prepareEnvironment threw an Exception.");
sandbox.tearDown();
diff --git a/src/com/android/tradefed/service/TradefedFeatureClient.java b/src/com/android/tradefed/service/TradefedFeatureClient.java
index 33845fb..9dbe801 100644
--- a/src/com/android/tradefed/service/TradefedFeatureClient.java
+++ b/src/com/android/tradefed/service/TradefedFeatureClient.java
@@ -17,6 +17,7 @@
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.StreamUtil;
@@ -70,7 +71,8 @@
private FeatureResponse triggerFeature(
String featureName, String invocationReference, Map<String, String> args) {
FeatureResponse response;
- try {
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope("triggerFeature:" + featureName)) {
CLog.d("invoking feature '%s'", featureName);
FeatureRequest.Builder request =
FeatureRequest.newBuilder().setName(featureName).putAllArgs(args);
@@ -92,7 +94,17 @@
.build())
.build();
}
- CLog.d("Feature name: %s. response: %s", featureName, response);
+ String message = String.format("Feature name: %s. response: %s", featureName, response);
+ if (response.hasErrorInfo()) {
+ StringBuilder callsite = new StringBuilder();
+ for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
+ callsite.append(e.toString());
+ }
+ message += String.format(". Callsite: %s", callsite);
+ CLog.w(message);
+ } else {
+ CLog.d(message);
+ }
return response;
}
diff --git a/src/com/android/tradefed/targetprep/CreateUserPreparer.java b/src/com/android/tradefed/targetprep/CreateUserPreparer.java
index c9c1260..41f3773 100644
--- a/src/com/android/tradefed/targetprep/CreateUserPreparer.java
+++ b/src/com/android/tradefed/targetprep/CreateUserPreparer.java
@@ -20,27 +20,23 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.TestDevice;
-import com.android.tradefed.device.UserInfo;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
-import java.util.ArrayList;
-import java.util.Map;
/** Target preparer for creating user and cleaning it up at the end. */
@OptionClass(alias = "create-user-preparer")
public class CreateUserPreparer extends BaseTargetPreparer {
- private static final String TF_CREATED_USER = "tf_created_user";
@Option(
name = "reuse-test-user",
description =
"Whether or not to reuse already created tradefed test user, or remove them "
+ " and re-create them between module runs.")
- private boolean mReuseTestUser = false;
+ private boolean mReuseTestUser;
- private Integer mOriginalUser = null;
- private Integer mCreatedUserId = null;
+ private Integer mOriginalUser;
+ private Integer mCreatedUserId;
@Override
public void setUp(TestInformation testInfo)
@@ -52,9 +48,18 @@
throw new TargetSetupError(
"Failed to get the current user.", device.getDeviceDescriptor());
}
+ CLog.i("setUp(): mOriginalUser=%d, mReuseTestUser=%b", mOriginalUser, mReuseTestUser);
- mCreatedUserId = createUser(device);
+ mCreatedUserId = UserCreationHelper.createUser(device, mReuseTestUser);
+ switchCurrentUser(device, mCreatedUserId);
+
+ device.waitForDeviceAvailable();
+ device.postBootSetup();
+ }
+
+ private void switchCurrentUser(ITestDevice device, int userId)
+ throws TargetSetupError, DeviceNotAvailableException {
if (!device.startUser(mCreatedUserId, true)) {
throw new TargetSetupError(
String.format("Failed to start to user '%s'", mCreatedUserId),
@@ -65,16 +70,12 @@
String.format("Failed to switch to user '%s'", mCreatedUserId),
device.getDeviceDescriptor());
}
- device.waitForDeviceAvailable();
- device.postBootSetup();
}
@Override
public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
if (mCreatedUserId == null) {
- return;
- }
- if (mOriginalUser == null) {
+ CLog.d("Skipping teardown because no user was created");
return;
}
if (e instanceof DeviceNotAvailableException) {
@@ -82,66 +83,24 @@
return;
}
ITestDevice device = testInfo.getDevice();
- if (!device.switchUser(mOriginalUser)) {
- CLog.e("Failed to switch back to original user '%s'", mOriginalUser);
+
+ if (mOriginalUser == null) {
+ CLog.d("Skipping teardown because original user is null");
+ return;
}
+ switchBackToOriginalUser(device);
+
if (!mReuseTestUser) {
device.removeUser(mCreatedUserId);
}
}
- private int createUser(ITestDevice device)
- throws DeviceNotAvailableException, TargetSetupError {
- if (mReuseTestUser) {
- Integer existingTFUser = findExistingTradefedUser(device);
- if (existingTFUser != null) {
- return existingTFUser;
- }
+ private void switchBackToOriginalUser(ITestDevice device) throws DeviceNotAvailableException {
+ CLog.d(
+ "switchBackToOriginalUser(): switching current user from %d to user %d ",
+ mCreatedUserId, mOriginalUser);
+ if (!device.switchUser(mOriginalUser)) {
+ CLog.e("Failed to switch back to original user '%s'", mOriginalUser);
}
-
- cleanupOldUsersIfLimitReached(device);
-
- try {
- return device.createUser(TF_CREATED_USER);
- } catch (IllegalStateException e) {
- throw new TargetSetupError("Failed to create user.", e, device.getDeviceDescriptor());
- }
- }
-
- private void cleanupOldUsersIfLimitReached(ITestDevice device)
- throws DeviceNotAvailableException {
- ArrayList<Integer> tfCreatedUsers = new ArrayList<>();
- int existingUsersCount = 0;
- for (Map.Entry<Integer, UserInfo> entry : device.getUserInfos().entrySet()) {
- UserInfo userInfo = entry.getValue();
- String userName = userInfo.userName();
-
- if (!userInfo.isGuest()) {
- // Guest users don't fall under the quota.
- existingUsersCount++;
- }
- if (userName != null && userName.equals(TF_CREATED_USER)) {
- tfCreatedUsers.add(entry.getKey());
- }
- }
-
- if (existingUsersCount >= device.getMaxNumberOfUsersSupported()) {
- // Reached the maximum number of users allowed. Remove stale users to free up space.
- for (int userId : tfCreatedUsers) {
- device.removeUser(userId);
- }
- }
- }
-
- private Integer findExistingTradefedUser(ITestDevice device)
- throws DeviceNotAvailableException {
- for (Map.Entry<Integer, UserInfo> entry : device.getUserInfos().entrySet()) {
- String userName = entry.getValue().userName();
-
- if (userName != null && userName.equals(TF_CREATED_USER)) {
- return entry.getKey();
- }
- }
- return null;
}
}
diff --git a/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java b/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java
index dd1bded..0a3ca9b 100644
--- a/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java
+++ b/src/com/android/tradefed/targetprep/DeviceFlashPreparer.java
@@ -36,6 +36,7 @@
import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.targetprep.IDeviceFlasher.UserDataFlashOption;
+import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
@@ -48,6 +49,7 @@
public abstract class DeviceFlashPreparer extends BaseTargetPreparer {
private static final int BOOT_POLL_TIME_MS = 5 * 1000;
+ private static final long SNAPSHOT_CANCEL_TIMEOUT = 20000L;
@Option(
name = "device-boot-time",
@@ -119,6 +121,11 @@
+ "should be flashed to")
private String mRamdiskPartition = "boot";
+ @Option(
+ name = "cancel-ota-snapshot",
+ description = "In case an OTA snapshot is in progress, cancel it.")
+ private boolean mCancelSnapshot = false;
+
/**
* Sets the device boot time
* <p/>
@@ -219,6 +226,18 @@
}
start = System.currentTimeMillis();
flasher.preFlashOperations(device, deviceBuild);
+ // After preFlashOperations device should be in bootloader
+ if (mCancelSnapshot && TestDeviceState.FASTBOOT.equals(device.getDeviceState())) {
+ CommandResult res =
+ device.executeFastbootCommand(
+ SNAPSHOT_CANCEL_TIMEOUT, "snapshot-update", "cancel");
+ if (!CommandStatus.SUCCESS.equals(res.getStatus())) {
+ CLog.w(
+ "Failed to cancel snapshot: %s.\nstdout:%s\nstderr:%s",
+ res.getStatus(), res.getStdout(), res.getStderr());
+ }
+ }
+
// Only #flash is included in the critical section
getHostOptions().takePermit(PermitLimitType.CONCURRENT_FLASHER);
queueTime = System.currentTimeMillis() - start;
diff --git a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
index aa89106..090782c 100644
--- a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
@@ -35,6 +35,7 @@
import com.android.tradefed.util.RunUtil;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
@@ -75,6 +76,10 @@
protected static final String PARENT_SESSION_CREATION_CMD = "pm install-create --multi-package";
protected static final String CHILD_SESSION_CREATION_CMD = "pm install-create";
protected static final String APEX_OPTION = "--apex";
+ // The dump logic in {@link com.android.server.pm.ComputerEngine#generateApexPackageInfo} is
+ // invalid.
+ private static final ImmutableList<String> PACKAGES_WITH_INVALID_DUMP_INFO =
+ ImmutableList.of("com.google.mainline.primary.libs");
private List<ApexInfo> mTestApexInfoList = new ArrayList<>();
private List<String> mApexModulesToUninstall = new ArrayList<>();
@@ -97,7 +102,13 @@
name = "apex-staging-wait-time",
description = "The time in ms to wait for apex staged session ready.",
isTimeVal = true)
- private long mApexStagingWaitTime = 1 * 60 * 1000;
+ private long mApexStagingWaitTime = 0;
+
+ @Option(
+ name = "apex-rollback-wait-time",
+ description = "The time in ms to wait for apex rollback success.",
+ isTimeVal = true)
+ private long mApexRollbackWaitTime = 1 * 60 * 1000;
@Option(
name="extra-booting-wait-time",
@@ -198,7 +209,9 @@
* @throws DeviceNotAvailableException if reboot fails.
*/
private void activateStagedInstall(ITestDevice device) throws DeviceNotAvailableException {
- RunUtil.getDefault().sleep(mApexStagingWaitTime);
+ if (mApexStagingWaitTime > 0) {
+ RunUtil.getDefault().sleep(mApexStagingWaitTime);
+ }
device.reboot();
// Some devices need extra waiting time after reboot to get fully ready.
if (mExtraBootingWaitTime > 0) {
@@ -408,12 +421,16 @@
}
}
CLog.i("Wait for rollback fully done.");
- RunUtil.getDefault().sleep(mApexStagingWaitTime);
+ RunUtil.getDefault().sleep(mApexRollbackWaitTime);
CLog.i("Device Rebooting");
device.reboot();
CLog.i("Reboot finished. Wait for rollback fully propagate.");
- RunUtil.getDefault().sleep(mApexStagingWaitTime);
+ RunUtil.getDefault().sleep(mApexRollbackWaitTime);
device.waitForDeviceAvailable();
+ // TODO(b/262626794): Remove after confirming with framework team about the
+ // behavior of rollbaking mainline modules.
+ CLog.i("Clean up staged and active session for mainline test mapping.");
+ cleanUpStagedAndActiveSession(device);
}
}
}
@@ -624,8 +641,9 @@
device.getDeviceDescriptor(),
DeviceErrorIdentifier.APK_INSTALLATION_FAILED);
}
- RunUtil.getDefault().sleep(mApexStagingWaitTime);
-
+ if (mApexStagingWaitTime > 0) {
+ RunUtil.getDefault().sleep(mApexStagingWaitTime);
+ }
if (log.contains("Success")) {
CLog.d(
"Train is staged successfully. Cmd: %s, Output: %s.",
@@ -651,7 +669,9 @@
device.getDeviceDescriptor(),
DeviceErrorIdentifier.FAIL_PUSH_FILE);
} else {
- CLog.d("%s pushed successfully to %s.", moduleFile.getName(), MODULE_PUSH_REMOTE_PATH + moduleFile.getName());
+ CLog.d(
+ "%s pushed successfully to %s.",
+ moduleFile.getName(), MODULE_PUSH_REMOTE_PATH + moduleFile.getName());
}
if (moduleFile.getName().endsWith(APK_SUFFIX)) {
String packageName = parsePackageName(moduleFile, device.getDeviceDescriptor());
@@ -670,7 +690,9 @@
CLog.d("Parent session %s created successfully. ", parentSessionId);
} else {
throw new TargetSetupError(
- String.format("Failed to create parent session. Error: %s, Stdout: %s", res.getStderr(), res.getStdout()),
+ String.format(
+ "Failed to create parent session. Error: %s, Stdout: %s",
+ res.getStderr(), res.getStdout()),
device.getDeviceDescriptor());
}
@@ -678,20 +700,45 @@
String childSessionId = null;
if (moduleFile.getName().endsWith(APEX_SUFFIX)) {
if (mEnableRollback) {
- res = device.executeShellV2Command(String.format("%s %s %s %s | egrep -o -e '[0-9]+'", CHILD_SESSION_CREATION_CMD, APEX_OPTION, STAGED_INSTALL_OPTION, ENABLE_ROLLBACK_INSTALL_OPTION));
+ res =
+ device.executeShellV2Command(
+ String.format(
+ "%s %s %s %s | egrep -o -e '[0-9]+'",
+ CHILD_SESSION_CREATION_CMD,
+ APEX_OPTION,
+ STAGED_INSTALL_OPTION,
+ ENABLE_ROLLBACK_INSTALL_OPTION));
} else {
- res = device.executeShellV2Command(String.format("%s %s %s | egrep -o -e '[0-9]+'", CHILD_SESSION_CREATION_CMD, APEX_OPTION, STAGED_INSTALL_OPTION));
+ res =
+ device.executeShellV2Command(
+ String.format(
+ "%s %s %s | egrep -o -e '[0-9]+'",
+ CHILD_SESSION_CREATION_CMD,
+ APEX_OPTION,
+ STAGED_INSTALL_OPTION));
}
} else {
if (mEnableRollback) {
- res = device.executeShellV2Command(String.format("%s %s %s | egrep -o -e '[0-9]+'", CHILD_SESSION_CREATION_CMD, STAGED_INSTALL_OPTION, ENABLE_ROLLBACK_INSTALL_OPTION));
+ res =
+ device.executeShellV2Command(
+ String.format(
+ "%s %s %s | egrep -o -e '[0-9]+'",
+ CHILD_SESSION_CREATION_CMD,
+ STAGED_INSTALL_OPTION,
+ ENABLE_ROLLBACK_INSTALL_OPTION));
} else {
- res = device.executeShellV2Command(String.format("%s %s | egrep -o -e '[0-9]+'", CHILD_SESSION_CREATION_CMD, STAGED_INSTALL_OPTION));
+ res =
+ device.executeShellV2Command(
+ String.format(
+ "%s %s | egrep -o -e '[0-9]+'",
+ CHILD_SESSION_CREATION_CMD, STAGED_INSTALL_OPTION));
}
}
if (res.getStatus() == CommandStatus.SUCCESS) {
childSessionId = res.getStdout();
- CLog.d("Child session %s created successfully for %s. ", childSessionId, moduleFile.getName());
+ CLog.d(
+ "Child session %s created successfully for %s. ",
+ childSessionId, moduleFile.getName());
} else {
throw new TargetSetupError(
String.format(
@@ -707,29 +754,33 @@
parsePackageName(moduleFile, device.getDeviceDescriptor()),
MODULE_PUSH_REMOTE_PATH + moduleFile.getName()));
if (res.getStatus() == CommandStatus.SUCCESS) {
- CLog.d("Successfully wrote %s to session %s. ", moduleFile.getName(), childSessionId);
+ CLog.d(
+ "Successfully wrote %s to session %s. ",
+ moduleFile.getName(), childSessionId);
} else {
throw new TargetSetupError(
String.format("Failed to write %s to session %s. Error: %s, Stdout: %s",
moduleFile.getName(), childSessionId, res.getStderr(), res.getStdout()),
device.getDeviceDescriptor());
}
- res = device.executeShellV2Command(
+ res =
+ device.executeShellV2Command(
String.format(
- "pm install-add-session " + parentSessionId + " " + childSessionId));
+ "pm install-add-session "
+ + parentSessionId
+ + " "
+ + childSessionId));
if (res.getStatus() != CommandStatus.SUCCESS) {
throw new TargetSetupError(
- String.format("Failed to add child session %s to parent session %s. Error: %s, Stdout: %s",
- childSessionId, parentSessionId, res.getStderr(), res.getStdout()),
- device.getDeviceDescriptor());
+ String.format(
+ "Failed to add child session %s to parent session %s. Error: %s,"
+ + " Stdout: %s",
+ childSessionId, parentSessionId, res.getStderr(), res.getStdout()),
+ device.getDeviceDescriptor());
}
}
res = device.executeShellV2Command("pm install-commit " + parentSessionId);
- // Wait until all apexes are fully staged and ready.
- // TODO: should have adb level solution b/130039562
- RunUtil.getDefault().sleep(mApexStagingWaitTime);
-
if (res.getStatus() == CommandStatus.SUCCESS) {
CLog.d("Train is staged successfully. Stdout: %s.", res.getStdout());
} else {
@@ -1026,6 +1077,10 @@
for (ApexInfo testApexInfo : mTestApexInfoList) {
if (!activatedApexInfo.containsKey(testApexInfo.name)) {
failToActivateApex.add(testApexInfo);
+ } else if (PACKAGES_WITH_INVALID_DUMP_INFO.contains(testApexInfo.name)) {
+ // Skip checking version or sourceDir if we can't get the valid info.
+ // ToDo(b/265785212): Remove this if bug fixed.
+ continue;
} else if (activatedApexInfo.get(testApexInfo.name).versionCode
!= testApexInfo.versionCode) {
failToActivateApex.add(testApexInfo);
diff --git a/src/com/android/tradefed/targetprep/RunCommandTargetPreparer.java b/src/com/android/tradefed/targetprep/RunCommandTargetPreparer.java
index b543552..7b4d591 100644
--- a/src/com/android/tradefed/targetprep/RunCommandTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/RunCommandTargetPreparer.java
@@ -16,6 +16,7 @@
package com.android.tradefed.targetprep;
+import com.android.annotations.VisibleForTesting;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.BackgroundDeviceAction;
@@ -159,6 +160,11 @@
mCommands.add(cmd);
}
+ @VisibleForTesting
+ public List<String> getCommands() {
+ return mCommands;
+ }
+
/**
* Returns the device to apply the preparer on.
*
diff --git a/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java b/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java
index 53e2fa3..9f21002 100644
--- a/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java
@@ -16,9 +16,6 @@
package com.android.tradefed.targetprep;
-import com.android.tradefed.config.ConfigurationException;
-import com.android.tradefed.config.IConfiguration;
-import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -46,8 +43,7 @@
* running on the device can read this argument to respond to this state.
*/
@OptionClass(alias = "run-on-secondary-user")
-public class RunOnSecondaryUserTargetPreparer extends BaseTargetPreparer
- implements IConfigurationReceiver {
+public class RunOnSecondaryUserTargetPreparer extends BaseTargetPreparer {
@VisibleForTesting static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER";
@@ -55,8 +51,6 @@
@VisibleForTesting static final String SKIP_TESTS_REASON_KEY = "skip-tests-reason";
- private IConfiguration mConfiguration;
-
private int userIdToDelete = -1;
private int originalUserId;
@@ -69,14 +63,6 @@
private List<String> mTestPackages = new ArrayList<>();
@Override
- public void setConfiguration(IConfiguration configuration) {
- if (configuration == null) {
- throw new NullPointerException("configuration must not be null");
- }
- mConfiguration = configuration;
- }
-
- @Override
public void setUp(TestInformation testInfo)
throws TargetSetupError, DeviceNotAvailableException {
int secondaryUserId = getSecondaryUserId(testInfo.getDevice());
@@ -85,7 +71,7 @@
if (!assumeTrue(
canCreateAdditionalUsers(testInfo.getDevice(), 1),
"Device cannot support additional users",
- testInfo.getDevice())) {
+ testInfo)) {
return;
}
@@ -131,6 +117,12 @@
@Override
public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ String value = testInfo.properties().remove(SKIP_TESTS_REASON_KEY);
+ if (value != null) {
+ // Skip teardown if a skip test reason was set.
+ return;
+ }
+
testInfo.properties().remove(RUN_TESTS_AS_USER_KEY);
int currentUser = testInfo.getDevice().getCurrentUser();
if (currentUser != originalUserId) {
@@ -146,17 +138,9 @@
*
* <p>This will return {@code value} and, if it is not true, setup should be skipped.
*/
- private boolean assumeTrue(boolean value, String reason, ITestDevice device)
- throws TargetSetupError {
+ private boolean assumeTrue(boolean value, String reason, TestInformation testInfo) {
if (!value) {
- setDisableTearDown(true);
- try {
- mConfiguration.injectOptionValue(
- "instrumentation-arg", SKIP_TESTS_REASON_KEY, reason.replace(" ", "\\ "));
- } catch (ConfigurationException e) {
- throw new TargetSetupError(
- "Error setting skip-tests-reason", e, device.getDeviceDescriptor());
- }
+ testInfo.properties().put(SKIP_TESTS_REASON_KEY, reason.replace(" ", "\\ "));
}
return value;
diff --git a/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java b/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java
index 834593b..9a7b4eb 100644
--- a/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java
@@ -16,9 +16,6 @@
package com.android.tradefed.targetprep;
-import com.android.tradefed.config.ConfigurationException;
-import com.android.tradefed.config.IConfiguration;
-import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -48,8 +45,7 @@
* to this state.
*/
@OptionClass(alias = "run-on-work-profile")
-public class RunOnWorkProfileTargetPreparer extends BaseTargetPreparer
- implements IConfigurationReceiver {
+public class RunOnWorkProfileTargetPreparer extends BaseTargetPreparer {
@VisibleForTesting static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER";
@@ -57,8 +53,6 @@
@VisibleForTesting static final String SKIP_TESTS_REASON_KEY = "skip-tests-reason";
- private IConfiguration mConfiguration;
-
private int mUserIdToDelete = -1;
private DeviceOwner mDeviceOwnerToSet = null;
@@ -81,17 +75,9 @@
private List<String> mTestPackages = new ArrayList<>();
@Override
- public void setConfiguration(IConfiguration configuration) {
- if (configuration == null) {
- throw new NullPointerException("configuration must not be null");
- }
- mConfiguration = configuration;
- }
-
- @Override
public void setUp(TestInformation testInfo)
throws TargetSetupError, DeviceNotAvailableException {
- if (!requireFeatures(testInfo.getDevice(), "android.software.managed_users")) {
+ if (!requireFeatures(testInfo, "android.software.managed_users")) {
return;
}
@@ -101,7 +87,7 @@
if (!assumeTrue(
canCreateAdditionalUsers(testInfo.getDevice(), 1),
"Device cannot support additional users",
- testInfo.getDevice())) {
+ testInfo)) {
return;
}
@@ -162,6 +148,11 @@
@Override
public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ String value = testInfo.properties().remove(SKIP_TESTS_REASON_KEY);
+ if (value != null) {
+ // Skip teardown if a skip test reason was set.
+ return;
+ }
testInfo.properties().remove(RUN_TESTS_AS_USER_KEY);
if (mUserIdToDelete != -1) {
testInfo.getDevice().removeUser(mUserIdToDelete);
@@ -173,13 +164,13 @@
}
}
- private boolean requireFeatures(ITestDevice device, String... features)
- throws TargetSetupError, DeviceNotAvailableException {
+ private boolean requireFeatures(TestInformation testInfo, String... features)
+ throws DeviceNotAvailableException {
for (String feature : features) {
if (!assumeTrue(
- device.hasFeature(feature),
+ testInfo.getDevice().hasFeature(feature),
"Device does not have feature " + feature,
- device)) {
+ testInfo)) {
return false;
}
}
@@ -192,16 +183,10 @@
*
* <p>This will return {@code value} and, if it is not true, setup should be skipped.
*/
- private boolean assumeTrue(boolean value, String reason, ITestDevice device)
- throws TargetSetupError {
+ private boolean assumeTrue(boolean value, String reason, TestInformation testInfo) {
if (!value) {
- setDisableTearDown(true);
- try {
- mConfiguration.injectOptionValue(
- "instrumentation-arg", SKIP_TESTS_REASON_KEY, reason.replace(" ", "\\ "));
- } catch (ConfigurationException e) {
- throw new TargetSetupError(
- "Error setting skip-tests-reason", e, device.getDeviceDescriptor());
+ if (!value) {
+ testInfo.properties().put(SKIP_TESTS_REASON_KEY, reason.replace(" ", "\\ "));
}
}
diff --git a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
index 787c494..4359adf0 100644
--- a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
+++ b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
@@ -179,7 +179,7 @@
private boolean mInstantMode = false;
@Option(name = "aapt-version", description = "The version of AAPT for APK parsing.")
- private AaptVersion mAaptVersion = AaptVersion.AAPT;
+ private AaptVersion mAaptVersion = AaptVersion.AAPT2;
@Option(
name = "force-install-mode",
diff --git a/src/com/android/tradefed/targetprep/UserCreationHelper.java b/src/com/android/tradefed/targetprep/UserCreationHelper.java
new file mode 100644
index 0000000..429ce8c
--- /dev/null
+++ b/src/com/android/tradefed/targetprep/UserCreationHelper.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 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.tradefed.targetprep;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.UserInfo;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+// Not directly unit tested, but its clients are
+final class UserCreationHelper {
+
+ private static final String TF_CREATED_USER = "tf_created_user";
+
+ public static int createUser(ITestDevice device, boolean reuseTestUser)
+ throws DeviceNotAvailableException, TargetSetupError {
+ if (reuseTestUser) {
+ Integer existingTFUser = findExistingTradefedUser(device);
+ if (existingTFUser != null) {
+ return existingTFUser;
+ }
+ }
+
+ cleanupOldUsersIfLimitReached(device);
+
+ try {
+ return device.createUser(TF_CREATED_USER);
+ } catch (IllegalStateException e) {
+ throw new TargetSetupError("Failed to create user.", e, device.getDeviceDescriptor());
+ }
+ }
+
+ private static void cleanupOldUsersIfLimitReached(ITestDevice device)
+ throws DeviceNotAvailableException {
+ ArrayList<Integer> tfCreatedUsers = new ArrayList<>();
+ int existingUsersCount = 0;
+ for (Map.Entry<Integer, UserInfo> entry : device.getUserInfos().entrySet()) {
+ UserInfo userInfo = entry.getValue();
+ String userName = userInfo.userName();
+
+ if (!userInfo.isGuest()) {
+ // Guest users don't fall under the quota.
+ existingUsersCount++;
+ }
+ if (userName != null && userName.equals(TF_CREATED_USER)) {
+ tfCreatedUsers.add(entry.getKey());
+ }
+ }
+
+ if (existingUsersCount >= device.getMaxNumberOfUsersSupported()) {
+ // Reached the maximum number of users allowed. Remove stale users to free up space.
+ for (int userId : tfCreatedUsers) {
+ device.removeUser(userId);
+ }
+ }
+ }
+
+ private static Integer findExistingTradefedUser(ITestDevice device)
+ throws DeviceNotAvailableException {
+ for (Map.Entry<Integer, UserInfo> entry : device.getUserInfos().entrySet()) {
+ String userName = entry.getValue().userName();
+
+ if (userName != null && userName.equals(TF_CREATED_USER)) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
+
+ private UserCreationHelper() {
+ throw new UnsupportedOperationException("provide only static methods");
+ }
+}
diff --git a/src/com/android/tradefed/targetprep/VisibleBackgroundUserPreparer.java b/src/com/android/tradefed/targetprep/VisibleBackgroundUserPreparer.java
new file mode 100644
index 0000000..671e3d7
--- /dev/null
+++ b/src/com/android/tradefed/targetprep/VisibleBackgroundUserPreparer.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2023 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.tradefed.targetprep;
+
+import com.android.annotations.VisibleForTesting;
+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.invoker.TestInformation;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.util.Iterator;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/** Target preparer for running tests in a user that is started in the visible in the background. */
+@OptionClass(alias = "visible-background-user-preparer")
+public class VisibleBackgroundUserPreparer extends BaseTargetPreparer {
+
+ @VisibleForTesting public static final int INVALID_DISPLAY = -1; // same as android.view.Display
+ @VisibleForTesting public static final int DEFAULT_DISPLAY = 0; // same as android.view.Display
+
+ // Needed when running tests on background user on visible display
+ @VisibleForTesting protected static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER";
+
+ @Option(
+ name = "reuse-test-user",
+ description =
+ "Whether or not to reuse already created tradefed test user, or remove them "
+ + " and re-create them between module runs.")
+ private boolean mReuseTestUser;
+
+ @Option(name = "display-id", description = "Which display to start the user visible on")
+ private int mDisplayId = INVALID_DISPLAY;
+
+ private Integer mUserId;
+ private boolean mUserAlreadyVisible;
+
+ @Override
+ public void setUp(TestInformation testInfo)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ ITestDevice device = testInfo.getDevice();
+ if (!device.isVisibleBackgroundUsersSupported()) {
+ throw new TargetSetupError("feature not supported", device.getDeviceDescriptor());
+ }
+ CLog.i("setUp(): mReuseTestUser=%b, mDisplayId=%d", mReuseTestUser, mDisplayId);
+
+ mUserId = UserCreationHelper.createUser(device, mReuseTestUser);
+
+ startUserVisibleOnBackground(testInfo, device, mUserId);
+
+ device.waitForDeviceAvailable();
+ device.postBootSetup();
+ }
+
+ public void setDisplayId(int displayId) {
+ if (displayId == INVALID_DISPLAY) {
+ throw new IllegalArgumentException(
+ "Cannot set it as INVALID_DISPLAY (" + INVALID_DISPLAY + ")");
+ }
+ mDisplayId = displayId;
+ }
+
+ @VisibleForTesting
+ public @Nullable Integer getDisplayId() {
+ return mDisplayId;
+ }
+
+ private void startUserVisibleOnBackground(
+ TestInformation testInfo, ITestDevice device, int userId)
+ throws TargetSetupError, DeviceNotAvailableException {
+ int displayId = mDisplayId;
+ if (displayId == INVALID_DISPLAY) {
+ // If display is not explicitly set (by option / setter), get the first available one
+ Set<Integer> displays = device.listDisplayIdsForStartingVisibleBackgroundUsers();
+ CLog.d("Displays: %s", displays);
+ if (displays.isEmpty()) {
+ throw new TargetSetupError(
+ String.format("No display available to start to user '%d'", userId),
+ device.getDeviceDescriptor());
+ }
+ Iterator<Integer> iterator = displays.iterator();
+ displayId = iterator.next();
+ if (displayId == DEFAULT_DISPLAY
+ && device.isVisibleBackgroundUsersOnDefaultDisplaySupported()) {
+ // Ignore default display - it's a special case where the display id should have
+ // been passed directly
+ CLog.d(
+ "Ignoring DEFAULT_DISPLAY because device supports background users on"
+ + " default display");
+ if (!iterator.hasNext()) {
+ throw new TargetSetupError(
+ String.format(
+ "Only DEFAULT_DISPLAY available to start to user '%d'", userId),
+ device.getDeviceDescriptor());
+ }
+ displayId = iterator.next();
+ }
+ }
+
+ mUserAlreadyVisible = device.isUserVisibleOnDisplay(userId, displayId);
+ if (mUserAlreadyVisible) {
+ CLog.d(
+ "startUserVisibleOnBackground(): user %d already visible on display %d",
+ userId, displayId);
+ } else {
+ CLog.d(
+ "startUserVisibleOnBackground(): starting user %d visible on display %d",
+ userId, displayId);
+
+ if (!device.startVisibleBackgroundUser(userId, displayId, /* waitFlag= */ true)) {
+ throw new TargetSetupError(
+ String.format(
+ "Failed to start to user '%s' on display %d", mUserId, displayId),
+ device.getDeviceDescriptor());
+ }
+ }
+
+ CLog.i("Setting test property %s=%d", RUN_TESTS_AS_USER_KEY, mUserId);
+ testInfo.properties().put(RUN_TESTS_AS_USER_KEY, Integer.toString(mUserId));
+ }
+
+ @Override
+ public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ if (mUserId == null) {
+ CLog.d("Skipping teardown because no user was created or reused");
+ return;
+ }
+ if (e instanceof DeviceNotAvailableException) {
+ CLog.d("Skipping teardown due to dnae: %s", e.getMessage());
+ return;
+ }
+ ITestDevice device = testInfo.getDevice();
+
+ stopTestUser(device);
+
+ if (!mReuseTestUser) {
+ device.removeUser(mUserId);
+ }
+ }
+
+ private void stopTestUser(ITestDevice device) throws DeviceNotAvailableException {
+ if (mUserAlreadyVisible) {
+ CLog.d("stopTestUser(): user %d was already visible on start", mUserId);
+ return;
+ }
+ CLog.d("stopTestUser(): stopping user %d ", mUserId);
+ if (!device.stopUser(mUserId, /* waitFlag= */ true, /* forceFlag= */ true)) {
+ CLog.e("Failed to stop user '%d'", mUserId);
+ }
+ }
+}
diff --git a/src/com/android/tradefed/targetprep/sync/DeviceSyncHelper.java b/src/com/android/tradefed/targetprep/sync/DeviceSyncHelper.java
index 594904f..7f5b29e 100644
--- a/src/com/android/tradefed/targetprep/sync/DeviceSyncHelper.java
+++ b/src/com/android/tradefed/targetprep/sync/DeviceSyncHelper.java
@@ -16,19 +16,21 @@
package com.android.tradefed.targetprep.sync;
import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.IFileEntry;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.RunUtil;
+import com.google.common.collect.ImmutableSet;
+
import java.io.File;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.Arrays;
-import java.util.HashSet;
-import java.util.List;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
import java.util.Set;
/**
@@ -40,6 +42,10 @@
private final ITestDevice mDevice;
private final File mTargetFilesFolder;
+ // Partitions known by adb in "adb sync"
+ private static final Set<String> ADB_PARTITIONS =
+ ImmutableSet.of("data", "odm", "oem", "product", "system", "system_ext", "vendor");
+
public DeviceSyncHelper(ITestDevice device, File targetFilesFolder) {
mDevice = device;
mTargetFilesFolder = targetFilesFolder;
@@ -62,7 +68,7 @@
private Set<String> getPartitions(File rootFolder) throws IOException {
File abPartitions = new File(rootFolder, "META/ab_partitions.txt");
String partitionString = FileUtil.readStringFromFile(abPartitions);
- return new HashSet<>(Arrays.asList(partitionString.split("\n")));
+ return new LinkedHashSet<>(Arrays.asList(partitionString.split("\n")));
}
private void lowerCaseDirectory(File rootFolder) {
@@ -86,12 +92,28 @@
device.executeAdbCommand("shell", "stop");
RunUtil.getDefault().sleep(20000L);
+ device.setRecoveryMode(RecoveryMode.NONE);
+ // Use adb sync when supported
+ try (CloseableTraceScope push = new CloseableTraceScope("sync all")) {
+ Map<String, String> env = new HashMap<>();
+ env.put("ANDROID_PRODUCT_OUT", mTargetFilesFolder.getAbsolutePath());
+ String output = device.executeAdbCommand(0L, env, "sync", "all");
+ if (output == null) {
+ throw new IOException("Failed to sync all");
+ }
+ CLog.d("%s", output);
+ }
+
for (String partition : partitions) {
File localToPush = new File(mTargetFilesFolder, partition);
if (!localToPush.exists()) {
CLog.w("%s is in the partition but doesn't exist", partition);
continue;
}
+ if (ADB_PARTITIONS.contains(partition)) {
+ continue;
+ }
+ // Push after deleting to ensure shell is fine
try (CloseableTraceScope push = new CloseableTraceScope("push " + partition)) {
String output =
device.executeAdbCommand(0L, "push", localToPush.getAbsolutePath(), "/");
@@ -99,57 +121,15 @@
throw new IOException("Failed to push " + localToPush);
}
}
- try (CloseableTraceScope delete = new CloseableTraceScope("delete_extra_files")) {
- List<String> removeFiles = syncFiles(device, localToPush, "/" + partition);
- CLog.d("Files to be removed from device: %s", removeFiles);
- for (String deviceFile : removeFiles) {
- device.executeShellCommand(String.format("rm -rf %s", deviceFile));
- }
- }
}
try (CloseableTraceScope reboot = new CloseableTraceScope("reboot")) {
- device.executeAdbCommand("reboot");
- device.waitForDeviceAvailable();
+ String output = device.executeAdbCommand("reboot");
+ CLog.d("reboot output: %s", output);
+ device.waitForDeviceNotAvailable(30 * 1000L);
+ device.waitForDeviceAvailable(15 * 60 * 1000);
}
device.enableAdbRoot();
}
- private List<String> syncFiles(ITestDevice device, File localFileDir, String deviceFilePath)
- throws DeviceNotAvailableException {
- CLog.i(
- "Syncing %s to %s on device %s",
- localFileDir.getAbsolutePath(), deviceFilePath, device.getSerialNumber());
- IFileEntry remoteFileEntry = device.getFileEntry(deviceFilePath);
- if (remoteFileEntry == null) {
- CLog.e("Could not find remote file entry %s ", deviceFilePath);
- remoteFileEntry = device.getFileEntry(deviceFilePath);
- if (remoteFileEntry == null) {
- CLog.e(
- "Could not find remote file entry %s a second time. doesExist: %s",
- deviceFilePath, device.doesFileExist(deviceFilePath, 0));
- return new ArrayList<String>();
- }
- }
- return syncFiles(device, localFileDir, remoteFileEntry);
- }
-
- private List<String> syncFiles(
- ITestDevice device, File localFileDir, final IFileEntry remoteFileEntry)
- throws DeviceNotAvailableException {
- // find newer files to sync
- // File[] localFiles = localFileDir.listFiles(new NoHiddenFilesFilter());
- List<String> filePathsToRemove = new ArrayList<>();
- for (IFileEntry entry : remoteFileEntry.getChildren(false)) {
- File local = new File(localFileDir, entry.getName());
- if (!local.exists()) {
- filePathsToRemove.add(entry.getFullPath());
- } else {
- if (local.isDirectory()) {
- filePathsToRemove.addAll(syncFiles(device, local, entry));
- }
- }
- }
- return filePathsToRemove;
- }
}
diff --git a/src/com/android/tradefed/testtype/coverage/CoverageOptions.java b/src/com/android/tradefed/testtype/coverage/CoverageOptions.java
index fbdfd91..60a5bc2 100644
--- a/src/com/android/tradefed/testtype/coverage/CoverageOptions.java
+++ b/src/com/android/tradefed/testtype/coverage/CoverageOptions.java
@@ -80,6 +80,12 @@
+ " \"foo.*\\.profraw\". Default: \".*\\.profraw\"")
private String mProfrawFilter = ".*\\.profraw";
+ @Option(
+ name = "pull-timeout",
+ isTimeVal = true,
+ description = "Timeout in milliseconds to pull coverage metrics from the device.")
+ private long mPullTimeout = 20 * 60 * 1000;
+
/**
* Returns whether coverage measurements should be collected from this run.
*
@@ -150,4 +156,13 @@
public String getProfrawFilter() {
return mProfrawFilter;
}
+
+ /**
+ * Returns the timeout in milliseconds for pulling coverage metrics from the device.
+ *
+ * @return a {@link long} as timeout in milliseconds.
+ */
+ public long getPullTimeout() {
+ return mPullTimeout;
+ }
}
diff --git a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
index dbf9749..46757a7 100644
--- a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
+++ b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
@@ -46,6 +46,7 @@
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestCollector;
import com.android.tradefed.testtype.ITestFilterReceiver;
+import com.android.tradefed.util.StreamUtil;
import com.google.common.annotations.VisibleForTesting;
@@ -98,6 +99,7 @@
// Tracking of the metrics
private RetryStatistics mRetryStats = null;
+ private int mCountRetryUsed = 0;
public GranularRetriableTestWrapper(
IRemoteTest test,
@@ -117,7 +119,11 @@
int maxRunLimit) {
mTest = test;
mModule = module;
- initializeGranularRunListener(mainListener);
+ IInvocationContext context = null;
+ if (module != null) {
+ context = module.getModuleInvocationContext();
+ }
+ initializeGranularRunListener(mainListener, context);
mFailureListener = failureListener;
mModuleLevelListeners = moduleLevelListeners;
mMaxRunLimit = maxRunLimit;
@@ -187,14 +193,14 @@
}
/**
- * Initialize granular run listener with {@link RemoteTestTimeOutEnforcer} if timeout is
- * set.
+ * Initialize granular run listener with {@link RemoteTestTimeOutEnforcer} if timeout is set.
*
* @param listener The listener for each test run should be wrapped.
- *
+ * @param moduleContext the invocation context of the module
*/
- private void initializeGranularRunListener(ITestInvocationListener listener) {
- mMainGranularRunListener = new ModuleListener(listener);
+ private void initializeGranularRunListener(
+ ITestInvocationListener listener, IInvocationContext moduleContext) {
+ mMainGranularRunListener = new ModuleListener(listener, moduleContext);
if (mModule != null) {
ConfigurationDescriptor configDesc =
mModule.getModuleInvocationContext().getConfigurationDescriptor();
@@ -306,6 +312,7 @@
}
}
firstCheck = false;
+ mCountRetryUsed++;
CLog.d("Intra-module retry attempt number %s", attemptNumber);
// Run the tests again
intraModuleRun(testInfo, allListeners, attemptNumber);
@@ -436,6 +443,10 @@
return mMainGranularRunListener;
}
+ public int getRetryCount() {
+ return mCountRetryUsed;
+ }
+
@Override
public void setCollectTestsOnly(boolean shouldCollectTest) {
mCollectTestsOnly = shouldCollectTest;
@@ -444,7 +455,9 @@
private FailureDescription createFromException(Throwable exception) {
String message =
(exception.getMessage() == null)
- ? String.format("No error message reported for: %s", exception)
+ ? String.format(
+ "No error message reported for: %s",
+ StreamUtil.getStackTrace(exception))
: exception.getMessage();
FailureDescription failure =
CurrentInvocation.createFailure(message, null).setCause(exception);
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index f583731d..17d3c4d 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -476,59 +476,73 @@
}
CLog.i(String.format("Start to stage test artifacts for %d modules.", modules.size()));
long startTime = System.currentTimeMillis();
- // Include the file if its path contains a folder name matching any of the module.
- String moduleRegex =
- modules.stream()
- .map(m -> String.format("/%s/", m))
- .collect(Collectors.joining("|"));
- List<String> includeFilters = Arrays.asList(moduleRegex);
- // Ignore config file as it's part of config zip artifact that's staged already.
- List<String> excludeFilters = Arrays.asList("[.]config$");
- if (mStageArtifactsViaFeature) {
- try (TradefedFeatureClient client = new TradefedFeatureClient()) {
- Map<String, String> args = new HashMap<>();
- args.put(ResolvePartialDownload.DESTINATION_DIR, getTestsDir().getAbsolutePath());
- args.put(ResolvePartialDownload.INCLUDE_FILTERS, String.join(";", includeFilters));
- args.put(ResolvePartialDownload.EXCLUDE_FILTERS, String.join(";", excludeFilters));
- // Pass the remote paths
- String remotePaths =
- mBuildInfo.getRemoteFiles().stream()
- .map(p -> p.toString())
- .collect(Collectors.joining(";"));
- args.put(ResolvePartialDownload.REMOTE_PATHS, remotePaths);
+ try (CloseableTraceScope ignored =
+ new CloseableTraceScope(
+ InvocationMetricKey.stage_suite_test_artifacts.toString())) {
+ // Include the file if its path contains a folder name matching any of the module.
+ String moduleRegex =
+ modules.stream()
+ .map(m -> String.format("/%s/", m))
+ .collect(Collectors.joining("|"));
+ List<String> includeFilters = Arrays.asList(moduleRegex);
+ // Ignore config file as it's part of config zip artifact that's staged already.
+ List<String> excludeFilters = Arrays.asList("[.]config$");
+ if (mStageArtifactsViaFeature) {
+ try (TradefedFeatureClient client = new TradefedFeatureClient()) {
+ Map<String, String> args = new HashMap<>();
+ args.put(
+ ResolvePartialDownload.DESTINATION_DIR,
+ getTestsDir().getAbsolutePath());
+ args.put(
+ ResolvePartialDownload.INCLUDE_FILTERS,
+ String.join(";", includeFilters));
+ args.put(
+ ResolvePartialDownload.EXCLUDE_FILTERS,
+ String.join(";", excludeFilters));
+ // Pass the remote paths
+ String remotePaths =
+ mBuildInfo.getRemoteFiles().stream()
+ .map(p -> p.toString())
+ .collect(Collectors.joining(";"));
+ args.put(ResolvePartialDownload.REMOTE_PATHS, remotePaths);
- FeatureResponse rep =
- client.triggerFeature(
- ResolvePartialDownload.RESOLVE_PARTIAL_DOWNLOAD_FEATURE_NAME, args);
- if (rep.hasErrorInfo()) {
- throw new HarnessRuntimeException(
- rep.getErrorInfo().getErrorTrace(),
- InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR);
- }
- } catch (FileNotFoundException e) {
- throw new HarnessRuntimeException(
- e.getMessage(), e, InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR);
- }
- } else {
- mDynamicResolver.setDevice(device);
- mDynamicResolver.addExtraArgs(
- mMainConfiguration.getCommandOptions().getDynamicDownloadArgs());
- for (File remoteFile : mBuildInfo.getRemoteFiles()) {
- try {
- mDynamicResolver.resolvePartialDownloadZip(
- getTestsDir(), remoteFile.toString(), includeFilters, excludeFilters);
- } catch (BuildRetrievalError | FileNotFoundException e) {
- String message =
- String.format(
- "Failed to download partial zip from %s for modules: %s",
- remoteFile, String.join(", ", modules));
- CLog.e(message);
- CLog.e(e);
- if (e instanceof IHarnessException) {
- throw new HarnessRuntimeException(message, (IHarnessException) e);
+ FeatureResponse rep =
+ client.triggerFeature(
+ ResolvePartialDownload.RESOLVE_PARTIAL_DOWNLOAD_FEATURE_NAME,
+ args);
+ if (rep.hasErrorInfo()) {
+ throw new HarnessRuntimeException(
+ rep.getErrorInfo().getErrorTrace(),
+ InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR);
}
+ } catch (FileNotFoundException e) {
throw new HarnessRuntimeException(
- message, e, InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR);
+ e.getMessage(), e, InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR);
+ }
+ } else {
+ mDynamicResolver.setDevice(device);
+ mDynamicResolver.addExtraArgs(
+ mMainConfiguration.getCommandOptions().getDynamicDownloadArgs());
+ for (File remoteFile : mBuildInfo.getRemoteFiles()) {
+ try {
+ mDynamicResolver.resolvePartialDownloadZip(
+ getTestsDir(),
+ remoteFile.toString(),
+ includeFilters,
+ excludeFilters);
+ } catch (BuildRetrievalError | FileNotFoundException e) {
+ String message =
+ String.format(
+ "Failed to download partial zip from %s for modules: %s",
+ remoteFile, String.join(", ", modules));
+ CLog.e(message);
+ CLog.e(e);
+ if (e instanceof IHarnessException) {
+ throw new HarnessRuntimeException(message, (IHarnessException) e);
+ }
+ throw new HarnessRuntimeException(
+ message, e, InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR);
+ }
}
}
}
@@ -983,6 +997,8 @@
// We report System checkers like tests.
reportModuleCheckerResult(MODULE_CHECKER_PRE, moduleName, failures, startTime, listener);
+ InvocationMetricLogger.addInvocationPairMetrics(
+ InvocationMetricKey.STATUS_CHECKER_PAIR, startTime, System.currentTimeMillis());
return properties;
}
@@ -1044,6 +1060,8 @@
// We report System checkers like tests.
reportModuleCheckerResult(MODULE_CHECKER_POST, moduleName, failures, startTime, listener);
+ InvocationMetricLogger.addInvocationPairMetrics(
+ InvocationMetricKey.STATUS_CHECKER_PAIR, startTime, System.currentTimeMillis());
return properties;
}
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index 0475be3..a419a3e 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -132,6 +132,7 @@
public static final String TEST_TIME = "TEST_TIME";
public static final String MODULE_TEST_COUNT = "MODULE_TEST_COUNT";
public static final String RETRY_TIME = "MODULE_RETRY_TIME";
+ public static final String ISOLATION_COST = "ISOLATION_COST";
public static final String RETRY_SUCCESS_COUNT = "MODULE_RETRY_SUCCESS";
public static final String RETRY_FAIL_COUNT = "MODULE_RETRY_FAILED";
@@ -515,6 +516,7 @@
// Run the tests
try {
mStartTestTime = getCurrentTime();
+ int perModuleRetryQuota = mMaxRetry;
while (true) {
IRemoteTest test = poll();
if (test == null) {
@@ -563,7 +565,7 @@
failureListener,
moduleLevelListeners,
skipTestCases,
- maxRunLimit);
+ perModuleRetryQuota);
mCurrentTestWrapper.setCollectTestsOnly(mCollectTestsOnly);
// Resolve the dynamic options for that one test.
preparationException =
@@ -605,6 +607,8 @@
// Keep track of each listener for attempts
mRunListenersResults.add(mCurrentTestWrapper.getResultListener());
}
+ // Limit escalating retries across all sub-IRemoteTests
+ perModuleRetryQuota -= mCurrentTestWrapper.getRetryCount();
mExpectedTests += mCurrentTestWrapper.getExpectedTestsCount();
// Get information about retry
@@ -811,15 +815,25 @@
metricsProto.put(MODULE_TEST_COUNT, TfMetricProtoUtil.createSingleValue(numResults, "int"));
// Report all the retry informations
if (!mRetryStats.isEmpty()) {
- RetryStatistics agg = RetryStatistics.aggregateStatistics(mRetryStats);
- metricsProto.put(
- RETRY_TIME,
- TfMetricProtoUtil.createSingleValue(agg.mRetryTime, "milliseconds"));
- metricsProto.put(
- RETRY_SUCCESS_COUNT,
- TfMetricProtoUtil.createSingleValue(agg.mRetrySuccess, ""));
- metricsProto.put(
- RETRY_FAIL_COUNT, TfMetricProtoUtil.createSingleValue(agg.mRetryFailure, ""));
+ if (attempt != null) {
+ long cost = RetryStatistics.isolationCostPerAttempt(attempt, mRetryStats);
+ if (cost != 0L) {
+ metricsProto.put(
+ ISOLATION_COST,
+ TfMetricProtoUtil.createSingleValue(cost, "milliseconds"));
+ }
+ } else {
+ RetryStatistics agg = RetryStatistics.aggregateStatistics(mRetryStats);
+ metricsProto.put(
+ RETRY_TIME,
+ TfMetricProtoUtil.createSingleValue(agg.mRetryTime, "milliseconds"));
+ metricsProto.put(
+ RETRY_SUCCESS_COUNT,
+ TfMetricProtoUtil.createSingleValue(agg.mRetrySuccess, ""));
+ metricsProto.put(
+ RETRY_FAIL_COUNT,
+ TfMetricProtoUtil.createSingleValue(agg.mRetryFailure, ""));
+ }
}
// Only report the mismatch if there were no error during the run.
diff --git a/src/com/android/tradefed/testtype/suite/ModuleListener.java b/src/com/android/tradefed/testtype/suite/ModuleListener.java
index 55e5e3e..5adf63a 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleListener.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleListener.java
@@ -17,6 +17,7 @@
import com.android.ddmlib.Log.LogLevel;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.logger.CurrentInvocation.IsolationGrade;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
@@ -44,7 +45,8 @@
private TestStatus mTestStatus;
private String mTrace;
private int mTestsRan = 1;
- private ITestInvocationListener mMainListener;
+ private final ITestInvocationListener mMainListener;
+ private final IInvocationContext mModuleContext;
private boolean mCollectTestsOnly = false;
/** Track runs in progress for logging purpose */
@@ -53,8 +55,9 @@
private IsolationGrade mAttemptIsolation = IsolationGrade.NOT_ISOLATED;
/** Constructor. */
- public ModuleListener(ITestInvocationListener listener) {
+ public ModuleListener(ITestInvocationListener listener, IInvocationContext moduleContext) {
mMainListener = listener;
+ mModuleContext = moduleContext;
mRunInProgress = false;
setIsAggregrateMetrics(true);
}
@@ -87,13 +90,15 @@
if (attemptNumber != 0) {
mTestsRan = 1;
}
- CLog.d("ModuleListener.testRunStarted(%s, %s, %s)", name, numTests, attemptNumber);
+ CLog.d(
+ "ModuleListener.testRunStarted(%s, %s, %s) on %s",
+ name, numTests, attemptNumber, getSerial());
}
/** {@inheritDoc} */
@Override
public void testRunFailed(String errorMessage) {
- CLog.d("ModuleListener.testRunFailed(%s)", errorMessage);
+ CLog.d("ModuleListener.testRunFailed(%s) on %s", errorMessage, getSerial());
super.testRunFailed(errorMessage);
}
@@ -101,17 +106,18 @@
@Override
public void testRunFailed(FailureDescription failure) {
CLog.d(
- "ModuleListener.testRunFailed(%s|%s|%s)",
+ "ModuleListener.testRunFailed(%s|%s|%s) on %s",
failure.getFailureStatus(),
failure.getErrorIdentifier(),
- failure.getErrorMessage());
+ failure.getErrorMessage(),
+ getSerial());
super.testRunFailed(failure);
}
/** {@inheritDoc} */
@Override
public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
- CLog.d("ModuleListener.testRunEnded(%s)", elapsedTime);
+ CLog.d("ModuleListener.testRunEnded(%s) on %s", elapsedTime, getSerial());
if (!IsolationGrade.NOT_ISOLATED.equals(mAttemptIsolation)) {
runMetrics.put(
@@ -133,7 +139,7 @@
@Override
public void testStarted(TestDescription test, long startTime) {
if (!mCollectTestsOnly) {
- CLog.d("ModuleListener.testStarted(%s)", test.toString());
+ CLog.d("ModuleListener.testStarted(%s) on %s", test.toString(), getSerial());
}
mTestStatus = TestStatus.PASSED;
mTrace = null;
@@ -155,7 +161,8 @@
String runAndTestCase = String.format("%s%s", runName, testName.toString());
String message =
String.format(
- "[%d/%d] %s %s", mTestsRan, getExpectedTests(), runAndTestCase, status);
+ "[%d/%d] %s %s %s",
+ mTestsRan, getExpectedTests(), getSerial(), runAndTestCase, status);
if (mTrace != null) {
message += ": " + mTrace;
}
@@ -261,4 +268,11 @@
}
}
}
+
+ private String getSerial() {
+ if (mModuleContext == null || mModuleContext.getDevices().isEmpty()) {
+ return "";
+ }
+ return mModuleContext.getDevices().get(0).getSerialNumber();
+ }
}
diff --git a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
index 3690770..a1d599e 100644
--- a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
+++ b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
@@ -20,17 +20,20 @@
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.error.HarnessRuntimeException;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.ZipUtil2;
import com.android.tradefed.util.testmapping.TestInfo;
import com.android.tradefed.util.testmapping.TestMapping;
import com.android.tradefed.util.testmapping.TestOption;
-import com.android.tradefed.util.ZipUtil2;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Joiner;
import com.google.common.io.Files;
import org.apache.commons.compress.archivers.zip.ZipFile;
@@ -41,6 +44,7 @@
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -287,7 +291,6 @@
IAbi abi = configDescriptor.getAbi();
// Get the parameterized module name by striping the abi information out.
String moduleName = entry.getKey().replace(String.format("%s ", abi.getName()), "");
- String configPath = moduleConfig.getName();
Set<TestInfo> testInfos = getTestInfos(testInfosToRun, moduleName);
// Only keep the same matching abi runner
allTests.addAll(createIndividualTests(testInfos, moduleConfig, abi));
@@ -352,7 +355,8 @@
configFile = null;
}
// De-duplicate test infos so that there won't be duplicate test options.
- testInfos = dedupTestInfos(testInfos);
+ testInfos = dedupTestInfos(configFile, testInfos);
+
for (TestInfo testInfo : testInfos) {
// Clean up all the test options injected in SuiteModuleLoader.
super.cleanUpSuiteSetup();
@@ -455,26 +459,52 @@
}
/**
- * De-duplicate test infos with the same test options.
+ * De-duplicate test infos and aggregate test-mapping sources with the same test options.
*
+ * @param config the config file being deduplicated
* @param testInfos A {@code Set<TestInfo>} containing multiple test options.
* @return A {@code Set<TestInfo>} of tests without duplicated test options.
*/
@VisibleForTesting
- Set<TestInfo> dedupTestInfos(Set<TestInfo> testInfos) {
+ Set<TestInfo> dedupTestInfos(File config, Set<TestInfo> testInfos) {
Set<String> nameOptions = new HashSet<>();
Set<TestInfo> dedupTestInfos = new HashSet<>();
+ Set<String> duplicateSources = new LinkedHashSet<String>();
for (TestInfo testInfo : testInfos) {
- String nameOption = testInfo.getName() + testInfo.getOptions().toString();
+ String nameOption = testInfo.getNameOption();
if (!nameOptions.contains(nameOption)) {
dedupTestInfos.add(testInfo);
+ duplicateSources.addAll(testInfo.getSources());
nameOptions.add(nameOption);
+ } else {
+ aggregateTestInfo(testInfo, dedupTestInfos);
}
}
+
+ // If size above 1 that means we have duplicated modules with different options
+ if (dedupTestInfos.size() > 1) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.DUPLICATE_MAPPING_DIFFERENT_OPTIONS,
+ String.format("%s:" + Joiner.on("+").join(duplicateSources), config));
+ }
return dedupTestInfos;
}
/**
+ * Aggregate test-mapping sources of the test info with the same test options
+ *
+ * @param testInfo A {@code TestInfo} of duplicated test to be aggregated.
+ * @param dedupTestInfos A {@code Set<TestInfo>} of tests without duplicated test options.
+ */
+ private void aggregateTestInfo(TestInfo testInfo, Set<TestInfo> dedupTestInfos) {
+ for (TestInfo dedupTestInfo : dedupTestInfos) {
+ if (testInfo.getNameOption().equals(dedupTestInfo.getNameOption())) {
+ dedupTestInfo.addSources(testInfo.getSources());
+ }
+ }
+ }
+
+ /**
* Get the test infos for the given module name.
*
* @param testInfos A {@code Set<TestInfo>} containing multiple test options.
diff --git a/src/com/android/tradefed/testtype/suite/params/ModuleParameters.java b/src/com/android/tradefed/testtype/suite/params/ModuleParameters.java
index 1f8d666..be91ba7 100644
--- a/src/com/android/tradefed/testtype/suite/params/ModuleParameters.java
+++ b/src/com/android/tradefed/testtype/suite/params/ModuleParameters.java
@@ -18,33 +18,56 @@
/** Special values associated with the suite "parameter" keys in the metadata of each module. */
public enum ModuleParameters {
/** describes a parameterization based on app that should be installed in instant mode. */
- INSTANT_APP("instant_app", "instant_app_family"),
- NOT_INSTANT_APP("not_instant_app", "instant_app_family"),
+ INSTANT_APP("instant_app", ModuleParameters.INSTANT_APP_FAMILY),
+ NOT_INSTANT_APP("not_instant_app", ModuleParameters.INSTANT_APP_FAMILY),
- MULTI_ABI("multi_abi", "multi_abi_family"),
- NOT_MULTI_ABI("not_multi_abi", "multi_abi_family"),
+ MULTI_ABI("multi_abi", ModuleParameters.MULTI_ABI_FAMILY),
+ NOT_MULTI_ABI("not_multi_abi", ModuleParameters.MULTI_ABI_FAMILY),
- SECONDARY_USER("secondary_user", "secondary_user_family"),
- NOT_SECONDARY_USER("not_secondary_user", "secondary_user_family"),
+ SECONDARY_USER("secondary_user", ModuleParameters.SECONDARY_USER_FAMILY),
+ NOT_SECONDARY_USER("not_secondary_user", ModuleParameters.SECONDARY_USER_FAMILY),
+
+ // Secondary user started on background, visible in a secondary display
+ SECONDARY_USER_ON_SECONDARY_DISPLAY(
+ "secondary_user_on_secondary_display",
+ ModuleParameters.SECONDARY_USER_ON_SECONDARY_DISPLAY_FAMILY),
+ NOT_SECONDARY_USER_ON_SECONDARY_DISPLAY(
+ "not_secondary_user_on_secondary_display",
+ ModuleParameters.SECONDARY_USER_ON_SECONDARY_DISPLAY_FAMILY),
+
+ // Secondary user started on background, visible in the default display
+ SECONDARY_USER_ON_DEFAULT_DISPLAY(
+ "secondary_user_on_defauilt_display",
+ ModuleParameters.SECONDARY_USER_ON_DEFAULT_DISPLAY_FAMILY),
+ NOT_SECONDARY_USER_ON_DEFAULT_DISPLAY(
+ "not_secondary_user_on_default_display",
+ ModuleParameters.SECONDARY_USER_ON_DEFAULT_DISPLAY_FAMILY),
// Multi-user
- MULTIUSER("multiuser", "multiuser_family"),
- RUN_ON_WORK_PROFILE("run_on_work_profile", "run_on_work_profile_family"),
- RUN_ON_SECONDARY_USER("run_on_secondary_user", "run_on_secondary_user_family"),
+ MULTIUSER("multiuser", ModuleParameters.MULTIUSER_FAMILY),
+ RUN_ON_WORK_PROFILE("run_on_work_profile", ModuleParameters.RUN_ON_WORK_PROFILE_FAMILY),
+ RUN_ON_SECONDARY_USER("run_on_secondary_user", ModuleParameters.RUN_ON_SECONDARY_USER_FAMILY),
// Foldable mode
- ALL_FOLDABLE_STATES("all_foldable_states", "foldable_family"),
- NO_FOLDABLE_STATES("no_foldable_states", "foldable_family"),
+ ALL_FOLDABLE_STATES("all_foldable_states", ModuleParameters.FOLDABLE_STATES_FAMILY),
+ NO_FOLDABLE_STATES("no_foldable_states", ModuleParameters.FOLDABLE_STATES_FAMILY),
// SDK sandbox mode
- RUN_ON_SDK_SANDBOX("run_on_sdk_sandbox", "run_on_sdk_sandbox_family"),
- NOT_RUN_ON_SDK_SANDBOX("not_run_on_sdk_sandbox", "run_on_sdk_sandbox_family");
+ RUN_ON_SDK_SANDBOX("run_on_sdk_sandbox", ModuleParameters.RUN_ON_SDK_SANDBOX_FAMILY),
+ NOT_RUN_ON_SDK_SANDBOX("not_run_on_sdk_sandbox", ModuleParameters.RUN_ON_SDK_SANDBOX_FAMILY);
public static final String INSTANT_APP_FAMILY = "instant_app_family";
public static final String MULTI_ABI_FAMILY = "multi_abi_family";
public static final String SECONDARY_USER_FAMILY = "secondary_user_family";
+ public static final String SECONDARY_USER_ON_SECONDARY_DISPLAY_FAMILY =
+ "secondary_user_on_secondary_display_family";
+ public static final String SECONDARY_USER_ON_DEFAULT_DISPLAY_FAMILY =
+ "secondary_user_on_default_display_family";
public static final String MULTIUSER_FAMILY = "multiuser_family";
public static final String FOLDABLE_STATES_FAMILY = "foldable_family";
+ public static final String RUN_ON_SDK_SANDBOX_FAMILY = "run_on_sdk_sandbox_family";
+ public static final String RUN_ON_WORK_PROFILE_FAMILY = "run_on_work_profile_family";
+ public static final String RUN_ON_SECONDARY_USER_FAMILY = "run_on_secondary_user_family";
private final String mName;
/** Defines whether several module parameters are associated and mutually exclusive. */
diff --git a/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelper.java b/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelper.java
index f0fd57e..d885633 100644
--- a/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelper.java
+++ b/src/com/android/tradefed/testtype/suite/params/ModuleParametersHelper.java
@@ -15,43 +15,51 @@
*/
package com.android.tradefed.testtype.suite.params;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.ALL_FOLDABLE_STATES;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.INSTANT_APP;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.MULTIUSER;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.MULTI_ABI;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.NOT_INSTANT_APP;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.NOT_MULTI_ABI;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.NOT_RUN_ON_SDK_SANDBOX;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.NOT_SECONDARY_USER;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.NOT_SECONDARY_USER_ON_DEFAULT_DISPLAY;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.NOT_SECONDARY_USER_ON_SECONDARY_DISPLAY;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.NO_FOLDABLE_STATES;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.RUN_ON_SDK_SANDBOX;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.RUN_ON_SECONDARY_USER;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.RUN_ON_WORK_PROFILE;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.SECONDARY_USER;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.SECONDARY_USER_ON_DEFAULT_DISPLAY;
+import static com.android.tradefed.testtype.suite.params.ModuleParameters.SECONDARY_USER_ON_SECONDARY_DISPLAY;
+
import com.android.tradefed.testtype.suite.params.multiuser.RunOnSecondaryUserParameterHandler;
import com.android.tradefed.testtype.suite.params.multiuser.RunOnWorkProfileParameterHandler;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/** Helper to get the {@link IModuleParameterHandler} associated with the parameter. */
-public class ModuleParametersHelper {
+public final class ModuleParametersHelper {
- private static Map<ModuleParameters, IModuleParameterHandler> sHandlerMap = new HashMap<>();
+ private static final Map<ModuleParameters, IModuleParameterHandler> sHandlerMap =
+ Map.of(
+ INSTANT_APP, new InstantAppHandler(),
+ NOT_INSTANT_APP, new NegativeHandler(),
+ // line separator
+ MULTI_ABI, new NegativeHandler(),
+ NOT_MULTI_ABI, new NotMultiAbiHandler(),
+ // line separator
+ RUN_ON_WORK_PROFILE, new RunOnWorkProfileParameterHandler(),
+ RUN_ON_SECONDARY_USER, new RunOnSecondaryUserParameterHandler(),
+ // line separator
+ NO_FOLDABLE_STATES, new NegativeHandler(),
+ ALL_FOLDABLE_STATES, new FoldableExpandingHandler());
- static {
- sHandlerMap.put(ModuleParameters.INSTANT_APP, new InstantAppHandler());
- sHandlerMap.put(ModuleParameters.NOT_INSTANT_APP, new NegativeHandler());
-
- sHandlerMap.put(ModuleParameters.MULTI_ABI, new NegativeHandler());
- sHandlerMap.put(ModuleParameters.NOT_MULTI_ABI, new NotMultiAbiHandler());
-
- sHandlerMap.put(
- ModuleParameters.RUN_ON_WORK_PROFILE, new RunOnWorkProfileParameterHandler());
- sHandlerMap.put(
- ModuleParameters.RUN_ON_SECONDARY_USER, new RunOnSecondaryUserParameterHandler());
-
- sHandlerMap.put(ModuleParameters.NO_FOLDABLE_STATES, new NegativeHandler());
- sHandlerMap.put(ModuleParameters.ALL_FOLDABLE_STATES, new FoldableExpandingHandler());
- }
-
- private static Map<ModuleParameters, Set<ModuleParameters>> sGroupMap = new HashMap<>();
-
- static {
- sGroupMap.put(
- ModuleParameters.MULTIUSER,
- Set.of(
- ModuleParameters.RUN_ON_WORK_PROFILE,
- ModuleParameters.RUN_ON_SECONDARY_USER));
- }
+ private static final Map<ModuleParameters, Set<ModuleParameters>> sGroupMap =
+ Map.of(MULTIUSER, Set.of(RUN_ON_WORK_PROFILE, RUN_ON_SECONDARY_USER));
/**
* Optional parameters are params that will not automatically be created when the module
@@ -59,19 +67,28 @@
* set of parameterization that is less commonly requested to run. They could be upgraded to
* main parameters in the future by moving them above.
*/
- private static Map<ModuleParameters, IModuleParameterHandler> sOptionalHandlerMap = new HashMap<>();
+ private static final Map<ModuleParameters, IModuleParameterHandler> sOptionalHandlerMap =
+ Map.of(
+ SECONDARY_USER,
+ new SecondaryUserHandler(),
+ NOT_SECONDARY_USER,
+ new NegativeHandler(),
+ SECONDARY_USER_ON_SECONDARY_DISPLAY,
+ new SecondaryUserOnSecondaryDisplayHandler(),
+ NOT_SECONDARY_USER_ON_SECONDARY_DISPLAY,
+ new NegativeHandler(),
+ SECONDARY_USER_ON_DEFAULT_DISPLAY,
+ new SecondaryUserOnDefaultDisplayHandler(),
+ NOT_SECONDARY_USER_ON_DEFAULT_DISPLAY,
+ new NegativeHandler(),
+ RUN_ON_SDK_SANDBOX,
+ new RunOnSdkSandboxHandler(),
+ NOT_RUN_ON_SDK_SANDBOX,
+ new NegativeHandler());
- static {
- sOptionalHandlerMap.put(ModuleParameters.SECONDARY_USER, new SecondaryUserHandler());
- sOptionalHandlerMap.put(ModuleParameters.NOT_SECONDARY_USER, new NegativeHandler());
- sOptionalHandlerMap.put(ModuleParameters.RUN_ON_SDK_SANDBOX, new RunOnSdkSandboxHandler());
- sOptionalHandlerMap.put(ModuleParameters.NOT_RUN_ON_SDK_SANDBOX, new NegativeHandler());
- }
-
- private static Map<ModuleParameters, Set<ModuleParameters>> sOptionalGroupMap = new HashMap<>();
-
- static {
- }
+ // NOTE: sOptionalGroupMap is currently empty, but used on resolveParam(), so don't remove it
+ private static Map<ModuleParameters, Set<ModuleParameters>> sOptionalGroupMap =
+ Collections.emptyMap();
/**
* Get the all {@link ModuleParameters} which are sub-params of a given {@link
diff --git a/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java b/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java
index 7684b1b..cd821d6 100644
--- a/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java
+++ b/src/com/android/tradefed/testtype/suite/params/SecondaryUserHandler.java
@@ -20,34 +20,73 @@
import com.android.tradefed.targetprep.CreateUserPreparer;
import com.android.tradefed.targetprep.ITargetPreparer;
import com.android.tradefed.targetprep.RunCommandTargetPreparer;
+import com.android.tradefed.targetprep.VisibleBackgroundUserPreparer;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestAnnotationFilterReceiver;
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import javax.annotation.Nullable;
+
/** Handler for {@link ModuleParameters#SECONDARY_USER}. */
public class SecondaryUserHandler implements IModuleParameterHandler {
+
+ @VisibleForTesting
+ static final List<String> LOCATION_COMMANDS =
+ Arrays.asList(
+ "settings put secure location_providers_allowed +network",
+ "settings put secure location_providers_allowed +gps");
+
+ private final boolean mStartUserVisibleOnBackground;
+ private final @Nullable Integer mDisplayId;
+
+ public SecondaryUserHandler() {
+ this(/*startUserVisibleOnBackground= */ false);
+ }
+
+ protected SecondaryUserHandler(boolean startUserVisibleOnBackground) {
+ this(startUserVisibleOnBackground, /* displayId= */ null);
+ }
+
+ protected SecondaryUserHandler(boolean startUserVisibleOnBackground, Integer displayId) {
+ mStartUserVisibleOnBackground = startUserVisibleOnBackground;
+ mDisplayId = displayId;
+ }
+
@Override
public String getParameterIdentifier() {
return "secondary_user";
}
- /** {@inheritDoc} */
@Override
- public void addParameterSpecificConfig(IConfiguration moduleConfiguration) {
+ public final void addParameterSpecificConfig(IConfiguration moduleConfiguration) {
for (IDeviceConfiguration deviceConfig : moduleConfiguration.getDeviceConfig()) {
List<ITargetPreparer> preparers = deviceConfig.getTargetPreparers();
// The first things module will do is switch to a secondary user
- preparers.add(0, new CreateUserPreparer());
+ ITargetPreparer userPreparer;
+ if (mStartUserVisibleOnBackground) {
+ userPreparer = new VisibleBackgroundUserPreparer();
+ if (mDisplayId != null) {
+ ((VisibleBackgroundUserPreparer) userPreparer).setDisplayId(mDisplayId);
+ }
+ } else {
+ userPreparer = new CreateUserPreparer();
+ }
+ preparers.add(0, userPreparer);
// Add a preparer to setup the location settings on the new user
- preparers.add(1, createLocationPreparer());
+ RunCommandTargetPreparer locationPreparer = new RunCommandTargetPreparer();
+ LOCATION_COMMANDS.forEach(cmd -> locationPreparer.addRunCommand(cmd));
+ preparers.add(1, locationPreparer);
}
}
@Override
- public void applySetup(IConfiguration moduleConfiguration) {
+ public final void applySetup(IConfiguration moduleConfiguration) {
// Add filter to exclude @SystemUserOnly
for (IRemoteTest test : moduleConfiguration.getTests()) {
if (test instanceof ITestAnnotationFilterReceiver) {
@@ -63,11 +102,4 @@
}
}
}
-
- private RunCommandTargetPreparer createLocationPreparer() {
- RunCommandTargetPreparer location = new RunCommandTargetPreparer();
- location.addRunCommand("settings put secure location_providers_allowed +gps");
- location.addRunCommand("settings put secure location_providers_allowed +network");
- return location;
- }
}
diff --git a/src/com/android/tradefed/testtype/suite/params/SecondaryUserOnDefaultDisplayHandler.java b/src/com/android/tradefed/testtype/suite/params/SecondaryUserOnDefaultDisplayHandler.java
new file mode 100644
index 0000000..952de95
--- /dev/null
+++ b/src/com/android/tradefed/testtype/suite/params/SecondaryUserOnDefaultDisplayHandler.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2023 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.tradefed.testtype.suite.params;
+
+import com.android.tradefed.targetprep.VisibleBackgroundUserPreparer;
+
+/** Handler for {@link ModuleParameters#SECONDARY_USER_ON_DEFAULT_DISPLAY}. */
+public final class SecondaryUserOnDefaultDisplayHandler extends SecondaryUserHandler {
+
+ public SecondaryUserOnDefaultDisplayHandler() {
+ super(
+ /*startUserVisibleOnBackground= */ true,
+ VisibleBackgroundUserPreparer.DEFAULT_DISPLAY);
+ }
+
+ @Override
+ public String getParameterIdentifier() {
+ return "secondary_user_on_default_display";
+ }
+}
diff --git a/src/com/android/tradefed/testtype/suite/params/SecondaryUserOnSecondaryDisplayHandler.java b/src/com/android/tradefed/testtype/suite/params/SecondaryUserOnSecondaryDisplayHandler.java
new file mode 100644
index 0000000..a12d605
--- /dev/null
+++ b/src/com/android/tradefed/testtype/suite/params/SecondaryUserOnSecondaryDisplayHandler.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 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.tradefed.testtype.suite.params;
+
+/** Handler for {@link ModuleParameters#SECONDARY_USER_ON_SECONDARY_DISPLAY}. */
+public final class SecondaryUserOnSecondaryDisplayHandler extends SecondaryUserHandler {
+
+ public SecondaryUserOnSecondaryDisplayHandler() {
+ super(/*startUserVisibleOnBackground= */ true);
+ }
+
+ @Override
+ public String getParameterIdentifier() {
+ return "secondary_user_on_secondary_display";
+ }
+}
diff --git a/src/com/android/tradefed/util/BuildTestsZipUtils.java b/src/com/android/tradefed/util/BuildTestsZipUtils.java
index ae33b8a..63ab25b 100644
--- a/src/com/android/tradefed/util/BuildTestsZipUtils.java
+++ b/src/com/android/tradefed/util/BuildTestsZipUtils.java
@@ -17,6 +17,8 @@
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.targetprep.AltDirBehavior;
import java.io.File;
@@ -146,6 +148,8 @@
if (testsDir != null) {
File apkFile = buildInfo.stageRemoteFile(apkFileName, testsDir);
if (apkFile != null) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.STAGE_UNDEFINED_DEPENDENCY, apkFileName);
return apkFile;
}
}
diff --git a/src/com/android/tradefed/util/SubprocessTestResultsParser.java b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
index 7a1d697..6e4ef15 100644
--- a/src/com/android/tradefed/util/SubprocessTestResultsParser.java
+++ b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
@@ -416,7 +416,7 @@
TestRunEndedEventInfo rei = new TestRunEndedEventInfo(new JSONObject(eventJson));
// TODO: Parse directly as proto.
mListener.testRunEnded(
- rei.mTime, TfMetricProtoUtil.upgradeConvert(rei.mRunMetrics));
+ rei.mTime, TfMetricProtoUtil.upgradeConvert(rei.mRunMetrics, true));
} finally {
mCurrentRunName = null;
mCurrentTestCase = null;
diff --git a/src/com/android/tradefed/util/testmapping/TestInfo.java b/src/com/android/tradefed/util/testmapping/TestInfo.java
index 5e8b8a5..a280b37 100644
--- a/src/com/android/tradefed/util/testmapping/TestInfo.java
+++ b/src/com/android/tradefed/util/testmapping/TestInfo.java
@@ -84,6 +84,11 @@
return String.format("%s - %s", mName, mHostOnly);
}
+ /** Get a {@link String} represent the test name and its options. */
+ public String getNameOption() {
+ return String.format("%s%s", mName, mOptions.toString());
+ }
+
/** Get a {@link Set} of the keywords supported by the test. */
public Set<String> getKeywords() {
return new HashSet<>(mKeywords);
diff --git a/test_framework/Android.bp b/test_framework/Android.bp
index 14525d0..3a46381 100644
--- a/test_framework/Android.bp
+++ b/test_framework/Android.bp
@@ -37,4 +37,6 @@
"tradefed-lib-core",
"loganalysis",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
}
diff --git a/test_framework/com/android/tradefed/device/metric/BluetoothHciSnoopLogCollector.java b/test_framework/com/android/tradefed/device/metric/BluetoothHciSnoopLogCollector.java
new file mode 100644
index 0000000..3ef9f09
--- /dev/null
+++ b/test_framework/com/android/tradefed/device/metric/BluetoothHciSnoopLogCollector.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2023 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.tradefed.device.metric;
+
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.DeviceRuntimeException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.FileInputStreamSource;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
+import com.android.tradefed.util.BluetoothUtils;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.util.Map;
+
+/**
+ * Collector to enable Bluetooth HCI snoop logging on the DUT and to collect the log for each test.
+ * The collector will configure and enable snoop logging for the test run and revert the settings
+ * after the test run.
+ */
+@OptionClass(alias = "bluetooth-hci-snoop-log-collector")
+public class BluetoothHciSnoopLogCollector extends FilePullerDeviceMetricCollector {
+
+ // Settings for HCI-snoop-log reporting.
+ private String reportingDir = null;
+ public static final String SNOOP_LOG_MODE_PROPERTY = "persist.bluetooth.btsnooplogmode";
+ private String initialSnoopLogMode = null;
+ // Snoop-log-file header is defined in:
+ // https://cs.android.com/android/platform/superproject/+/master:packages/modules/Bluetooth/system/gd/hal/snoop_logger_common.h
+ private static final int SNOOP_LOG_FILE_HEADER_BYTE_SIZE = 16;
+
+ @Override
+ public void onTestRunStart(DeviceMetricData runData) throws DeviceNotAvailableException {
+ // Remember the initial snoop-log mode on the device.
+ initialSnoopLogMode = getSnoopLogModeProperty();
+ // Enable snoop logging on device.
+ setSnoopLogModeProperty("full");
+ for (ITestDevice device : getRealDevices()) {
+ // Restart Bluetooth service, to allow the snoop-log setting to take effect.
+ disableBluetoothService(device);
+ enableBluetoothService(device);
+ }
+ }
+
+ @Override
+ public void onTestRunEnd(DeviceMetricData runData, final Map<String, Metric> currentRunMetrics)
+ throws DeviceNotAvailableException {
+ // Disable snoop logging on device.
+ setSnoopLogModeProperty(initialSnoopLogMode);
+ for (ITestDevice device : getRealDevices()) {
+ // Wind down Bluetooth service after testing.
+ disableBluetoothService(device);
+ }
+ }
+
+ @Override
+ public void onTestStart(DeviceMetricData testData) throws DeviceNotAvailableException {
+ // Create the reporting directory for test snoop log.
+ deleteReportingDirectory();
+ createReportingDirectory();
+ for (ITestDevice device : getRealDevices()) {
+ // Get a clean slate on the snoop log for the test by only preserving header.
+ executeShellCommand(
+ device,
+ String.format(
+ "truncate -s %s %s",
+ SNOOP_LOG_FILE_HEADER_BYTE_SIZE, BluetoothUtils.GOLD_BTSNOOP_LOG_PATH));
+ }
+ }
+
+ @Override
+ public void onTestEnd(
+ DeviceMetricData testData,
+ final Map<String, Metric> currentTestCaseMetrics,
+ TestDescription test)
+ throws DeviceNotAvailableException {
+ // Saving HCI snoop logs for the test.
+ String testName = test.toString();
+ String normalisedTestName = normaliseTestName(testName);
+
+ for (ITestDevice device : getRealDevices()) {
+ String serialNumber = device.getSerialNumber();
+ String testSnoopLogFilename =
+ String.format(getHciSnoopLogPathFormat(), normalisedTestName, serialNumber);
+ executeShellCommand(
+ device,
+ String.format(
+ "cp -p %s %s",
+ BluetoothUtils.GOLD_BTSNOOP_LOG_PATH, testSnoopLogFilename));
+ }
+
+ super.onTestEnd(testData, currentTestCaseMetrics);
+ }
+
+ @Override
+ public final void processMetricFile(String key, File metricFile, DeviceMetricData runData) {
+ try (InputStreamSource source = new FileInputStreamSource(metricFile, true)) {
+ testLog(FileUtil.getBaseName(metricFile.getName()), LogDataType.BT_SNOOP_LOG, source);
+ }
+ }
+
+ // From
+ // tools/tradefederation/core/src/com/android/tradefed/device/metric/FilePullerLogCollector.java
+ @Override
+ public void processMetricDirectory(String key, File metricDirectory, DeviceMetricData runData) {
+ if (metricDirectory.listFiles() == null) {
+ CLog.e("metricDirectory.listFiles() is null.");
+ return;
+ }
+ for (File file : metricDirectory.listFiles()) {
+ if (file.isDirectory()) {
+ processMetricDirectory(key, file, runData);
+ } else {
+ processMetricFile(key, file, runData);
+ }
+ }
+ FileUtil.recursiveDelete(metricDirectory);
+ }
+
+ /** Retrieve the directory to report the HCI snoop logs to. */
+ public String getReportingDir() {
+ if (reportingDir == null) {
+ if (mDirectoryKeys.size() == 0) {
+ CLog.w("No directory key set.");
+ } else if (mDirectoryKeys.size() > 1) {
+ CLog.w("%s directory keys were set.", mDirectoryKeys.size());
+ }
+ // Assume that the first directory key contains the location to store the HCI snoop
+ // logs.
+ reportingDir = mDirectoryKeys.iterator().next();
+ }
+ return reportingDir;
+ }
+
+ /** Construct a filename path for HCI snoop logs, to be tagged with test name and device id. */
+ private String getHciSnoopLogPathFormat() throws DeviceNotAvailableException {
+ return getReportingDir() + "/%s-%s-btsnoop_hci.log";
+ }
+
+ private void createReportingDirectory() throws DeviceNotAvailableException {
+ for (ITestDevice device : getRealDevices()) {
+ executeShellCommand(device, "mkdir -p " + getReportingDir());
+ }
+ }
+
+ private void deleteReportingDirectory() throws DeviceNotAvailableException {
+ for (ITestDevice device : getRealDevices()) {
+ executeShellCommand(device, "rm -rf " + getReportingDir());
+ }
+ }
+
+ private String getSnoopLogModeProperty() throws DeviceNotAvailableException {
+ for (ITestDevice device : getRealDevices()) {
+ return device.getProperty(SNOOP_LOG_MODE_PROPERTY);
+ }
+ return null;
+ }
+
+ private void setSnoopLogModeProperty(String mode) throws DeviceNotAvailableException {
+ if (mode == null) {
+ CLog.i("mode is null. Using empty string instead.");
+ mode = "";
+ }
+ for (ITestDevice device : getRealDevices()) {
+ boolean successfullySetPropOnDevice = device.setProperty(SNOOP_LOG_MODE_PROPERTY, mode);
+ if (!successfullySetPropOnDevice) {
+ CLog.w(
+ "Failed to set property [%s] to [%s] on [%s].",
+ SNOOP_LOG_MODE_PROPERTY, mode, device.getSerialNumber());
+ }
+ }
+ }
+
+ private void disableBluetoothService(ITestDevice device) throws DeviceNotAvailableException {
+ executeShellCommand(device, "cmd bluetooth_manager disable");
+ executeShellCommand(device, "cmd bluetooth_manager wait-for-state:STATE_OFF");
+ }
+
+ private void enableBluetoothService(ITestDevice device) throws DeviceNotAvailableException {
+ executeShellCommand(device, "cmd bluetooth_manager enable");
+ executeShellCommand(device, "cmd bluetooth_manager wait-for-state:STATE_ON");
+ }
+
+ /**
+ * Execute shell command on the device. If the execution failed (non-zero exit code), throw a
+ * {@link com.android.tradefed.device.DeviceRuntimeException}.
+ *
+ * @throws DeviceRuntimeException
+ */
+ protected void executeShellCommand(ITestDevice device, String command)
+ throws DeviceNotAvailableException {
+ CommandResult result = device.executeShellV2Command(command);
+ if (result.getExitCode() != 0) {
+ throw new DeviceRuntimeException(
+ "Failed to execute command: " + command,
+ DeviceErrorIdentifier.SHELL_COMMAND_ERROR);
+ }
+ }
+
+ /**
+ * Normalise the test name to avoid using slash /. For instance, "A2DP_SNK#A2DP/SNK/AS/BV-01-I"
+ * would be updated to "A2DP_SNK#A2DP_SNK_AS_BV-01-I".
+ */
+ private String normaliseTestName(String testName) {
+ return testName.replace("/", "_");
+ }
+}
diff --git a/test_framework/com/android/tradefed/device/metric/ShowmapPullerMetricCollector.java b/test_framework/com/android/tradefed/device/metric/ShowmapPullerMetricCollector.java
index 1d278fd..183c79c 100644
--- a/test_framework/com/android/tradefed/device/metric/ShowmapPullerMetricCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/ShowmapPullerMetricCollector.java
@@ -249,12 +249,13 @@
* @return true or false
*/
private Boolean isProcessFound(String line) {
+ if (mProcessNames.isEmpty()) return false;
boolean psResult;
Pattern psPattern = Pattern.compile(PROCESS_NAME_REGEX);
Matcher psMatcher = psPattern.matcher(line);
if (psMatcher.find()) {
processName = psMatcher.group(2);
- psResult = mProcessNames.isEmpty() || mProcessNames.contains(processName);
+ psResult = mProcessNames.contains(processName);
return psResult;
}
return false;
diff --git a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
index 17d371d..d824633 100644
--- a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
@@ -27,6 +27,8 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.observatory.IDiscoverDependencies;
import com.android.tradefed.result.error.DeviceErrorIdentifier;
@@ -327,6 +329,8 @@
// Try to stage the files from remote zip files.
src = buildInfo.stageRemoteFile(fileName, testDir);
if (src != null) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.STAGE_UNDEFINED_DEPENDENCY, fileName);
try {
// Search again with filtering on ABI
File srcWithAbi = FileUtil.findFile(fileName, mAbi, testDir);
@@ -486,4 +490,12 @@
}
return deps;
}
+
+ public boolean shouldRemountSystem() {
+ return mRemountSystem;
+ }
+
+ public boolean shouldRemountVendor() {
+ return mRemountVendor;
+ }
}
diff --git a/test_framework/com/android/tradefed/testtype/AndroidJUnitTest.java b/test_framework/com/android/tradefed/testtype/AndroidJUnitTest.java
index 4126519..2802e40 100644
--- a/test_framework/com/android/tradefed/testtype/AndroidJUnitTest.java
+++ b/test_framework/com/android/tradefed/testtype/AndroidJUnitTest.java
@@ -31,14 +31,20 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
+import com.android.tradefed.targetprep.BuildError;
+import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.targetprep.TestAppInstallSetup;
import com.android.tradefed.util.ArrayUtil;
+import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.ListInstrumentationParser;
+import com.android.tradefed.util.ResourceUtil;
import com.google.common.annotations.VisibleForTesting;
import org.junit.runner.notification.RunListener;
import java.io.File;
+import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collection;
@@ -90,6 +96,8 @@
*/
public static final String NEW_RUN_LISTENER_ORDER_KEY = "newRunListenerMode";
+ public static final String USE_TEST_STORAGE_SERVICE = "useTestStorageService";
+
/** Options from the collector side helper library. */
public static final String INCLUDE_COLLECTOR_FILTER_KEY = "include-filter-group";
@@ -142,6 +150,17 @@
private String mTestFilterDir = "/data/local/tmp/ajur";
@Option(
+ name = "test-storage-dir",
+ description = "The device directory path where test storage read files.")
+ private String mTestStorageInternalDir = "/sdcard/googletest/test_runfiles";
+
+ @Option(
+ name = "use-test-storage",
+ description =
+ "If set to true, we will push filters to the test storage instead of disk.")
+ private boolean mUseTestStorage = false;
+
+ @Option(
name = "ajur-max-shard",
description = "The maximum number of shard we want to allow the AJUR test to shard into"
)
@@ -334,6 +353,10 @@
if (mIncludeTestFile != null && mIncludeTestFile.length() > 0) {
mDeviceIncludeFile = mTestFilterDir.replaceAll("/$", "") + "/" + INCLUDE_FILE;
pushTestFile(mIncludeTestFile, mDeviceIncludeFile, listener);
+ if (mUseTestStorage) {
+ pushTestFile(
+ mIncludeTestFile, mTestStorageInternalDir + mDeviceIncludeFile, listener);
+ }
pushedFile = true;
// If an explicit include file filter is provided, do not use the package
setTestPackageName(null);
@@ -343,14 +366,43 @@
if (mExcludeTestFile != null && mExcludeTestFile.length() > 0) {
mDeviceExcludeFile = mTestFilterDir.replaceAll("/$", "") + "/" + EXCLUDE_FILE;
pushTestFile(mExcludeTestFile, mDeviceExcludeFile, listener);
+ if (mUseTestStorage) {
+ pushTestFile(
+ mExcludeTestFile, mTestStorageInternalDir + mDeviceExcludeFile, listener);
+ }
pushedFile = true;
}
+ TestAppInstallSetup serviceInstaller = null;
+ if (pushedFile && mUseTestStorage) {
+ File testServices = null;
+ try {
+ testServices = FileUtil.createTempFile("services", ".apk");
+ boolean extracted =
+ ResourceUtil.extractResourceAsFile(
+ "/test-services-1.4.2.apk", testServices);
+ if (extracted) {
+ serviceInstaller = new TestAppInstallSetup();
+ serviceInstaller.addTestFile(testServices);
+ serviceInstaller.setUp(testInfo);
+ } else {
+ throw new IOException("Failed to extract test-services.apk");
+ }
+ } catch (IOException | TargetSetupError | BuildError e) {
+ CLog.e(e);
+ mUseTestStorage = false;
+ } finally {
+ FileUtil.deleteFile(testServices);
+ }
+ }
if (mTotalShards > 0 && !isShardable() && mShardIndex != 0) {
// If not shardable, only first shard can run.
CLog.i("%s is not shardable.", getRunnerName());
return;
}
super.run(testInfo, listener);
+ if (serviceInstaller != null) {
+ serviceInstaller.tearDown(testInfo, null);
+ }
if (pushedFile) {
// Remove the directory where the files where pushed
removeTestFilterDir();
@@ -440,6 +492,10 @@
runner.addInstrumentationArg(
NEW_RUN_LISTENER_ORDER_KEY, Boolean.toString(mNewRunListenerOrderMode));
}
+ if (mUseTestStorage) {
+ runner.addInstrumentationArg(
+ USE_TEST_STORAGE_SERVICE, Boolean.toString(mUseTestStorage));
+ }
// Add the listeners received from Options
addDeviceListeners(mExtraDeviceListeners);
}
diff --git a/test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java b/test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
index 4602742..113d7e7 100644
--- a/test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
+++ b/test_framework/com/android/tradefed/testtype/DeviceJUnit4ClassRunner.java
@@ -21,6 +21,7 @@
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
@@ -180,7 +181,7 @@
}
private Set<File> resolveRemoteFileForObject(Object obj) {
- try {
+ try (CloseableTraceScope ignore = new CloseableTraceScope("junit4:resolveRemoteFiles")) {
OptionSetter setter = new OptionSetter(obj);
return setter.validateRemoteFilePath(createResolver());
} catch (BuildRetrievalError | ConfigurationException e) {
diff --git a/test_framework/com/android/tradefed/testtype/GTestResultParser.java b/test_framework/com/android/tradefed/testtype/GTestResultParser.java
index 16cf9b0..2a5ef00 100644
--- a/test_framework/com/android/tradefed/testtype/GTestResultParser.java
+++ b/test_framework/com/android/tradefed/testtype/GTestResultParser.java
@@ -762,6 +762,10 @@
listener.testFailed(testId, testFailure);
listener.testEnded(testId, emptyMap);
}
+ if (mMethodScope != null) {
+ mMethodScope.close();
+ mMethodScope = null;
+ }
clearCurrentTestResult();
}
// Report the test run failed
diff --git a/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
index d063706..2cbb2bd 100644
--- a/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
+++ b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
@@ -23,6 +23,7 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
@@ -214,7 +215,7 @@
CollectingOutputReceiver outputCollector = createOutputCollector();
GoogleBenchmarkResultParser resultParser = createResultParser(runName, listener);
listener.testRunStarted(runName, numTests);
- try {
+ try (CloseableTraceScope ignore = new CloseableTraceScope(runName)) {
String cmd =
String.format(
"%s%s%s %s",
diff --git a/test_framework/com/android/tradefed/testtype/HostTest.java b/test_framework/com/android/tradefed/testtype/HostTest.java
index 1d494a9..57def46 100644
--- a/test_framework/com/android/tradefed/testtype/HostTest.java
+++ b/test_framework/com/android/tradefed/testtype/HostTest.java
@@ -704,7 +704,9 @@
new TestTimeoutEnforcer(
mTestCaseTimeout.toMillis(), TimeUnit.MILLISECONDS, listener);
}
- return JUnitRunUtil.runTest(listener, junitTest, className, mTestInfo);
+ try (CloseableTraceScope ignored = new CloseableTraceScope(className)) {
+ return JUnitRunUtil.runTest(listener, junitTest, className, mTestInfo);
+ }
}
}
@@ -1381,7 +1383,7 @@
}
private Set<File> resolveRemoteFileForObject(Object obj) {
- try {
+ try (CloseableTraceScope ignore = new CloseableTraceScope("infra:resolveRemoteFiles")) {
OptionSetter setter = new OptionSetter(obj);
return setter.validateRemoteFilePath(createResolver());
} catch (BuildRetrievalError | ConfigurationException e) {
diff --git a/test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java b/test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java
index 4451770..dade01d 100644
--- a/test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java
+++ b/test_framework/com/android/tradefed/testtype/InstalledInstrumentationsTest.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.testtype;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.IConfigurationReceiver;
@@ -46,8 +47,10 @@
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
@@ -196,6 +199,17 @@
"Create InstrumentationTest type rather than more recent AndroidJUnitTest.")
private boolean mDowngradeInstrumentation = false;
+ @Option(
+ name = "test-storage-dir",
+ description = "The device directory path where test storage read files.")
+ private String mTestStorageInternalDir = "/sdcard/googletest/test_runfiles";
+
+ @Option(
+ name = "use-test-storage",
+ description =
+ "If set to true, we will push filters to the test storage instead of disk.")
+ private boolean mUseTestStorage = false;
+
private int mTotalShards = 0;
private int mShardIndex = 0;
private List<IMetricCollector> mMetricCollectorList = new ArrayList<>();
@@ -213,16 +227,27 @@
public boolean shouldRetry(int attemptJustExecuted, List<TestRunResult> previousResults)
throws DeviceNotAvailableException {
boolean retry = false;
- mRunTestsFailureMap = new HashMap<>();
+ if (mRunTestsFailureMap == null) {
+ mRunTestsFailureMap = new HashMap<>();
+ }
for (TestRunResult run : previousResults) {
if (run == null) {
continue;
}
if (run.isRunFailure() || run.hasFailedTests()) {
retry = true;
+ HashSet<TestDescription> excludes =
+ new LinkedHashSet<>(
+ run.getTestsInState(
+ Arrays.asList(
+ TestStatus.PASSED,
+ TestStatus.ASSUMPTION_FAILURE,
+ TestStatus.IGNORED)));
+ if (mRunTestsFailureMap.get(run.getName()) != null) {
+ excludes.addAll(mRunTestsFailureMap.get(run.getName()));
+ }
// Exclude passed tests from rerunning
- mRunTestsFailureMap.put(
- run.getName(), new LinkedHashSet<TestDescription>(run.getPassedTests()));
+ mRunTestsFailureMap.put(run.getName(), excludes);
} else {
// Set null if we should not rerun it
mRunTestsFailureMap.put(run.getName(), null);
diff --git a/test_framework/com/android/tradefed/testtype/InstrumentationListener.java b/test_framework/com/android/tradefed/testtype/InstrumentationListener.java
index eea4590..ea56254 100644
--- a/test_framework/com/android/tradefed/testtype/InstrumentationListener.java
+++ b/test_framework/com/android/tradefed/testtype/InstrumentationListener.java
@@ -50,6 +50,8 @@
// Message from ddmlib for ShellCommandUnresponsiveException
private static final String DDMLIB_SHELL_UNRESPONSIVE =
"Failed to receive adb shell test output within";
+ private static final String JUNIT4_TIMEOUT =
+ "org.junit.runners.model.TestTimedOutException: test timed out";
// Message from ddmlib when there is a mismatch of test cases count
private static final String DDMLIB_UNEXPECTED_COUNT = "Instrumentation reported numtests=";
@@ -117,6 +119,15 @@
}
@Override
+ public void testFailed(TestDescription test, FailureDescription failure) {
+ String message = failure.getErrorMessage();
+ if (message.startsWith(JUNIT4_TIMEOUT) || message.contains(DDMLIB_SHELL_UNRESPONSIVE)) {
+ failure.setErrorIdentifier(TestErrorIdentifier.TEST_TIMEOUT).setRetriable(false);
+ }
+ super.testFailed(test, failure);
+ }
+
+ @Override
public void testEnded(TestDescription test, long endTime, HashMap<String, Metric> testMetrics) {
super.testEnded(test, endTime, testMetrics);
if (mMethodScope != null) {
diff --git a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
index 902148c..c17257c 100644
--- a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
@@ -98,6 +98,7 @@
public static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER";
public static final String RUN_TESTS_ON_SDK_SANDBOX = "RUN_TESTS_ON_SDK_SANDBOX";
+ private static final String SKIP_TESTS_REASON_KEY = "skip-tests-reason";
@Option(
name = "package",
@@ -746,6 +747,10 @@
createRemoteAndroidTestRunner(
mPackageName, mRunnerName, mDevice.getIDevice(), testInfo);
setRunnerArgs(mRunner);
+ if (testInfo != null && testInfo.properties().containsKey(SKIP_TESTS_REASON_KEY)) {
+ mRunner.addInstrumentationArg(
+ SKIP_TESTS_REASON_KEY, testInfo.properties().get(SKIP_TESTS_REASON_KEY));
+ }
doTestRun(testInfo, listener);
if (mInstallFile != null) {
diff --git a/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java b/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java
index 3f847e7..4b3a770 100644
--- a/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java
+++ b/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java
@@ -322,6 +322,9 @@
if (mRobolectricResources) {
cmdArgs.addAll(compileRobolectricOptions());
+ // Prevent tradefed from eagerly loading classes, which may not load without shadows
+ // applied.
+ mExcludePaths.add("org/robolectric");
}
if (this.debug) {
diff --git a/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java b/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
index 7b7db94..435d851 100644
--- a/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
+++ b/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
@@ -446,12 +446,25 @@
listener.testRunEnded(0L, new HashMap<String, Metric>());
}
+ private Set<String> cleanFilters(Set<String> filters) {
+ Set<String> new_filters = new LinkedHashSet<String>();
+ for (String filter : filters) {
+ new_filters.add(filter.replace("#", "."));
+ }
+ return new_filters;
+ }
+
@VisibleForTesting
- String getLogDirAbsolutePath() {
+ protected String getLogDirAbsolutePath() {
return getLogDir().getAbsolutePath();
}
@VisibleForTesting
+ protected File getLogDirFile() {
+ return mLogDir;
+ }
+
+ @VisibleForTesting
String getTestBed() {
return mTestBed;
}
@@ -478,6 +491,10 @@
commandLine.add("--device_serial=" + device.getSerialNumber());
}
commandLine.add("--log_path=" + getLogDirAbsolutePath());
+ if (!mIncludeFilters.isEmpty()) {
+ commandLine.add("--tests");
+ commandLine.addAll(cleanFilters(mIncludeFilters));
+ }
// Add all the other options
commandLine.addAll(getTestOptions());
return commandLine.toArray(new String[0]);
@@ -503,6 +520,8 @@
}
}
FileUtil.recursiveDelete(logDir);
+ // reset log dir to be recreated for retries
+ mLogDir = null;
}
@VisibleForTesting
diff --git a/test_result_interfaces/Android.bp b/test_result_interfaces/Android.bp
index 73d097a..da4e22f 100644
--- a/test_result_interfaces/Android.bp
+++ b/test_result_interfaces/Android.bp
@@ -34,4 +34,6 @@
"tradefed-common-util",
"tradefed-protos",
],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
}
diff --git a/test_result_interfaces/com/android/tradefed/util/proto/TfMetricProtoUtil.java b/test_result_interfaces/com/android/tradefed/util/proto/TfMetricProtoUtil.java
index 799f3c2..a548ccb 100644
--- a/test_result_interfaces/com/android/tradefed/util/proto/TfMetricProtoUtil.java
+++ b/test_result_interfaces/com/android/tradefed/util/proto/TfMetricProtoUtil.java
@@ -71,13 +71,48 @@
* limitations.
*/
public static HashMap<String, Metric> upgradeConvert(Map<String, String> metrics) {
+ return upgradeConvert(metrics, false);
+ }
+
+ /**
+ * Conversion from Map<String, String> to HashMap<String, Metric>. In order to go to the new
+ * interface. Information might only be partially populated because of the old format
+ * limitations.
+ *
+ * @param smartNumbers convert numbers to int metrics
+ */
+ public static HashMap<String, Metric> upgradeConvert(
+ Map<String, String> metrics, boolean smartNumbers) {
HashMap<String, Metric> newFormat = new LinkedHashMap<>();
for (String key : metrics.keySet()) {
- newFormat.put(key, stringToMetric(metrics.get(key)));
+ Metric metric = null;
+ String stringMetric = metrics.get(key);
+ if (smartNumbers) {
+ Long numMetric = isLong(stringMetric);
+ if (numMetric != null) {
+ metric = createSingleValue(numMetric.longValue(), null);
+ }
+ }
+ // Default to String metric
+ if (metric == null) {
+ metric = stringToMetric(stringMetric);
+ }
+ newFormat.put(key, metric);
}
return newFormat;
}
+ private static Long isLong(String strNum) {
+ if (strNum == null) {
+ return null;
+ }
+ try {
+ return Long.parseLong(strNum);
+ } catch (NumberFormatException nfe) {
+ return null;
+ }
+ }
+
/**
* Convert a simple String metric (old format) to a {@link Metric} (new format).
*
diff --git a/util_apps/ContentProvider/hostsidetests/Android.bp b/util_apps/ContentProvider/hostsidetests/Android.bp
index 14d3680..7284d16 100644
--- a/util_apps/ContentProvider/hostsidetests/Android.bp
+++ b/util_apps/ContentProvider/hostsidetests/Android.bp
@@ -24,6 +24,9 @@
// Only compile source java files in this jar.
srcs: ["src/**/*.java"],
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
+
libs: ["tradefed"],
static_libs: [
diff --git a/util_apps/WifiUtil/Android.bp b/util_apps/WifiUtil/Android.bp
index 2f7e9eb..15d465f 100644
--- a/util_apps/WifiUtil/Android.bp
+++ b/util_apps/WifiUtil/Android.bp
@@ -22,6 +22,8 @@
min_sdk_version: "7",
target_sdk_version: "31",
sdk_version: "current",
+ // b/267831518: Pin tradefed and dependencies to Java 11.
+ java_version: "11",
manifest: "src/com/android/tradefed/utils/wifi/AndroidManifest.xml",
optimize: {
enabled: false,