aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp30
-rw-r--r--atest/Android.bp1
-rw-r--r--atest/TEST_MAPPING10
-rw-r--r--common_util/com/android/tradefed/config/ConfigurationException.java40
-rw-r--r--common_util/com/android/tradefed/error/HarnessException.java (renamed from device_build_interfaces/com/android/tradefed/error/HarnessException.java)0
-rw-r--r--common_util/com/android/tradefed/error/HarnessRuntimeException.java (renamed from device_build_interfaces/com/android/tradefed/error/HarnessRuntimeException.java)6
-rw-r--r--common_util/com/android/tradefed/error/IHarnessException.java (renamed from device_build_interfaces/com/android/tradefed/error/IHarnessException.java)0
-rw-r--r--common_util/com/android/tradefed/result/LogDataType.java3
-rw-r--r--common_util/com/android/tradefed/result/error/DeviceErrorIdentifier.java (renamed from test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java)3
-rw-r--r--common_util/com/android/tradefed/result/error/ErrorIdentifier.java (renamed from test_result_interfaces/com/android/tradefed/result/error/ErrorIdentifier.java)3
-rw-r--r--common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java (renamed from test_result_interfaces/com/android/tradefed/result/error/InfraErrorIdentifier.java)10
-rw-r--r--common_util/com/android/tradefed/result/error/TestErrorIdentifier.java41
-rw-r--r--common_util/com/android/tradefed/util/SparseImageUtil.java264
-rw-r--r--common_util/com/android/tradefed/util/ZipUtil.java1
-rw-r--r--common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java4
-rw-r--r--device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java28
-rw-r--r--device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java23
-rw-r--r--invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java8
-rw-r--r--invocation_interfaces/com/android/tradefed/result/TestResult.java10
-rw-r--r--invocation_interfaces/com/android/tradefed/result/TestRunResult.java4
-rw-r--r--proto/monitoring/server/lab_resource.proto153
-rw-r--r--res/suite/allowed-preparers.txt4
-rw-r--r--src/com/android/tradefed/build/BootstrapBuildProvider.java11
-rw-r--r--src/com/android/tradefed/build/DependenciesResolver.java2
-rw-r--r--src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java7
-rw-r--r--src/com/android/tradefed/cluster/ClusterCommandEvent.java1
-rw-r--r--src/com/android/tradefed/cluster/ClusterCommandLauncher.java21
-rw-r--r--src/com/android/tradefed/cluster/ClusterCommandScheduler.java17
-rw-r--r--src/com/android/tradefed/cluster/ClusterDeviceInfo.java2
-rw-r--r--src/com/android/tradefed/cluster/ClusterDeviceMonitor.java7
-rw-r--r--src/com/android/tradefed/cluster/ClusterHostEvent.java2
-rw-r--r--src/com/android/tradefed/cluster/ClusterHostUtil.java107
-rw-r--r--src/com/android/tradefed/cluster/SubprocessCommandException.java24
-rw-r--r--src/com/android/tradefed/cluster/SubprocessConfigBuilder.java15
-rw-r--r--src/com/android/tradefed/cluster/SubprocessReportingHelper.java3
-rw-r--r--src/com/android/tradefed/command/CommandOptions.java31
-rw-r--r--src/com/android/tradefed/command/CommandRunner.java6
-rw-r--r--src/com/android/tradefed/command/CommandScheduler.java31
-rw-r--r--src/com/android/tradefed/command/DeviceAllocationResult.java67
-rw-r--r--src/com/android/tradefed/command/ICommandOptions.java11
-rw-r--r--src/com/android/tradefed/config/proxy/TradefedDelegator.java27
-rw-r--r--src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java70
-rw-r--r--src/com/android/tradefed/config/yaml/YamlClassOptionsParser.java (renamed from src/com/android/tradefed/config/yaml/YamlTestRunners.java)47
-rw-r--r--src/com/android/tradefed/device/DeviceSelectionOptions.java84
-rw-r--r--src/com/android/tradefed/device/IDeviceSelection.java6
-rw-r--r--src/com/android/tradefed/device/NativeDevice.java26
-rw-r--r--src/com/android/tradefed/device/NoDeviceException.java33
-rw-r--r--src/com/android/tradefed/device/TestDevice.java7
-rw-r--r--src/com/android/tradefed/device/cloud/GceManager.java10
-rw-r--r--src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java57
-rw-r--r--src/com/android/tradefed/device/metric/FilePullerLogCollector.java2
-rw-r--r--src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java30
-rw-r--r--src/com/android/tradefed/invoker/DelegatedInvocationExecution.java4
-rw-r--r--src/com/android/tradefed/invoker/InvocationExecution.java10
-rw-r--r--src/com/android/tradefed/invoker/TestInvocation.java120
-rw-r--r--src/com/android/tradefed/invoker/shard/StrictShardHelper.java43
-rw-r--r--src/com/android/tradefed/monitoring/LabResourceDeviceMonitor.java96
-rw-r--r--src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java47
-rw-r--r--src/com/android/tradefed/result/CollectingTestListener.java6
-rw-r--r--src/com/android/tradefed/result/JsonHttpTestResultReporter.java28
-rw-r--r--src/com/android/tradefed/result/LogcatCrashResultForwarder.java29
-rw-r--r--src/com/android/tradefed/result/ResultForwarder.java14
-rw-r--r--src/com/android/tradefed/result/suite/SuiteResultReporter.java3
-rw-r--r--src/com/android/tradefed/retry/BaseRetryDecision.java92
-rw-r--r--src/com/android/tradefed/retry/IRetryDecision.java19
-rw-r--r--src/com/android/tradefed/retry/ResultAggregator.java6
-rw-r--r--src/com/android/tradefed/sandbox/ISandbox.java2
-rw-r--r--src/com/android/tradefed/sandbox/TradefedSandbox.java4
-rw-r--r--src/com/android/tradefed/sandbox/TradefedSandboxRunner.java2
-rw-r--r--src/com/android/tradefed/targetprep/DeviceSetup.java2
-rw-r--r--src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java25
-rw-r--r--src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java89
-rw-r--r--src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java147
-rw-r--r--src/com/android/tradefed/targetprep/TestAppInstallSetup.java71
-rw-r--r--src/com/android/tradefed/testtype/SubprocessTfLauncher.java9
-rw-r--r--src/com/android/tradefed/testtype/suite/BaseTestSuite.java2
-rw-r--r--src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java15
-rw-r--r--src/com/android/tradefed/testtype/suite/ITestSuite.java46
-rw-r--r--src/com/android/tradefed/testtype/suite/ModuleDefinition.java117
-rw-r--r--src/com/android/tradefed/testtype/suite/ModuleSplitter.java38
-rw-r--r--src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java9
-rw-r--r--src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java10
-rw-r--r--src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java9
-rw-r--r--src/com/android/tradefed/util/AaptParser.java67
-rw-r--r--src/com/android/tradefed/util/ProtoUtil.java89
-rw-r--r--src/com/android/tradefed/util/StringEscapeUtils.java9
-rw-r--r--src/com/android/tradefed/util/SubprocessTestResultsParser.java12
-rw-r--r--src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java15
-rw-r--r--test_framework/Android.bp1
-rw-r--r--test_framework/com/android/tradefed/device/metric/AtraceCollector.java5
-rw-r--r--test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java54
-rw-r--r--test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java37
-rw-r--r--test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java130
-rw-r--r--test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java52
-rw-r--r--test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java78
-rw-r--r--test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java52
-rw-r--r--test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java52
-rw-r--r--test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java7
-rw-r--r--test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java175
-rw-r--r--test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java224
-rw-r--r--test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java163
-rw-r--r--test_framework/com/android/tradefed/device/metric/TemperatureCollector.java155
-rw-r--r--test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java53
-rw-r--r--test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java44
-rw-r--r--test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java157
-rw-r--r--test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java55
-rw-r--r--test_framework/com/android/tradefed/targetprep/PushFilePreparer.java41
-rw-r--r--test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java70
-rw-r--r--test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java31
-rw-r--r--test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java69
-rw-r--r--test_framework/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java98
-rw-r--r--test_framework/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java100
-rw-r--r--test_framework/com/android/tradefed/targetprep/WifiPreparer.java8
-rw-r--r--test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java5
-rw-r--r--test_framework/com/android/tradefed/testtype/ArtGTest.java30
-rw-r--r--test_framework/com/android/tradefed/testtype/ArtRunTest.java241
-rw-r--r--test_framework/com/android/tradefed/testtype/GTest.java4
-rw-r--r--test_framework/com/android/tradefed/testtype/GTestBase.java10
-rw-r--r--test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java15
-rw-r--r--test_framework/com/android/tradefed/testtype/HostGTest.java18
-rw-r--r--test_framework/com/android/tradefed/testtype/InstrumentationTest.java40
-rw-r--r--test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java56
-rw-r--r--test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultHandlerFactory.java6
-rw-r--r--test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultParser.java4
-rw-r--r--test_framework/com/android/tradefed/util/PythonVirtualenvHelper.java128
-rw-r--r--tests/Android.bp13
-rw-r--r--tests/res/proto/proto_util_test.proto38
-rw-r--r--tests/res/testconfigs/yaml/not-target-preparer.tf_yaml16
-rw-r--r--tests/res/testconfigs/yaml/test-config.tf_yaml18
-rw-r--r--tests/res/util/partial_zip.zipbin33565 -> 50283 bytes
-rw-r--r--tests/src/com/android/tradefed/UnitTests.java46
-rw-r--r--tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java49
-rw-r--r--tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java122
-rw-r--r--tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java18
-rw-r--r--tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java70
-rw-r--r--tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java17
-rw-r--r--tests/src/com/android/tradefed/command/CommandSchedulerTest.java34
-rw-r--r--tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java10
-rw-r--r--tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java20
-rw-r--r--tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java37
-rw-r--r--tests/src/com/android/tradefed/device/TestDeviceTest.java126
-rw-r--r--tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java106
-rw-r--r--tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java31
-rw-r--r--tests/src/com/android/tradefed/device/metric/BuddyInfoMetricCollectorTest.java83
-rw-r--r--tests/src/com/android/tradefed/device/metric/BugreportzMetricCollectorTest.java73
-rw-r--r--tests/src/com/android/tradefed/device/metric/DumpHeapCollectorTest.java183
-rw-r--r--tests/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollectorTest.java83
-rw-r--r--tests/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollectorTest.java90
-rw-r--r--tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java43
-rw-r--r--tests/src/com/android/tradefed/device/metric/MemInfoMetricCollectorTest.java86
-rw-r--r--tests/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollectorTest.java85
-rw-r--r--tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java42
-rw-r--r--tests/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollectorTest.java108
-rw-r--r--tests/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollectorTest.java284
-rw-r--r--tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java124
-rw-r--r--tests/src/com/android/tradefed/device/metric/TemperatureCollectorTest.java100
-rw-r--r--tests/src/com/android/tradefed/device/metric/TraceMetricCollectorTest.java85
-rw-r--r--tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java24
-rw-r--r--tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java24
-rw-r--r--tests/src/com/android/tradefed/invoker/TestInvocationTest.java34
-rw-r--r--tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java2
-rw-r--r--tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java102
-rw-r--r--tests/src/com/android/tradefed/monitoring/LabResourceDeviceMonitorTest.java49
-rw-r--r--tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java80
-rw-r--r--tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java2
-rw-r--r--tests/src/com/android/tradefed/result/JsonHttpTestResultReporterTest.java58
-rw-r--r--tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java52
-rw-r--r--tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java1
-rw-r--r--tests/src/com/android/tradefed/retry/ResultAggregatorTest.java129
-rw-r--r--tests/src/com/android/tradefed/sandbox/SandboxedInvocationExecutionTest.java73
-rw-r--r--tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java16
-rw-r--r--tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java68
-rw-r--r--tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java37
-rw-r--r--tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java793
-rw-r--r--tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java48
-rw-r--r--tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java45
-rw-r--r--tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java77
-rw-r--r--tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java17
-rw-r--r--tests/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java172
-rw-r--r--tests/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java204
-rw-r--r--tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java113
-rw-r--r--tests/src/com/android/tradefed/testtype/ArtGTestTest.java95
-rw-r--r--tests/src/com/android/tradefed/testtype/ArtRunTestTest.java134
-rw-r--r--tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java19
-rw-r--r--tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java24
-rw-r--r--tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java80
-rw-r--r--tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java109
-rw-r--r--tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java39
-rw-r--r--tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java4
-rw-r--r--tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java6
-rw-r--r--tests/src/com/android/tradefed/util/AaptParserTest.java85
-rw-r--r--tests/src/com/android/tradefed/util/ProtoUtilTest.java181
-rw-r--r--tests/src/com/android/tradefed/util/PythonVirtualenvHelperTest.java143
-rw-r--r--tests/src/com/android/tradefed/util/RemoteZipTest.java6
-rw-r--r--tests/src/com/android/tradefed/util/SparseImageUtilTest.java137
-rw-r--r--tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java26
-rw-r--r--tests/src/com/android/tradefed/util/ZipUtilTest.java13
-rw-r--r--tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java20
-rw-r--r--tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java26
-rw-r--r--util-apps/ContentProvider/main/AndroidManifest.xml1
200 files changed, 7173 insertions, 3619 deletions
diff --git a/Android.bp b/Android.bp
index 4d6f01bbb..fb8090fb3 100644
--- a/Android.bp
+++ b/Android.bp
@@ -48,6 +48,8 @@ java_defaults {
"-Xep:TryFailThrowable:ERROR",
"-Xep:UnnecessaryParentheses:ERROR",
"-Xep:UseCorrectAssertInTests:ERROR",
+ "-XepDisableWarningsInGeneratedCode", // Disable warnings in gRPC generated code.
+ "-XepExcludedPaths:.*/srcjars/.*"
],
},
}
@@ -77,6 +79,24 @@ java_library_host {
],
}
+java_genrule_host {
+ name: "lab-resource-grpc-gen",
+ srcs: [
+ "proto/monitoring/server/lab_resource.proto",
+ ],
+ tools: [
+ "aprotoc",
+ "protoc-gen-grpc-java-plugin",
+ "soong_zip",
+ ],
+ arch: "common",
+ cmd: "$(location aprotoc) -Iexternal/protobuf/src" +
+ " -Itools/tradefederation/core/proto/monitoring/server" +
+ " --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-java-plugin) $(in)" +
+ " --grpc_out=$(genDir) && $(location soong_zip) -o $(out) -C $(genDir) -D $(genDir)",
+ out: ["tradefed-grpc.srcjar"],
+}
+
python_library_host {
name: "tradefed-protos-py",
pkg_path: "atest",
@@ -133,6 +153,7 @@ java_library_host {
srcs: [
"src/**/*.java",
"global_configuration/**/*.java",
+ ":lab-resource-grpc-gen",
],
static_libs: [
"tradefed-common-util",
@@ -159,6 +180,14 @@ java_library_host {
"tradefed-protos",
"tradefed-isolation-protos",
"tradefed-lite",
+ "guava",
+ "guava-testlib",
+ "grpc-java",
+ "grpc-java-testing",
+ "grpc-java-netty-shaded",
+ "javax-annotation-api-prebuilt-host-jar",
+ "opencensus-java-api",
+ "opencensus-java-contrib-grpc-metrics",
],
libs: [
"loganalysis",
@@ -210,7 +239,6 @@ droiddoc_host {
"-werror " +
"-package " +
"-devsite ",
- create_stubs: false,
}
sh_binary_host {
diff --git a/atest/Android.bp b/atest/Android.bp
index 53553fd81..a4031734e 100644
--- a/atest/Android.bp
+++ b/atest/Android.bp
@@ -134,7 +134,6 @@ python_test_host {
"atest_proto",
],
test_config: "atest_unittests.xml",
- test_suites: ["general-tests"],
defaults: ["atest_py2_default"],
}
diff --git a/atest/TEST_MAPPING b/atest/TEST_MAPPING
index 32e6a6dac..09a0ffb68 100644
--- a/atest/TEST_MAPPING
+++ b/atest/TEST_MAPPING
@@ -28,11 +28,11 @@
}
],
"presubmit": [
- {
- // Host side ATest unittests.
- "name": "atest_unittests",
- "host": true
- },
+// {
+// // Host side ATest unittests.
+// "name": "atest_unittests",
+// "host": true
+// },
{
// Host side metrics tests.
"name": "asuite_metrics_lib_tests",
diff --git a/common_util/com/android/tradefed/config/ConfigurationException.java b/common_util/com/android/tradefed/config/ConfigurationException.java
index 2af9c01dc..696ee59ca 100644
--- a/common_util/com/android/tradefed/config/ConfigurationException.java
+++ b/common_util/com/android/tradefed/config/ConfigurationException.java
@@ -15,10 +15,13 @@
*/
package com.android.tradefed.config;
-/**
- * Thrown if configuration could not be loaded.
- */
-public class ConfigurationException extends Exception {
+import com.android.tradefed.error.HarnessException;
+import com.android.tradefed.result.error.ErrorIdentifier;
+
+import java.lang.StackWalker.Option;
+
+/** Thrown if configuration could not be loaded. */
+public class ConfigurationException extends HarnessException {
private static final long serialVersionUID = 7742154448569011969L;
/**
@@ -27,7 +30,19 @@ public class ConfigurationException extends Exception {
* @param msg a meaningful error message
*/
public ConfigurationException(String msg) {
- super(msg);
+ super(msg, null);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
+ }
+
+ /**
+ * Creates a {@link ConfigurationException}.
+ *
+ * @param msg a meaningful error message
+ * @param error The {@link ErrorIdentifier} associated with the exception
+ */
+ public ConfigurationException(String msg, ErrorIdentifier error) {
+ super(msg, error);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
}
/**
@@ -37,7 +52,20 @@ public class ConfigurationException extends Exception {
* @param cause the {@link Throwable} that represents the original cause of the error
*/
public ConfigurationException(String msg, Throwable cause) {
- super(msg, cause);
+ super(msg, cause, null);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
+ }
+
+ /**
+ * Creates a {@link ConfigurationException}.
+ *
+ * @param msg a meaningful error message
+ * @param cause the {@link Throwable} that represents the original cause of the error
+ * @param error The {@link ErrorIdentifier} associated with the exception
+ */
+ public ConfigurationException(String msg, Throwable cause, ErrorIdentifier error) {
+ super(msg, cause, error);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
}
}
diff --git a/device_build_interfaces/com/android/tradefed/error/HarnessException.java b/common_util/com/android/tradefed/error/HarnessException.java
index f6fc35e10..f6fc35e10 100644
--- a/device_build_interfaces/com/android/tradefed/error/HarnessException.java
+++ b/common_util/com/android/tradefed/error/HarnessException.java
diff --git a/device_build_interfaces/com/android/tradefed/error/HarnessRuntimeException.java b/common_util/com/android/tradefed/error/HarnessRuntimeException.java
index 1da5df9bb..9b88de772 100644
--- a/device_build_interfaces/com/android/tradefed/error/HarnessRuntimeException.java
+++ b/common_util/com/android/tradefed/error/HarnessRuntimeException.java
@@ -78,4 +78,10 @@ public class HarnessRuntimeException extends RuntimeException implements IHarnes
public String getOrigin() {
return mOrigin;
}
+
+ protected final void setCallerClass(Class<?> clazz) {
+ if (clazz != null) {
+ mOrigin = clazz.getCanonicalName();
+ }
+ }
}
diff --git a/device_build_interfaces/com/android/tradefed/error/IHarnessException.java b/common_util/com/android/tradefed/error/IHarnessException.java
index aa9831bb4..aa9831bb4 100644
--- a/device_build_interfaces/com/android/tradefed/error/IHarnessException.java
+++ b/common_util/com/android/tradefed/error/IHarnessException.java
diff --git a/common_util/com/android/tradefed/result/LogDataType.java b/common_util/com/android/tradefed/result/LogDataType.java
index da1eb9eab..0bcb9a80f 100644
--- a/common_util/com/android/tradefed/result/LogDataType.java
+++ b/common_util/com/android/tradefed/result/LogDataType.java
@@ -32,7 +32,7 @@ public enum LogDataType {
JPEG("jpeg", "image/jpeg", true, false),
TAR_GZ("tar.gz", "application/gzip", true, false),
GZIP("gz", "application/gzip", true, false),
- HPROF("hprof", "text/plain", true, false),
+ HPROF("hprof", "application/octet-stream", true, false),
COVERAGE("ec", "text/plain", false, false), // Emma coverage file
NATIVE_COVERAGE("zip", "application/zip", true, false), // gcov coverage archive
CLANG_COVERAGE("profdata", "text/plain", false, false), // LLVM indexed profile data
@@ -60,6 +60,7 @@ public enum LogDataType {
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),
+ CFG("cfg", "application/octet-stream", false, true),
/* Unknown file type */
UNKNOWN("dat", "text/plain", false, false);
diff --git a/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java b/common_util/com/android/tradefed/result/error/DeviceErrorIdentifier.java
index 72efb6ad0..483d02b9b 100644
--- a/test_result_interfaces/com/android/tradefed/result/error/DeviceErrorIdentifier.java
+++ b/common_util/com/android/tradefed/result/error/DeviceErrorIdentifier.java
@@ -30,6 +30,9 @@ public enum DeviceErrorIdentifier implements ErrorIdentifier {
SHELL_COMMAND_ERROR(520_100, FailureStatus.DEPENDENCY_ISSUE),
DEVICE_UNEXPECTED_RESPONSE(30_101, FailureStatus.DEPENDENCY_ISSUE),
+ FAIL_PUSH_FILE(30_102, FailureStatus.DEPENDENCY_ISSUE),
+ FAIL_PULL_FILE(30_103, FailureStatus.DEPENDENCY_ISSUE),
+ DEVICE_FAILED_TO_RESET(30_104, FailureStatus.DEPENDENCY_ISSUE),
INSTRUMENTATION_CRASH(520_200, FailureStatus.SYSTEM_UNDER_TEST_CRASHED),
diff --git a/test_result_interfaces/com/android/tradefed/result/error/ErrorIdentifier.java b/common_util/com/android/tradefed/result/error/ErrorIdentifier.java
index 4d00bddf3..48e0b40df 100644
--- a/test_result_interfaces/com/android/tradefed/result/error/ErrorIdentifier.java
+++ b/common_util/com/android/tradefed/result/error/ErrorIdentifier.java
@@ -15,7 +15,6 @@
*/
package com.android.tradefed.result.error;
-import com.android.tradefed.result.FailureDescription;
import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
/**
@@ -33,7 +32,7 @@ public interface ErrorIdentifier {
/**
* The failure status associated with the identifier, this status is expected to align with the
- * {@link FailureDescription} one.
+ * FailureDescription one.
*/
public FailureStatus status();
}
diff --git a/test_result_interfaces/com/android/tradefed/result/error/InfraErrorIdentifier.java b/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java
index a91045724..619b4fbb6 100644
--- a/test_result_interfaces/com/android/tradefed/result/error/InfraErrorIdentifier.java
+++ b/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java
@@ -30,20 +30,28 @@ public enum InfraErrorIdentifier implements ErrorIdentifier {
CODE_COVERAGE_ERROR(500_004, FailureStatus.INFRA_FAILURE),
MODULE_SETUP_RUNTIME_EXCEPTION(500_005, FailureStatus.CUSTOMER_ISSUE),
CONFIGURED_ARTIFACT_NOT_FOUND(500_006, FailureStatus.CUSTOMER_ISSUE),
+ INVOCATION_TIMEOUT(500_007, FailureStatus.TIMED_OUT),
+ OPTION_CONFIGURATION_ERROR(500_008, FailureStatus.CUSTOMER_ISSUE),
+ RUNNER_ALLOCATION_ERROR(500_009, FailureStatus.INFRA_FAILURE),
+ SCHEDULER_ALLOCATION_ERROR(500_010, FailureStatus.CUSTOMER_ISSUE),
// 500_501 - 501_000: Build, Artifacts download related errors
ARTIFACT_REMOTE_PATH_NULL(500_501, FailureStatus.INFRA_FAILURE),
ARTIFACT_UNSUPPORTED_PATH(500_502, FailureStatus.INFRA_FAILURE),
- ARTIFACT_DOWNLOAD_ERROR(500_503, FailureStatus.INFRA_FAILURE),
+ ARTIFACT_DOWNLOAD_ERROR(500_503, FailureStatus.DEPENDENCY_ISSUE),
GCS_ERROR(500_504, FailureStatus.DEPENDENCY_ISSUE),
// 501_001 - 501_500: environment issues: For example: lab wifi
WIFI_FAILED_CONNECT(501_001, FailureStatus.DEPENDENCY_ISSUE),
GOOGLE_ACCOUNT_SETUP_FAILED(501_002, FailureStatus.DEPENDENCY_ISSUE),
+ NO_WIFI(501_003, FailureStatus.DEPENDENCY_ISSUE),
// 502_000 - 502_100: Test issues detected by infra
EXPECTED_TESTS_MISMATCH(502_000, FailureStatus.TEST_FAILURE),
+ // 505_000 - 505_250: Acloud errors
+ NO_ACLOUD_REPORT(505_000, FailureStatus.DEPENDENCY_ISSUE),
+
UNDETERMINED(510_000, FailureStatus.UNSET);
private final long code;
diff --git a/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java b/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java
new file mode 100644
index 000000000..5f80ba469
--- /dev/null
+++ b/common_util/com/android/tradefed/result/error/TestErrorIdentifier.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2020 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.result.error;
+
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
+
+/** Error identifier from tests and tests runners. */
+public enum TestErrorIdentifier implements ErrorIdentifier {
+ MODULE_DID_NOT_EXECUTE(530_001, FailureStatus.NOT_EXECUTED);
+
+ private final long code;
+ private final FailureStatus status;
+
+ TestErrorIdentifier(int code, FailureStatus status) {
+ this.code = code;
+ this.status = status;
+ }
+
+ @Override
+ public long code() {
+ return code;
+ }
+
+ @Override
+ public FailureStatus status() {
+ return status;
+ }
+}
diff --git a/common_util/com/android/tradefed/util/SparseImageUtil.java b/common_util/com/android/tradefed/util/SparseImageUtil.java
new file mode 100644
index 000000000..ce557a588
--- /dev/null
+++ b/common_util/com/android/tradefed/util/SparseImageUtil.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2020 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 java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Utility to unsparse sparse images.
+ *
+ * <p>This piece of code is adopted from:
+ * frameworks/base/packages/DynamicSystemInstallationService/src/com/android/dynsystem/SparseInputStream.java
+ */
+public class SparseImageUtil {
+ private static final int SPARSE_IMAGE_MAGIC = 0xED26FF3A;
+
+ /**
+ * Tests if file is a sparse image.
+ *
+ * @param imgFile a {@link File} that is to be tested.
+ * @return true if imgFile is a sparse image.
+ */
+ public static boolean isSparse(File imgFile) {
+ if (!imgFile.isFile()) {
+ return false;
+ }
+ try (FileInputStream in = new FileInputStream(imgFile)) {
+ // Check magic bytes
+ return readBuffer(in, 4).getInt() == SPARSE_IMAGE_MAGIC;
+ } catch (IOException e) {
+ // Return false if failed to read file
+ return false;
+ }
+ }
+
+ /**
+ * Unsparses a sparse image file.
+ *
+ * @param imgFile a {@link File} that is a sparse image.
+ * @param destFile a {@link File} to write the unsparsed image to.
+ * @throws IOException if imgFile is not a sparse image.
+ */
+ public static void unsparse(File imgFile, File destFile) throws IOException {
+ try (FileInputStream in = new FileInputStream(imgFile)) {
+ SparseInputStream sis = new SparseInputStream(new BufferedInputStream(in));
+ if (!sis.isSparse()) {
+ throw new IOException("Not a sparse image: " + imgFile);
+ }
+ FileUtil.writeToFile(sis, destFile);
+ }
+ }
+
+ /** Reads exact number of bytes. */
+ private static byte[] readFully(InputStream in, int size) throws IOException {
+ byte[] buf = new byte[size];
+ int n = 0;
+ int off = 0;
+ int left = size;
+ while (left > 0) {
+ n = in.read(buf, off, left);
+ if (n < 0) {
+ throw new IOException("Unexpected EOF in readFully()");
+ }
+ off += n;
+ left -= n;
+ }
+ return buf;
+ }
+
+ /** Helper that wraps result of readFully() in a ByteBuffer for easy consumption. */
+ private static ByteBuffer readBuffer(InputStream in, int size) throws IOException {
+ return ByteBuffer.wrap(readFully(in, size)).order(ByteOrder.LITTLE_ENDIAN);
+ }
+
+ /**
+ * SparseInputStream read from upstream and detects the data format. If the upstream is a valid
+ * sparse data, it will unsparse it on the fly. Otherwise, it just passthrough as is.
+ */
+ private static class SparseInputStream extends InputStream {
+ private static final int FILE_HDR_SIZE = 28;
+ private static final int CHUNK_HDR_SIZE = 12;
+
+ /**
+ * This class represents a chunk in the Android sparse image.
+ *
+ * @see system/core/libsparse/sparse_format.h
+ */
+ private static class SparseChunk {
+ public static final short RAW = (short) 0xCAC1;
+ public static final short FILL = (short) 0xCAC2;
+ public static final short DONTCARE = (short) 0xCAC3;
+ public short mChunkType;
+ public int mChunkSize;
+ public int mTotalSize;
+ public byte[] mFill;
+
+ @Override
+ public String toString() {
+ return String.format(
+ "type: %x, chunk_size: %d, total_size: %d",
+ mChunkType, mChunkSize, mTotalSize);
+ }
+
+ public static SparseChunk readChunk(InputStream in) throws IOException {
+ SparseChunk chunk = new SparseChunk();
+ ByteBuffer buf = readBuffer(in, CHUNK_HDR_SIZE);
+ chunk.mChunkType = buf.getShort();
+ /* padding = */ buf.getShort();
+ chunk.mChunkSize = buf.getInt();
+ chunk.mTotalSize = buf.getInt();
+ if (chunk.mChunkType == FILL) {
+ chunk.mFill = readFully(in, 4);
+ }
+ return chunk;
+ }
+ }
+
+ private BufferedInputStream mIn;
+ private boolean mIsSparse;
+ private long mBlockSize;
+ private long mTotalBlocks;
+ private long mTotalChunks;
+ private SparseChunk mCur;
+ private long mLeft;
+ private int mCurChunks;
+
+ public SparseInputStream(BufferedInputStream in) throws IOException {
+ mIn = in;
+ in.mark(FILE_HDR_SIZE * 2);
+ ByteBuffer buf = readBuffer(mIn, FILE_HDR_SIZE);
+ mIsSparse = (buf.getInt() == SPARSE_IMAGE_MAGIC);
+ if (!mIsSparse) {
+ mIn.reset();
+ return;
+ }
+ int major = buf.getShort();
+ int minor = buf.getShort();
+ if (major > 0x1 || minor > 0x0) {
+ throw new IOException("Unsupported sparse version: " + major + "." + minor);
+ }
+ if (buf.getShort() != FILE_HDR_SIZE) {
+ throw new IOException("Illegal file header size");
+ }
+ if (buf.getShort() != CHUNK_HDR_SIZE) {
+ throw new IOException("Illegal chunk header size");
+ }
+ mBlockSize = buf.getInt();
+ if ((mBlockSize & 0x3) != 0) {
+ throw new IOException("Illegal block size, must be a multiple of 4: " + mBlockSize);
+ }
+ mTotalBlocks = buf.getInt();
+ mTotalChunks = buf.getInt();
+ mLeft = 0;
+ mCurChunks = 0;
+ }
+
+ /**
+ * Check if it needs to open a new chunk.
+ *
+ * @return true if it's EOF
+ */
+ private boolean prepareChunk() throws IOException {
+ if (mCur == null || mLeft <= 0) {
+ if (++mCurChunks > mTotalChunks) {
+ return true;
+ }
+ mCur = SparseChunk.readChunk(mIn);
+ mLeft = mCur.mChunkSize * mBlockSize;
+ }
+ return mLeft == 0;
+ }
+
+ @Override
+ public int read(byte[] buf, int off, int len) throws IOException {
+ if (!mIsSparse) {
+ return mIn.read(buf, off, len);
+ }
+ if (prepareChunk()) {
+ return -1;
+ }
+ int n = -1;
+ switch (mCur.mChunkType) {
+ case SparseChunk.RAW:
+ n = mIn.read(buf, off, (int) Math.min(mLeft, len));
+ mLeft -= n;
+ break;
+ case SparseChunk.DONTCARE:
+ n = (int) Math.min(mLeft, len);
+ Arrays.fill(buf, off, off + n, (byte) 0);
+ mLeft -= n;
+ break;
+ case SparseChunk.FILL:
+ // The FILL type is rarely used, so use a simple implementation.
+ n = super.read(buf, off, len);
+ break;
+ default:
+ throw new IOException("Unsupported Chunk:" + mCur);
+ }
+ return n;
+ }
+
+ @Override
+ public int read() throws IOException {
+ if (!mIsSparse) {
+ return mIn.read();
+ }
+ if (prepareChunk()) {
+ return -1;
+ }
+ int ret = -1;
+ switch (mCur.mChunkType) {
+ case SparseChunk.RAW:
+ ret = mIn.read();
+ break;
+ case SparseChunk.DONTCARE:
+ ret = 0;
+ break;
+ case SparseChunk.FILL:
+ ret = mCur.mFill[(4 - ((int) mLeft & 0x3)) & 0x3];
+ break;
+ default:
+ throw new IOException("Unsupported Chunk:" + mCur);
+ }
+ mLeft--;
+ return ret;
+ }
+
+ /**
+ * Get the unsparse size
+ *
+ * @return -1 if stream doesn't contain sparse image data.
+ */
+ public long getUnsparseSize() {
+ if (!mIsSparse) {
+ return -1;
+ }
+ return mBlockSize * mTotalBlocks;
+ }
+
+ public boolean isSparse() {
+ return mIsSparse;
+ }
+ }
+}
diff --git a/common_util/com/android/tradefed/util/ZipUtil.java b/common_util/com/android/tradefed/util/ZipUtil.java
index 31f3ff8c6..a6902ecdb 100644
--- a/common_util/com/android/tradefed/util/ZipUtil.java
+++ b/common_util/com/android/tradefed/util/ZipUtil.java
@@ -526,6 +526,7 @@ public class ZipUtil {
return;
} else if (zipEntry.getCompressedSize() == 0) {
// The file is empty, just create an empty file.
+ FileUtil.mkdirsRWX(targetFile.getParentFile());
targetFile.createNewFile();
return;
}
diff --git a/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java b/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java
index fc2687c97..963805778 100644
--- a/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java
+++ b/common_util/com/android/tradefed/util/zip/CentralDirectoryInfo.java
@@ -16,7 +16,6 @@
package com.android.tradefed.util.zip;
-import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.ByteArrayUtil;
import com.google.common.annotations.VisibleForTesting;
@@ -241,9 +240,6 @@ public final class CentralDirectoryInfo {
if (Long.toHexString(mUncompressedSize).equals("ffffffff") ||
Long.toHexString(mCompressedSize).equals("ffffffff") ||
Long.toHexString(mLocalHeaderOffset).equals("ffffffff")) {
- CLog.i("Values(compressed/uncompressed size, and relative offset of local header)) in "
- + "CentralDirectoryInfo for file name: %s reaches the limitation(0xffffffff), "
- + "getting the data from extra field.", mFileName);
byte[] zip64FieldId = Arrays.copyOfRange(
data, startOffset + mFileNameLength + 46, startOffset + mFileNameLength + 48);
// There should be a ZIP64 Field ID(0x0001) existing here.
diff --git a/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java b/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java
index 1e853c80a..4a996f442 100644
--- a/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java
+++ b/device_build_interfaces/com/android/tradefed/device/DeviceRuntimeException.java
@@ -15,31 +15,29 @@
*/
package com.android.tradefed.device;
+import com.android.tradefed.error.HarnessRuntimeException;
+import com.android.tradefed.result.error.ErrorIdentifier;
+
+import java.lang.StackWalker.Option;
+
/**
* Thrown when a device action did not results in the expected results.
*
* <p>For example: 'pm list users' is vastly expected to return the list of users, failure to do so
* should be raised as a DeviceRuntimeException since something went very wrong.
*/
-public class DeviceRuntimeException extends RuntimeException {
+public class DeviceRuntimeException extends HarnessRuntimeException {
private static final long serialVersionUID = -7928528651742852301L;
/**
* Creates a {@link DeviceRuntimeException}.
*
* @param msg a descriptive error message of the error.
+ * @param errorId The {@link ErrorIdentifier} categorizing the exception.
*/
- public DeviceRuntimeException(String msg) {
- super(msg);
- }
-
- /**
- * Creates a {@link DeviceRuntimeException}.
- *
- * @param t {@link Throwable} that should be wrapped in {@link DeviceRuntimeException}.
- */
- public DeviceRuntimeException(Throwable t) {
- super(t);
+ public DeviceRuntimeException(String msg, ErrorIdentifier errorId) {
+ super(msg, errorId);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
}
/**
@@ -47,8 +45,10 @@ public class DeviceRuntimeException extends RuntimeException {
*
* @param msg a descriptive error message of the error
* @param t {@link Throwable} that should be wrapped in {@link DeviceRuntimeException}.
+ * @param errorId The {@link ErrorIdentifier} categorizing the exception.
*/
- public DeviceRuntimeException(String msg, Throwable t) {
- super(msg, t);
+ public DeviceRuntimeException(String msg, Throwable t, ErrorIdentifier errorId) {
+ super(msg, t, errorId);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
}
}
diff --git a/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java b/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
index 3e37f20ba..3163b52af 100644
--- a/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
+++ b/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
@@ -244,6 +244,29 @@ public class ContentProviderHandler {
return false;
}
+ /**
+ * Determines if the file or non-empty directory exists on the device.
+ *
+ * @param deviceFilePath The absolute file path on device to check for existence.
+ * @return True if file/directory exists, False otherwise. If directory is empty, it will return
+ * False as well.
+ */
+ public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException {
+ String contentUri = createEscapedContentUri(deviceFilePath);
+ String queryContentCommand =
+ String.format(
+ "content query --user %d --uri %s", mDevice.getCurrentUser(), contentUri);
+
+ String listCommandResult = mDevice.executeShellCommand(queryContentCommand);
+
+ if (NO_RESULTS_STRING.equals(listCommandResult.trim())) {
+ // No file found.
+ return false;
+ }
+
+ return true;
+ }
+
/** Returns true if {@link CommandStatus} is successful and there is no error message. */
private boolean isSuccessful(CommandResult result) {
if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
diff --git a/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java b/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
index caa5f1edd..50084c4cb 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
@@ -47,7 +47,13 @@ public class InvocationMetricLogger {
CF_FETCH_ARTIFACT_TIME("cf_fetch_artifact_time_ms", false),
CF_GCE_CREATE_TIME("cf_gce_create_time_ms", false),
CF_LAUNCH_CVD_TIME("cf_launch_cvd_time_ms", false),
- CF_INSTANCE_COUNT("cf_instance_count", false);
+ CF_INSTANCE_COUNT("cf_instance_count", false),
+ CRASH_FAILURES("crash_failures", true),
+ UNCAUGHT_CRASH_FAILURES("uncaught_crash_failures", true),
+ TEST_CRASH_FAILURES("test_crash_failures", true),
+ UNCAUGHT_TEST_CRASH_FAILURES("uncaught_test_crash_failures", true),
+ DEVICE_RESET_COUNT("device_reset_count", true),
+ DEVICE_RESET_MODULES("device_reset_modules", true);
private final String mKeyName;
// Whether or not to add the value when the key is added again.
diff --git a/invocation_interfaces/com/android/tradefed/result/TestResult.java b/invocation_interfaces/com/android/tradefed/result/TestResult.java
index daee50c26..1b26db68e 100644
--- a/invocation_interfaces/com/android/tradefed/result/TestResult.java
+++ b/invocation_interfaces/com/android/tradefed/result/TestResult.java
@@ -208,6 +208,7 @@ public class TestResult {
int ignored = 0;
int incomplete = 0;
+ TestStatus lastStatus = null;
for (TestResult attempt : results) {
mergedResult.mProtoMetrics.putAll(attempt.getProtoMetrics());
mergedResult.mMetrics.putAll(attempt.getMetrics());
@@ -238,6 +239,7 @@ public class TestResult {
ignored++;
break;
}
+ lastStatus = attempt.getStatus();
}
switch (strategy) {
@@ -258,7 +260,13 @@ public class TestResult {
mergedResult.setStatus(TestStatus.INCOMPLETE);
}
} else {
- mergedResult.setStatus(TestStatus.FAILURE);
+ if (TestStatus.ASSUMPTION_FAILURE.equals(lastStatus)) {
+ mergedResult.setStatus(TestStatus.ASSUMPTION_FAILURE);
+ } else if (TestStatus.IGNORED.equals(lastStatus)) {
+ mergedResult.setStatus(TestStatus.IGNORED);
+ } else {
+ mergedResult.setStatus(TestStatus.FAILURE);
+ }
}
break;
default:
diff --git a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
index a3fdc1e9f..3f3520992 100644
--- a/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
+++ b/invocation_interfaces/com/android/tradefed/result/TestRunResult.java
@@ -307,6 +307,10 @@ public class TestRunResult {
updateTestResult(test, TestStatus.ASSUMPTION_FAILURE, FailureDescription.create(trace));
}
+ public void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+ updateTestResult(test, TestStatus.ASSUMPTION_FAILURE, failure);
+ }
+
public void testIgnored(TestDescription test) {
updateTestResult(test, TestStatus.IGNORED, null);
}
diff --git a/proto/monitoring/server/lab_resource.proto b/proto/monitoring/server/lab_resource.proto
new file mode 100644
index 000000000..5397abe62
--- /dev/null
+++ b/proto/monitoring/server/lab_resource.proto
@@ -0,0 +1,153 @@
+/*
+ * Copyright 2020 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.
+ */
+
+// The protobuf messages for lab host to export metadata and reosurce metrics.
+syntax = "proto3";
+
+package dual_home_lab.monitoring_agent.resource_monitoring;
+
+import "google/protobuf/timestamp.proto";
+
+option java_package = "com.google.dualhomelab.monitoringagent.resourcemonitoring";
+option java_multiple_files = true;
+option java_generic_services = true;
+
+// A tag-value pair message represents the metric value.
+// For example:
+// To represent device disk used percentage.
+// metric {
+// tag: "/data"
+// value: 20.5
+// }
+message Metric {
+ // A string tag associates to the value.
+ string tag = 1;
+ // A float value represents the resource value.
+ float value = 2;
+}
+
+// A message that describes the resource and its metrics.
+// For example:
+// To represent disk space usage values at certain moment.
+// resource {
+// resource_name: "disk_space"
+// resource_instance: "/data"
+// timestamp {
+// seconds: 1589342214
+// }
+// metric: {
+// tag: "avail"
+// value: 20.5
+// }
+// metric: {
+// tag: "used"
+// value: 18.7
+// }
+// metric: {
+// tag: "reserved for root"
+// value: 16.2
+// }
+// }
+message Resource {
+ // A string resource name. ex. "cpu", "memory", "disk_space".
+ string resource_name = 1;
+ // A string reperesent the sub resource name.
+ string resource_instance = 2;
+ // The Metric describe the host or device resource usages.
+ repeated Metric metric = 3;
+ // The collecting timestamp.
+ google.protobuf.Timestamp timestamp = 4;
+}
+
+// A name-value message to represent the metadata attribute.
+// For example:
+// To represent the run target.
+// attribute {
+// name: "run_target"
+// value: "atom-userdebug"
+// }
+// To reperent the pools.
+// attribute {
+// name: "pool"
+// value: "asit"
+// }
+// attribute {
+// name: "pool"
+// value: "apct"
+// }
+message Attribute {
+ string name = 1;
+ string value = 2;
+}
+
+// A message that describes the device state and resource usages.
+// For example:
+// To represent a monitored host
+// host {
+// identifier: {
+// key: "lab_name"
+// value: "us-mtv43"
+// }
+// identifier: {
+// key: "host_name"
+// value: "foo.bar.com"
+// }
+// identifier: {
+// key: "test_harness"
+// value: "tradefed"
+// }
+// attribute: {... check the attribute example above ...}
+// resource: {... check the resource example abobe ...}
+// }
+// To represent a monitored device
+// device {
+// identifier: {
+// key: "device_serial"
+// value: "VVEG-GIDSAN"
+// }
+// attribute: {... check the attribute example above ...}
+// resource: {... check the resource example abobe ...}
+// }
+message MonitoredEntity {
+ // The string map that helps identify the monitored entity
+ map<string, string> identifier = 1;
+ // The attribute messages that describe the device metadata.
+ repeated Attribute attribute = 2;
+ // The resource messages that describe the device state and resource metrics.
+ repeated Resource resource = 3;
+}
+
+// A message that describe the state and resource usages for a lab host and its
+// connected devices.
+message LabResource {
+ MonitoredEntity host = 1;
+ repeated MonitoredEntity device = 2;
+}
+
+// The request message to query the LabResource.
+message LabResourceRequest {}
+
+// The service which is intend to export device metrics and metadata to the host
+// monitoring agent. The host monitoring agent is responsible for
+// collecting host/device metrics and exporting the metrics to user defined
+// cloud PubSub topics.
+service LabResourceService {
+ // Queries lab resource message.
+ rpc GetLabResource(LabResourceRequest) returns (LabResource) {
+ // The http equivalent is curl http://ADDRESS/v1/lab_resource_message
+ // (Assuming your service is hosted at the given 'ADDRESS')
+ }
+}
diff --git a/res/suite/allowed-preparers.txt b/res/suite/allowed-preparers.txt
new file mode 100644
index 000000000..48ed998f2
--- /dev/null
+++ b/res/suite/allowed-preparers.txt
@@ -0,0 +1,4 @@
+com.android.tradefed.targetprep.CrashCollector
+com.android.tradefed.targetprep.DeviceCleaner
+com.android.tradefed.targetprep.RootTargetPreparer
+com.android.tradefed.targetprep.WifiPreparer \ No newline at end of file
diff --git a/src/com/android/tradefed/build/BootstrapBuildProvider.java b/src/com/android/tradefed/build/BootstrapBuildProvider.java
index 383a30cb6..7a22a940b 100644
--- a/src/com/android/tradefed/build/BootstrapBuildProvider.java
+++ b/src/com/android/tradefed/build/BootstrapBuildProvider.java
@@ -90,6 +90,8 @@ public class BootstrapBuildProvider implements IDeviceBuildProvider {
+ "Can be repeated. For example --extra-file file_key_1=/path/to/file")
private Map<String, File> mExtraFiles = new LinkedHashMap<>();
+ private boolean mCreatedTestDir = false;
+
@Override
public IBuildInfo getBuild() throws BuildRetrievalError {
throw new UnsupportedOperationException("Call getBuild(ITestDevice)");
@@ -97,6 +99,9 @@ public class BootstrapBuildProvider implements IDeviceBuildProvider {
@Override
public void cleanUp(IBuildInfo info) {
+ if (mCreatedTestDir) {
+ FileUtil.recursiveDelete(mTestsDir);
+ }
}
@Override
@@ -128,9 +133,9 @@ public class BootstrapBuildProvider implements IDeviceBuildProvider {
info.setFile("testsdir", mTestsDir, info.getBuildId());
}
// Avoid tests dir being null, by creating a temporary dir.
- boolean createdTestDir = false;
+ mCreatedTestDir = false;
if (mTestsDir == null) {
- createdTestDir = true;
+ mCreatedTestDir = true;
try {
mTestsDir =
FileUtil.createTempDir(
@@ -147,7 +152,7 @@ public class BootstrapBuildProvider implements IDeviceBuildProvider {
.put(
FilesKey.TESTS_DIRECTORY,
mTestsDir,
- !createdTestDir /* shouldNotDelete */);
+ !mCreatedTestDir /* shouldNotDelete */);
}
return info;
}
diff --git a/src/com/android/tradefed/build/DependenciesResolver.java b/src/com/android/tradefed/build/DependenciesResolver.java
index f55d0982c..7825f39f8 100644
--- a/src/com/android/tradefed/build/DependenciesResolver.java
+++ b/src/com/android/tradefed/build/DependenciesResolver.java
@@ -88,7 +88,7 @@ public class DependenciesResolver
try {
mTestsDir =
FileUtil.createTempDir(
- "bootstrap-test-dir",
+ "bootstrap-dep-test-dir",
CurrentInvocation.getInfo(InvocationInfo.WORK_FOLDER));
} catch (IOException e) {
throw new BuildRetrievalError(
diff --git a/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java b/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java
index 6e3ffbbd8..e50699c20 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandConfigBuilder.java
@@ -192,7 +192,9 @@ public class ClusterCommandConfigBuilder {
envVars.put("TF_WORK_DIR", mWorkDir.getAbsolutePath());
envVars.putAll(mTestEnvironment.getEnvVars());
envVars.putAll(mTestContext.getEnvVars());
+
for (String serial : mCommand.getTargetDeviceSerials()) {
+ serial = ClusterHostUtil.getLocalDeviceSerial(serial);
IDeviceConfiguration device =
new DeviceConfigurationHolder(String.format("TF_DEVICE_%d", index++));
device.getDeviceRequirements().setSerial(serial);
@@ -203,6 +205,11 @@ public class ClusterCommandConfigBuilder {
}
deviceConfigs.get(0).addSpecificConfig(new ClusterBuildProvider());
config.setDeviceConfigList(deviceConfigs);
+ // Perform target preparation in parallel with an unlimited timeout
+ // TODO(b/166455187): Consider making parallel setup options configurable
+ config.injectOptionValue("parallel-setup", "true");
+ config.injectOptionValue("parallel-setup-timeout", "0");
+
config.setTest(new ClusterCommandLauncher());
config.setLogSaver(new ClusterLogSaver());
// TODO(b/135636270): return log path to TFC instead of relying on a specific filename
diff --git a/src/com/android/tradefed/cluster/ClusterCommandEvent.java b/src/com/android/tradefed/cluster/ClusterCommandEvent.java
index 725e48e72..46db15c27 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandEvent.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandEvent.java
@@ -38,6 +38,7 @@ public class ClusterCommandEvent implements IClusterEvent {
public static final String DATA_KEY_PASSED_TEST_COUNT = "passed_test_count";
public static final String DATA_KEY_FAILED_TEST_RUN_COUNT = "failed_test_run_count";
public static final String DATA_KEY_LOST_DEVICE_DETECTED = "device_lost_detected";
+ public static final String DATA_KEY_SUBPROCESS_COMMAND_ERROR = "subprocess_command_error";
// Maximum size of an individual data string value.
public static final int MAX_DATA_STRING_SIZE = 4095;
diff --git a/src/com/android/tradefed/cluster/ClusterCommandLauncher.java b/src/com/android/tradefed/cluster/ClusterCommandLauncher.java
index a79a60588..099220257 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandLauncher.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandLauncher.java
@@ -42,6 +42,7 @@ import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.StreamUtil;
import com.android.tradefed.util.StringEscapeUtils;
import com.android.tradefed.util.StringUtil;
+import com.android.tradefed.util.SubprocessEventHelper.InvocationFailedEventInfo;
import com.android.tradefed.util.SubprocessTestResultsParser;
import com.android.tradefed.util.SystemUtil;
@@ -192,12 +193,26 @@ public class ClusterCommandLauncher
javaCommandArgs.toArray(new String[javaCommandArgs.size()]));
if (!result.getStatus().equals(CommandStatus.SUCCESS)) {
String error = null;
+ Throwable cause = null;
if (result.getStatus().equals(CommandStatus.TIMED_OUT)) {
- error = "timeout";
+ error =
+ String.format(
+ "Command timed out after %sms",
+ mConfiguration.getCommandOptions().getInvocationTimeout());
} else {
- error = FileUtil.readStringFromFile(stderrFile);
+ error =
+ String.format(
+ "Command finished unsuccessfully: status=%s, exit_code=%s",
+ result.getStatus(), result.getExitCode());
+ InvocationFailedEventInfo errorInfo =
+ subprocessEventParser.getReportedInvocationFailedEventInfo();
+ if (errorInfo != null) {
+ cause = errorInfo.mCause;
+ } else {
+ cause = new Throwable(FileUtil.readStringFromFile(stderrFile));
+ }
}
- throw new RuntimeException(String.format("Command failed to run: %s", error));
+ throw new SubprocessCommandException(error, cause);
}
CLog.i("Successfully ran a command");
diff --git a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
index 2e09168fb..075b3e83b 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
@@ -145,6 +145,7 @@ public class ClusterCommandScheduler extends CommandScheduler {
private String mSummary;
private Set<String> processedSummaries = new HashSet<>();
private String mError;
+ private String mSubprocessCommandError;
private File mWorkDir;
private InvocationStatus mInvocationStatus;
@@ -261,6 +262,10 @@ public class ClusterCommandScheduler extends CommandScheduler {
super.invocationFailed(cause);
mError = StreamUtil.getStackTrace(cause);
+ if (cause instanceof SubprocessCommandException && cause.getCause() != null) {
+ // The inner exception holds an exception stack trace from a subprocess.
+ mSubprocessCommandError = cause.getCause().getMessage();
+ }
}
/** {@inheritDoc} */
@@ -272,6 +277,9 @@ public class ClusterCommandScheduler extends CommandScheduler {
createEventBuilder()
.setType(ClusterCommandEvent.Type.InvocationEnded)
.setData(ClusterCommandEvent.DATA_KEY_ERROR, mError)
+ .setData(
+ ClusterCommandEvent.DATA_KEY_SUBPROCESS_COMMAND_ERROR,
+ mSubprocessCommandError)
.build();
getClusterClient().getCommandEventUploader().postEvent(event);
getClusterClient().getCommandEventUploader().flush();
@@ -318,6 +326,9 @@ public class ClusterCommandScheduler extends CommandScheduler {
.setType(ClusterCommandEvent.Type.InvocationCompleted)
.setInvocationStatus(mInvocationStatus)
.setData(ClusterCommandEvent.DATA_KEY_ERROR, mError)
+ .setData(
+ ClusterCommandEvent.DATA_KEY_SUBPROCESS_COMMAND_ERROR,
+ mSubprocessCommandError)
.setData(ClusterCommandEvent.DATA_KEY_SUMMARY, mSummary)
.setData(
ClusterCommandEvent.DATA_KEY_FETCH_BUILD_TIME_MILLIS,
@@ -497,7 +508,6 @@ public class ClusterCommandScheduler extends CommandScheduler {
String runTarget =
ClusterHostUtil.getRunTarget(
device, runTargetFormat, getClusterOptions().getDeviceTag());
- CLog.d("%s is available", runTarget);
devices.put(runTarget, device);
}
return devices;
@@ -605,6 +615,9 @@ public class ClusterCommandScheduler extends CommandScheduler {
ClusterCommandEvent.createEventBuilder(commandTask)
.setHostName(ClusterHostUtil.getHostName())
.setType(ClusterCommandEvent.Type.AllocationFailed)
+ .setData(
+ ClusterCommandEvent.DATA_KEY_ERROR,
+ StreamUtil.getStackTrace(e))
.build());
eventUploader.flush();
} catch (ConfigurationException | IOException | JSONException e) {
@@ -640,7 +653,7 @@ public class ClusterCommandScheduler extends CommandScheduler {
if (commandTask.getTargetDeviceSerials() != null) {
for (String serial : commandTask.getTargetDeviceSerials()) {
cmdLine += " --serial ";
- cmdLine += serial;
+ cmdLine += ClusterHostUtil.getLocalDeviceSerial(serial);
}
}
CLog.i("executing cluster command: [%s] %s", commandTask.getTaskId(), cmdLine);
diff --git a/src/com/android/tradefed/cluster/ClusterDeviceInfo.java b/src/com/android/tradefed/cluster/ClusterDeviceInfo.java
index 23c39eb23..0fcf3460c 100644
--- a/src/com/android/tradefed/cluster/ClusterDeviceInfo.java
+++ b/src/com/android/tradefed/cluster/ClusterDeviceInfo.java
@@ -82,7 +82,7 @@ public class ClusterDeviceInfo {
*/
public JSONObject toJSON() throws JSONException {
final JSONObject json = new JSONObject();
- json.put("device_serial", mDeviceDescriptor.getSerial());
+ json.put("device_serial", ClusterHostUtil.getUniqueDeviceSerial(mDeviceDescriptor));
json.put("run_target", mRunTarget);
json.put("build_id", mDeviceDescriptor.getBuildId());
json.put("product", mDeviceDescriptor.getProduct());
diff --git a/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java b/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java
index 33175d457..c8f0fa598 100644
--- a/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java
+++ b/src/com/android/tradefed/cluster/ClusterDeviceMonitor.java
@@ -108,7 +108,12 @@ public class ClusterDeviceMonitor implements IDeviceMonitor {
.setHostName(ClusterHostUtil.getHostName())
.setTfVersion(ClusterHostUtil.getTfVersion())
.setData(getAdditionalHostInfo())
- .setData("host_ip", ClusterHostUtil.getHostIpAddress())
+ .setData(
+ ClusterHostEvent.HOST_IP_KEY,
+ ClusterHostUtil.getHostIpAddress())
+ .setData(
+ ClusterHostEvent.LABEL_KEY,
+ String.join(",", getClusterOptions().getLabels()))
.setClusterId(getClusterOptions().getClusterId())
.setNextClusterIds(getClusterOptions().getNextClusterIds())
.setLabName(getClusterOptions().getLabName());
diff --git a/src/com/android/tradefed/cluster/ClusterHostEvent.java b/src/com/android/tradefed/cluster/ClusterHostEvent.java
index 8a56e93dd..b3bb4304d 100644
--- a/src/com/android/tradefed/cluster/ClusterHostEvent.java
+++ b/src/com/android/tradefed/cluster/ClusterHostEvent.java
@@ -37,6 +37,8 @@ public class ClusterHostEvent implements IClusterEvent {
private Map<String, String> mData = new HashMap<>();
private String mLabName;
public static final String EVENT_QUEUE = "host-event-queue";
+ public static final String LABEL_KEY = "label";
+ public static final String HOST_IP_KEY = "host_ip";
/** Enums of the different types of host events. */
public enum HostEventType {
diff --git a/src/com/android/tradefed/cluster/ClusterHostUtil.java b/src/com/android/tradefed/cluster/ClusterHostUtil.java
index 0e167b910..5bc620936 100644
--- a/src/com/android/tradefed/cluster/ClusterHostUtil.java
+++ b/src/com/android/tradefed/cluster/ClusterHostUtil.java
@@ -27,13 +27,22 @@ import com.google.common.net.HostAndPort;
import com.google.common.net.InetAddresses;
import com.google.common.primitives.Longs;
+import java.net.Inet6Address;
import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
import java.net.UnknownHostException;
import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.List;
import java.util.Map;
+import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+
/** Static util functions for TF Cluster to get global config instances, host information, etc. */
public class ClusterHostUtil {
@@ -42,39 +51,113 @@ public class ClusterHostUtil {
private static String sHostIpAddress = null;
static final String DEFAULT_TF_VERSION = "(unknown)";
+ static final String EMULATOR_SERIAL_PREFIX = "emulator-";
+ static final String NULL_DEVICE_SERIAL_PLACEHOLDER = "(no device serial)";
+ static final String UNKNOWN = "UNKNOWN";
private static long sTfStartTime = getCurrentTimeMillis();
/**
* Gets the hostname.
*
+ * <p>1. Try to get hostname from InetAddress. 2. If fail, try to get hostname from HOSTNAME
+ * env. 3. If not set, generate a unique hostname.
+ *
* @return the hostname or null if we were unable to fetch it.
*/
public static String getHostName() {
- if (sHostName == null) {
- try {
- sHostName = InetAddress.getLocalHost().getHostName();
- } catch (UnknownHostException e) {
- CLog.w("failed to get hostname: %s", e);
- }
+ if (sHostName != null) {
+ return sHostName;
+ }
+ try {
+ sHostName = InetAddress.getLocalHost().getHostName();
+ return sHostName;
+ } catch (UnknownHostException e) {
+ CLog.w("Failed to get hostname from InetAddress: %s", e);
+ }
+ CLog.i("Get hostname from HOSTNAME env.");
+ sHostName = System.getenv("HOSTNAME");
+ if (!Strings.isNullOrEmpty(sHostName)) {
+ return sHostName;
}
+ sHostName = "unknown-" + UUID.randomUUID().toString();
+ CLog.i("No HOSTNAME env set. Generate hostname: %s.", sHostName);
return sHostName;
}
/**
+ * Returns a unique device serial for a device.
+ *
+ * <p>Non-physical devices (e.g. emulator) have pseudo serials which are not unique across
+ * hosts. This method prefixes those with a hostname to make them unique.
+ *
+ * @param device a device descriptor.
+ * @return a unique device serial.
+ */
+ public static String getUniqueDeviceSerial(DeviceDescriptor device) {
+ String serial = device.getSerial();
+ if (Strings.isNullOrEmpty(serial)
+ || device.isStubDevice()
+ || serial.startsWith(EMULATOR_SERIAL_PREFIX)) {
+ if (Strings.isNullOrEmpty(serial)) {
+ serial = NULL_DEVICE_SERIAL_PLACEHOLDER;
+ }
+ serial = String.format("%s:%s", getHostName(), serial);
+ }
+ return serial;
+ }
+
+ /**
+ * Returns a local device serial for a given unique device serial.
+ *
+ * <p>TFC sends down unique device serials for non-physical devices which TF does not
+ * understand. This method converts them back to local device serials.
+ *
+ * @param serial a unique device serial from TFC.
+ * @return a local device serial.
+ */
+ public static String getLocalDeviceSerial(String serial) {
+ String prefix = getHostName() + ":";
+ if (serial.startsWith(prefix)) {
+ return serial.substring(prefix.length());
+ }
+ return serial;
+ }
+ /**
* Gets the IP address.
*
- * @return the IP address or null if we were unable to fetch it.
+ * @return the IPV4 address String or "UNKNOWN" if we were unable to fetch it.
*/
public static String getHostIpAddress() {
if (sHostIpAddress == null) {
+ List<InetAddress> addresses = new ArrayList<>();
try {
- sHostIpAddress = InetAddress.getLocalHost().getHostAddress();
- } catch (UnknownHostException e) {
- CLog.w("failed to get hostname: %s", e);
+ Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
+ if (interfaces == null) {
+ return UNKNOWN;
+ }
+ for (NetworkInterface networkInterface : Collections.list(interfaces)) {
+ if (!networkInterface.isUp() || networkInterface.isLoopback()) {
+ continue;
+ }
+ for (InetAddress address :
+ Collections.list(networkInterface.getInetAddresses())) {
+ if (address.isLinkLocalAddress()
+ || address.isLoopbackAddress()
+ || address instanceof Inet6Address) {
+ continue;
+ }
+ addresses.add(address);
+ }
+ }
+ } catch (SocketException e) {
+ CLog.w(e);
+ }
+ if (!addresses.isEmpty()) {
+ sHostIpAddress = addresses.get(0).getHostAddress();
}
}
- return sHostIpAddress;
+ return sHostIpAddress == null ? UNKNOWN : sHostIpAddress;
}
/**
@@ -143,7 +226,7 @@ public class ClusterHostUtil {
txt = device.getDeviceClass();
break;
case "SERIAL":
- txt = device.getSerial();
+ txt = getUniqueDeviceSerial(device);
break;
case "TAG":
if (deviceTags == null || deviceTags.isEmpty()) {
diff --git a/src/com/android/tradefed/cluster/SubprocessCommandException.java b/src/com/android/tradefed/cluster/SubprocessCommandException.java
new file mode 100644
index 000000000..3be5dc980
--- /dev/null
+++ b/src/com/android/tradefed/cluster/SubprocessCommandException.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2020 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.cluster;
+
+/** A subprocess command failed to run. */
+public class SubprocessCommandException extends RuntimeException {
+
+ public SubprocessCommandException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java b/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java
index a69dc8645..6952542fb 100644
--- a/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java
+++ b/src/com/android/tradefed/cluster/SubprocessConfigBuilder.java
@@ -119,8 +119,12 @@ public class SubprocessConfigBuilder {
in = loader.getResourceAsStream(String.format("config/%s", mOriginalConfig));
}
if (in == null) {
+ File f = new File(mOriginalConfig);
+ if (!f.isAbsolute()) {
+ f = new File(mWorkDir, mOriginalConfig);
+ }
try {
- in = new FileInputStream(mOriginalConfig);
+ in = new FileInputStream(f);
} catch (FileNotFoundException e) {
throw new RuntimeException(
String.format("Could not find configuration '%s'", mOriginalConfig));
@@ -143,7 +147,14 @@ public class SubprocessConfigBuilder {
root.appendChild(reporter);
}
- File f = new File(mWorkDir, createConfigName(mOriginalConfig));
+ File f = new File(mWorkDir, mOriginalConfig);
+ if (!f.exists() || !f.isFile()) {
+ // If the original config is an existing file, we need to update it since some old TFs
+ // check the file system first before bundled configs when loading configs.
+ // If the original config is not an existing file, we can use any name since the
+ // original config name will be assigned when creating a injection jar.
+ f = File.createTempFile("subprocess_config_", ".xml", mWorkDir);
+ }
TransformerFactory transformerFactory = TransformerFactory.newInstance();
try {
Transformer transformer = transformerFactory.newTransformer();
diff --git a/src/com/android/tradefed/cluster/SubprocessReportingHelper.java b/src/com/android/tradefed/cluster/SubprocessReportingHelper.java
index e65722655..494485948 100644
--- a/src/com/android/tradefed/cluster/SubprocessReportingHelper.java
+++ b/src/com/android/tradefed/cluster/SubprocessReportingHelper.java
@@ -42,7 +42,8 @@ public class SubprocessReportingHelper {
private static final String REPORTER_JAR_NAME = "subprocess-results-reporter.jar";
private static final String CLASS_FILTER =
String.format(
- "(^%s|^%s|^%s|^%s|^%s).*class$",
+ "(^%s|^%s|^%s|^%s|^%s|^%s).*class$",
+ "ErrorIdentifier",
"LegacySubprocessResultsReporter",
"SubprocessTestResultsParser",
"SubprocessEventHelper",
diff --git a/src/com/android/tradefed/command/CommandOptions.java b/src/com/android/tradefed/command/CommandOptions.java
index 1760e9cf8..b3c5ed366 100644
--- a/src/com/android/tradefed/command/CommandOptions.java
+++ b/src/com/android/tradefed/command/CommandOptions.java
@@ -23,6 +23,7 @@ import com.android.tradefed.device.metric.AutoLogCollector;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.UniqueMultiMap;
+import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
@@ -108,6 +109,10 @@ public class CommandOptions implements ICommandOptions {
"[0, shard-count)")
private Integer mShardIndex;
+ @Option(name = "optimize-mainline-test", description =
+ "Whether or not to optimize the list of test modules for mainline.")
+ private boolean mOptimizeMainlineTest;
+
@Option(
name = "enable-token-sharding",
description = "Whether or not to allow sharding with the token support enabled."
@@ -161,6 +166,12 @@ public class CommandOptions implements ICommandOptions {
"For remote sharded invocation, whether or not to attempt the setup in parallel.")
private boolean mUseParallelRemoteSetup = false;
+ @Option(name = "parallel-setup", description = "Whether to attempt the setup in parallel.")
+ private boolean mUseParallelSetup = false;
+
+ @Option(name = "parallel-setup-timeout", description = "Timeout to use during parallel setup.")
+ private Duration mParallelSetupTimeout = Duration.ofMinutes(30L);
+
@Option(
name = "replicate-parent-setup",
description =
@@ -370,6 +381,14 @@ public class CommandOptions implements ICommandOptions {
* {@inheritDoc}
*/
@Override
+ public boolean getOptimizeMainlineTest() {
+ return mOptimizeMainlineTest;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
public Integer getShardCount() {
return mShardCount;
}
@@ -514,6 +533,18 @@ public class CommandOptions implements ICommandOptions {
/** {@inheritDoc} */
@Override
+ public boolean shouldUseParallelSetup() {
+ return mUseParallelSetup;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Duration getParallelSetupTimeout() {
+ return mParallelSetupTimeout;
+ }
+
+ /** {@inheritDoc} */
+ @Override
public boolean shouldUseReplicateSetup() {
return mReplicateParentSetup;
}
diff --git a/src/com/android/tradefed/command/CommandRunner.java b/src/com/android/tradefed/command/CommandRunner.java
index fe77903cf..0cb94feab 100644
--- a/src/com/android/tradefed/command/CommandRunner.java
+++ b/src/com/android/tradefed/command/CommandRunner.java
@@ -21,6 +21,7 @@ import com.android.tradefed.clearcut.TerminateClearcutClient;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.device.NoDeviceException;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.SerializationUtil;
@@ -119,7 +120,10 @@ public class CommandRunner {
// After 1 min we check if the command was executed.
if (mScheduler.getReadyCommandCount() > 0
&& mScheduler.getExecutingCommandCount() == 0) {
- printStackTrace(new NoDeviceException("No device was allocated for the command."));
+ printStackTrace(
+ new NoDeviceException(
+ "No device was allocated for the command.",
+ InfraErrorIdentifier.RUNNER_ALLOCATION_ERROR));
mErrorCode = ExitCode.NO_DEVICE_ALLOCATED;
mScheduler.removeAllCommands();
mScheduler.shutdown();
diff --git a/src/com/android/tradefed/command/CommandScheduler.java b/src/com/android/tradefed/command/CommandScheduler.java
index 722884090..0ee2ca021 100644
--- a/src/com/android/tradefed/command/CommandScheduler.java
+++ b/src/com/android/tradefed/command/CommandScheduler.java
@@ -69,6 +69,7 @@ import com.android.tradefed.log.LogRegistry;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.ResultForwarder;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.result.suite.SuiteResultReporter;
import com.android.tradefed.sandbox.ISandbox;
import com.android.tradefed.testtype.IRemoteTest;
@@ -1075,8 +1076,9 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm
IConfiguration config = cmd.getConfiguration();
IInvocationContext context = new InvocationContext();
context.setConfigurationDescriptor(config.getConfigurationDescription());
- Map<String, ITestDevice> devices = allocateDevices(config, manager);
- if (!devices.isEmpty()) {
+ DeviceAllocationResult allocationResults = allocateDevices(config, manager);
+ if (allocationResults.wasAllocationSuccessful()) {
+ Map<String, ITestDevice> devices = allocationResults.getAllocatedDevices();
cmdIter.remove();
mExecutingCommands.add(cmd);
context.addAllocatedDevice(devices);
@@ -1534,8 +1536,9 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm
ExecutableCommand execCmd = createExecutableCommand(cmdTracker, config, false);
context.setConfigurationDescriptor(config.getConfigurationDescription());
- Map<String, ITestDevice> devices = allocateDevices(config, manager);
- if (!devices.isEmpty()) {
+ DeviceAllocationResult allocationResults = allocateDevices(config, manager);
+ if (allocationResults.wasAllocationSuccessful()) {
+ Map<String, ITestDevice> devices = allocationResults.getAllocatedDevices();
context.addAllocatedDevice(devices);
synchronized (this) {
mExecutingCommands.add(execCmd);
@@ -1543,8 +1546,16 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm
CLog.d("Executing '%s' on '%s'", cmdTracker.getArgs()[0], devices);
startInvocation(context, execCmd, listener, new FreeDeviceHandler(manager));
} else {
+ // Log adb output just to help debug
+ String adbOutput =
+ ((DeviceManager) GlobalConfiguration.getDeviceManagerInstance())
+ .executeGlobalAdbCommand("devices");
+ CLog.e("'adb devices' output:\n%s", adbOutput);
throw new NoDeviceException(
- "no devices is available for command: " + Arrays.asList(args));
+ String.format(
+ "no devices is available for command: %s\n%s",
+ Arrays.asList(args), allocationResults.formattedReason()),
+ InfraErrorIdentifier.SCHEDULER_ALLOCATION_ERROR);
}
}
@@ -1585,11 +1596,12 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm
/**
* Allocate devices for a config.
+ *
* @param config a {@link IConfiguration} has device requirements.
* @param manager a {@link IDeviceManager}
* @return allocated devices
*/
- Map<String, ITestDevice> allocateDevices(IConfiguration config, IDeviceManager manager) {
+ DeviceAllocationResult allocateDevices(IConfiguration config, IDeviceManager manager) {
Map<String, ITestDevice> devices = new LinkedHashMap<String, ITestDevice>();
ITestDevice device = null;
if (config.getDeviceConfig().isEmpty()) {
@@ -1598,6 +1610,7 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm
// If we need to replicate the setup on all devices
ParentShardReplicate.replicatedSetup(config, getKeyStoreClient());
synchronized(this) {
+ DeviceAllocationResult allocationResults = new DeviceAllocationResult();
for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) {
device =
manager.allocateDevice(
@@ -1605,6 +1618,9 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm
if (device != null) {
devices.put(deviceConfig.getDeviceName(), device);
} else {
+ allocationResults.addAllocationFailureReason(
+ deviceConfig.getDeviceName(),
+ deviceConfig.getDeviceRequirements().getNoMatchReason());
// If one of the several device cannot be allocated, we de-allocate
// all the previous one.
for (ITestDevice allocatedDevice : devices.values()) {
@@ -1623,7 +1639,8 @@ public class CommandScheduler extends Thread implements ICommandScheduler, IComm
break;
}
}
- return devices;
+ allocationResults.addAllocatedDevices(devices);
+ return allocationResults;
}
}
diff --git a/src/com/android/tradefed/command/DeviceAllocationResult.java b/src/com/android/tradefed/command/DeviceAllocationResult.java
new file mode 100644
index 000000000..849b04a5f
--- /dev/null
+++ b/src/com/android/tradefed/command/DeviceAllocationResult.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2020 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.command;
+
+import com.android.tradefed.device.ITestDevice;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/** Represents the results of an allocation attempt for a command. */
+public class DeviceAllocationResult {
+
+ private Map<String, String> mNotAllocatedReason = new LinkedHashMap<>();
+ private Map<String, ITestDevice> mAllocatedDevices = new LinkedHashMap<>();
+
+ /** returns whether or not the allocation was successful. */
+ public boolean wasAllocationSuccessful() {
+ return !mAllocatedDevices.isEmpty();
+ }
+
+ /** Add devices that have been allocated. */
+ public void addAllocatedDevices(Map<String, ITestDevice> devices) {
+ mAllocatedDevices.putAll(devices);
+ }
+
+ /** Add the reasons for not being allocated for each device config. */
+ public void addAllocationFailureReason(String deviceConfigName, Map<String, String> reasons) {
+ mNotAllocatedReason.put(deviceConfigName, createReasonMessage(reasons));
+ }
+
+ /** Returns the map of allocated devices */
+ public Map<String, ITestDevice> getAllocatedDevices() {
+ return mAllocatedDevices;
+ }
+
+ public String formattedReason() {
+ if (mNotAllocatedReason.size() == 1) {
+ return mNotAllocatedReason.values().iterator().next().toString();
+ }
+ return mNotAllocatedReason.toString();
+ }
+
+ private String createReasonMessage(Map<String, String> reasons) {
+ StringBuilder sb = new StringBuilder();
+ for (String serial : reasons.keySet()) {
+ String reason = reasons.get(serial);
+ if (reason == null) {
+ reason = "No reason provided";
+ }
+ sb.append(String.format("device '%s': %s", serial, reason));
+ }
+ return sb.toString();
+ }
+}
diff --git a/src/com/android/tradefed/command/ICommandOptions.java b/src/com/android/tradefed/command/ICommandOptions.java
index 37dd02215..ea2ce99f4 100644
--- a/src/com/android/tradefed/command/ICommandOptions.java
+++ b/src/com/android/tradefed/command/ICommandOptions.java
@@ -19,6 +19,7 @@ package com.android.tradefed.command;
import com.android.tradefed.device.metric.AutoLogCollector;
import com.android.tradefed.util.UniqueMultiMap;
+import java.time.Duration;
import java.util.Map;
import java.util.Set;
@@ -120,6 +121,10 @@ public interface ICommandOptions {
*/
public void setInvocationTimeout(Long mInvocationTimeout);
+
+ /** Returns true if we should optimize the list of test modules for mainline test. */
+ public boolean getOptimizeMainlineTest();
+
/**
* Return the total shard count for the command.
*/
@@ -185,6 +190,12 @@ public interface ICommandOptions {
/** Whether or not to attempt parallel setup of the remote devices. */
public boolean shouldUseParallelRemoteSetup();
+ /** Whether or not to attempt parallel setup. */
+ public boolean shouldUseParallelSetup();
+
+ /** Returns the timeout to use during parallel setups. */
+ public Duration getParallelSetupTimeout();
+
/** Whether or not to use replicated setup for all the remote devices. */
public boolean shouldUseReplicateSetup();
diff --git a/src/com/android/tradefed/config/proxy/TradefedDelegator.java b/src/com/android/tradefed/config/proxy/TradefedDelegator.java
index 19c9ae972..17af0196d 100644
--- a/src/com/android/tradefed/config/proxy/TradefedDelegator.java
+++ b/src/com/android/tradefed/config/proxy/TradefedDelegator.java
@@ -18,15 +18,17 @@ package com.android.tradefed.config.proxy;
import com.android.tradefed.command.CommandOptions;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.Option;
+import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.UniqueMultiMap;
import com.google.common.base.Joiner;
import java.io.File;
-import java.io.FileFilter;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.Set;
/** Objects that helps delegating the invocation to another Tradefed binary. */
public class TradefedDelegator {
@@ -60,16 +62,8 @@ public class TradefedDelegator {
}
/** Creates the classpath out of the jars in the directory. */
- public String createClasspath() {
- List<File> jars =
- Arrays.asList(
- mDelegatedTfRootDir.listFiles(
- new FileFilter() {
- @Override
- public boolean accept(File pathname) {
- return pathname.getName().endsWith(".jar");
- }
- }));
+ public String createClasspath() throws IOException {
+ Set<File> jars = FileUtil.findFilesObject(mDelegatedTfRootDir, ".*\\.jar");
return Joiner.on(":").join(jars);
}
@@ -96,10 +90,17 @@ public class TradefedDelegator {
*/
public static String[] clearCommandline(String[] originalCommand)
throws ConfigurationException {
+ String[] commandLine = clearCommandlineFromOneArg(originalCommand, DELETEGATED_OPTION_NAME);
+ return commandLine;
+ }
+
+ /** Remove a given option from the command line. */
+ private static String[] clearCommandlineFromOneArg(String[] originalCommand, String optionName)
+ throws ConfigurationException {
List<String> argsList = new ArrayList<>(Arrays.asList(originalCommand));
try {
- while (argsList.contains("--" + DELETEGATED_OPTION_NAME)) {
- int index = argsList.indexOf("--" + DELETEGATED_OPTION_NAME);
+ while (argsList.contains("--" + optionName)) {
+ int index = argsList.indexOf("--" + optionName);
if (index != -1) {
argsList.remove(index + 1);
argsList.remove(index);
diff --git a/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java b/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java
index 1aef60cc0..7a847710c 100644
--- a/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java
+++ b/src/com/android/tradefed/config/yaml/ConfigurationYamlParser.java
@@ -21,6 +21,7 @@ import com.android.tradefed.config.ConfigurationDef;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.config.yaml.IDefaultObjectLoader.LoaderConfiguration;
+import com.android.tradefed.config.yaml.YamlClassOptionsParser.ClassAndOptions;
import com.google.common.collect.ImmutableList;
@@ -40,6 +41,8 @@ import java.util.Set;
public final class ConfigurationYamlParser {
private static final String DESCRIPTION_KEY = "description";
+ public static final String PRE_SETUP_ACTION_KEY = "pre_setup_action";
+ public static final String POST_SETUP_ACTION_KEY = "post_setup_action";
public static final String DEPENDENCIES_KEY = "dependencies";
public static final String TESTS_KEY = "tests";
@@ -89,6 +92,16 @@ public final class ConfigurationYamlParser {
mSeenKeys.add(DESCRIPTION_KEY);
}
Set<String> dependencyFiles = new LinkedHashSet<>();
+ if (yamlObjects.containsKey(PRE_SETUP_ACTION_KEY)) {
+ YamlClassOptionsParser classAndOptions =
+ new YamlClassOptionsParser(
+ "action",
+ PRE_SETUP_ACTION_KEY,
+ (List<Map<String, Object>>) yamlObjects.get(PRE_SETUP_ACTION_KEY));
+ mSeenKeys.add(PRE_SETUP_ACTION_KEY);
+ convertClassAndOptionsToObjects(
+ configDef, classAndOptions, Configuration.TARGET_PREPARER_TYPE_NAME);
+ }
if (yamlObjects.containsKey(DEPENDENCIES_KEY)) {
YamlTestDependencies testDeps =
new YamlTestDependencies(
@@ -97,12 +110,24 @@ public final class ConfigurationYamlParser {
mSeenKeys.add(DEPENDENCIES_KEY);
}
if (yamlObjects.containsKey(TESTS_KEY)) {
- YamlTestRunners runnerInfo =
- new YamlTestRunners((List<Map<String, Object>>) yamlObjects.get(TESTS_KEY));
+ YamlClassOptionsParser runnerInfo =
+ new YamlClassOptionsParser(
+ "test",
+ TESTS_KEY,
+ (List<Map<String, Object>>) yamlObjects.get(TESTS_KEY));
mSeenKeys.add(TESTS_KEY);
- convertTestsToObjects(configDef, runnerInfo);
+ convertClassAndOptionsToObjects(configDef, runnerInfo, Configuration.TEST_TYPE_NAME);
+ }
+ if (yamlObjects.containsKey(POST_SETUP_ACTION_KEY)) {
+ YamlClassOptionsParser runnerInfo =
+ new YamlClassOptionsParser(
+ "action",
+ POST_SETUP_ACTION_KEY,
+ (List<Map<String, Object>>) yamlObjects.get(POST_SETUP_ACTION_KEY));
+ mSeenKeys.add(POST_SETUP_ACTION_KEY);
+ convertClassAndOptionsToObjects(
+ configDef, runnerInfo, Configuration.TARGET_PREPARER_TYPE_NAME);
}
-
if (!mSeenKeys.containsAll(REQUIRED_KEYS)) {
Set<String> missingKeys = new HashSet<>(REQUIRED_KEYS);
missingKeys.removeAll(mSeenKeys);
@@ -185,27 +210,26 @@ public final class ConfigurationYamlParser {
return dependencies;
}
- private void convertTestsToObjects(ConfigurationDef def, YamlTestRunners tests) {
- if (tests.getRunner() == null) {
+ private void convertClassAndOptionsToObjects(
+ ConfigurationDef def, YamlClassOptionsParser tests, String configObjType) {
+ if (tests.getClassesAndOptions().isEmpty()) {
return;
}
- String className = tests.getRunner();
- int classCount = def.addConfigObjectDef(Configuration.TEST_TYPE_NAME, className);
- for (Entry<String, String> options : tests.getOptions().entries()) {
- String optionName =
- String.format(
- "%s%c%d%c%s",
- className,
- OptionSetter.NAMESPACE_SEPARATOR,
- classCount,
- OptionSetter.NAMESPACE_SEPARATOR,
- options.getKey());
- def.addOptionDef(
- optionName,
- null,
- options.getValue(),
- def.getName(),
- Configuration.TEST_TYPE_NAME);
+ for (ClassAndOptions classOptions : tests.getClassesAndOptions()) {
+ String className = classOptions.mClass;
+ int classCount = def.addConfigObjectDef(configObjType, className);
+ for (Entry<String, String> options : classOptions.mOptions.entries()) {
+ String optionName =
+ String.format(
+ "%s%c%d%c%s",
+ className,
+ OptionSetter.NAMESPACE_SEPARATOR,
+ classCount,
+ OptionSetter.NAMESPACE_SEPARATOR,
+ options.getKey());
+ def.addOptionDef(
+ optionName, null, options.getValue(), def.getName(), configObjType);
+ }
}
}
}
diff --git a/src/com/android/tradefed/config/yaml/YamlTestRunners.java b/src/com/android/tradefed/config/yaml/YamlClassOptionsParser.java
index 1c8952e6b..3dcdae1fc 100644
--- a/src/com/android/tradefed/config/yaml/YamlTestRunners.java
+++ b/src/com/android/tradefed/config/yaml/YamlClassOptionsParser.java
@@ -20,57 +20,54 @@ import com.android.tradefed.config.ConfigurationException;
import com.google.common.collect.LinkedListMultimap;
import com.google.common.collect.Multimap;
+import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/** Helper to parse test runner information from the YAML Tradefed Configuration. */
-public class YamlTestRunners {
+public class YamlClassOptionsParser {
- private static final String TEST_KEY = "test";
- private static final String TEST_NAME_KEY = "name";
+ private static final String CLASS_NAME_KEY = "name";
private static final String OPTIONS_KEY = "options";
- private String mRunner;
- private Multimap<String, String> mOptions = LinkedListMultimap.create();
+ class ClassAndOptions {
+ public String mClass;
+ public Multimap<String, String> mOptions = LinkedListMultimap.create();
+ }
- public YamlTestRunners(List<Map<String, Object>> tests) throws ConfigurationException {
- if (tests.size() > 1) {
- throw new ConfigurationException("Currently only support one runner at a time.");
- }
+ private List<ClassAndOptions> mListClassAndOptions = new ArrayList<>();
+
+ public YamlClassOptionsParser(String mainkey, String category, List<Map<String, Object>> tests)
+ throws ConfigurationException {
for (Map<String, Object> runnerEntry : tests) {
- if (runnerEntry.containsKey(TEST_KEY)) {
+ if (runnerEntry.containsKey(mainkey)) {
+ ClassAndOptions classOptions = new ClassAndOptions();
+ mListClassAndOptions.add(classOptions);
for (Entry<String, Object> entry :
- ((Map<String, Object>) runnerEntry.get(TEST_KEY)).entrySet()) {
- if (TEST_NAME_KEY.equals(entry.getKey())) {
- mRunner = (String) entry.getValue();
+ ((Map<String, Object>) runnerEntry.get(mainkey)).entrySet()) {
+ if (CLASS_NAME_KEY.equals(entry.getKey())) {
+ classOptions.mClass = (String) entry.getValue();
}
if (OPTIONS_KEY.equals(entry.getKey())) {
for (Map<String, Object> optionMap :
(List<Map<String, Object>>) entry.getValue()) {
for (Entry<String, Object> optionVal : optionMap.entrySet()) {
// TODO: Support map option
- mOptions.put(optionVal.getKey(), optionVal.getValue().toString());
+ classOptions.mOptions.put(
+ optionVal.getKey(), optionVal.getValue().toString());
}
}
}
}
} else {
throw new ConfigurationException(
- String.format(
- "'%s' key is mandatory in '%s'",
- TEST_KEY, ConfigurationYamlParser.TESTS_KEY));
+ String.format("'%s' key is mandatory in '%s'", mainkey, category));
}
}
}
- /** Returns the test runner to be used. */
- public String getRunner() {
- return mRunner;
- }
-
- /** Returns the options for the test runner */
- public Multimap<String, String> getOptions() {
- return mOptions;
+ public List<ClassAndOptions> getClassesAndOptions() {
+ return mListClassAndOptions;
}
}
diff --git a/src/com/android/tradefed/device/DeviceSelectionOptions.java b/src/com/android/tradefed/device/DeviceSelectionOptions.java
index 6dbae6d5a..fac18c72e 100644
--- a/src/com/android/tradefed/device/DeviceSelectionOptions.java
+++ b/src/com/android/tradefed/device/DeviceSelectionOptions.java
@@ -29,6 +29,7 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
@@ -161,6 +162,8 @@ public class DeviceSelectionOptions implements IDeviceSelection {
// If we have tried to fetch the environment variable ANDROID_SERIAL before.
private boolean mFetchedEnvVariable = false;
+ // Store the reason for which the device was not matched.
+ private Map<String, String> mNoMatchReason = new LinkedHashMap<>();
private static final String VARIANT_SEPARATOR = ":";
@@ -448,6 +451,7 @@ public class DeviceSelectionOptions implements IDeviceSelection {
*/
@Override
public boolean matches(IDevice device) {
+ String deviceSerial = device.getSerialNumber();
Collection<String> serials = getSerials(device);
Collection<String> excludeSerials = getExcludeSerials();
Map<String, Collection<String>> productVariants = splitOnVariant(getProductTypes());
@@ -456,9 +460,17 @@ public class DeviceSelectionOptions implements IDeviceSelection {
if (!serials.isEmpty() &&
!serials.contains(device.getSerialNumber())) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device serial does not match any requested serial(%s)", serials));
return false;
}
if (excludeSerials.contains(device.getSerialNumber())) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device serial was part of excluded serials(%s)", excludeSerials));
return false;
}
if (!productTypes.isEmpty()) {
@@ -468,15 +480,31 @@ public class DeviceSelectionOptions implements IDeviceSelection {
String productVariant = getDeviceProductVariant(device);
Collection<String> variants = productVariants.get(productType);
if (variants != null && !variants.contains(productVariant)) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device variant (%s) does not match requested variants(%s)",
+ productVariant, variants));
return false;
}
} else {
// no product type matches; bye-bye
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device product type (%s) does not match requested product types(%s)",
+ productType, productTypes));
return false;
}
}
for (Map.Entry<String, String> propEntry : properties.entrySet()) {
- if (!propEntry.getValue().equals(device.getProperty(propEntry.getKey()))) {
+ String deviceProperty = device.getProperty(propEntry.getKey());
+ if (!propEntry.getValue().equals(deviceProperty)) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device property (%s) value(%s) does not match requested value(%s)",
+ propEntry.getKey(), deviceProperty, propEntry.getValue()));
return false;
}
}
@@ -488,12 +516,25 @@ public class DeviceSelectionOptions implements IDeviceSelection {
if ((mMinSdk != null) || (mMaxSdk != null)) {
int deviceSdkLevel = getDeviceSdkLevel(device);
if (deviceSdkLevel < 0) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format("device returned unexpected sdk level (%s)", deviceSdkLevel));
return false;
}
if (mMinSdk != null && deviceSdkLevel < mMinSdk) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device sdk (%s) is below the requested min sdk (%s)",
+ deviceSdkLevel, mMinSdk));
return false;
}
if (mMaxSdk != null && mMaxSdk < deviceSdkLevel) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device sdk (%s) is above the requested max sdk (%s)",
+ deviceSdkLevel, mMaxSdk));
return false;
}
}
@@ -505,19 +546,35 @@ public class DeviceSelectionOptions implements IDeviceSelection {
if (device instanceof StubDevice || device instanceof FastbootDevice) {
// Reading battery of fastboot and StubDevice device does not work and could
// lead to weird log.
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device type is (%s) which cannot have a battery required.",
+ device.getClass()));
return false;
}
Integer deviceBattery = getBatteryLevel(device);
if (deviceBattery == null) {
// Couldn't determine battery level when that check is required; reject device
+ addNoMatchReason(deviceSerial, "device failed to return a battery reading.");
return false;
}
if (isLessAndNotNull(deviceBattery, mMinBattery)) {
// deviceBattery < mMinBattery
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device battery (%s) is below the requested min battery (%s)",
+ deviceBattery, mMinBattery));
return false;
}
if (isLessEqAndNotNull(mMaxBattery, deviceBattery)) {
// mMaxBattery <= deviceBattery
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device battery (%s) is above the requested max battery (%s)",
+ deviceBattery, mMaxBattery));
return false;
}
}
@@ -558,39 +615,55 @@ public class DeviceSelectionOptions implements IDeviceSelection {
if ((emulatorRequested() || stubEmulatorRequested()) && !device.isEmulator()) {
return false;
}
+ String deviceSerial = device.getSerialNumber();
// If physical device is requested but device is emulator or remote ip device, skip
if (deviceRequested()
&& (device.isEmulator()
|| RemoteAndroidDevice.checkSerialFormatValid(device.getSerialNumber()))) {
+ addNoMatchReason(deviceSerial, "device is not a physical device");
return false;
}
if (mRequestedType != null) {
Class<?> classNeeded = mRequestedType.getRequiredClass();
if (!device.getClass().equals(classNeeded)) {
+ addNoMatchReason(
+ deviceSerial,
+ String.format(
+ "device is type (%s) while requested type was (%s)",
+ device.getClass(), classNeeded));
return false;
}
} else {
if (device.isEmulator() && (device instanceof StubDevice) && !stubEmulatorRequested()) {
// only allocate the stub emulator if requested
+ addNoMatchReason(deviceSerial, "device is emulator while requested type was not");
return false;
}
if (nullDeviceRequested() != (device instanceof NullDevice)) {
+ addNoMatchReason(
+ deviceSerial, "device is null-device while requested type was not");
return false;
}
if (tcpDeviceRequested() != TcpDevice.class.equals(device.getClass())) {
// We only match an exact TcpDevice here, no child class.
+ addNoMatchReason(deviceSerial, "device is tcp-device while requested type was not");
return false;
}
if (gceDeviceRequested() != RemoteAvdIDevice.class.equals(device.getClass())) {
// We only match an exact RemoteAvdIDevice here, no child class.
+ addNoMatchReason(deviceSerial, "device is gce-device while requested type was not");
return false;
}
if (remoteDeviceRequested() != VmRemoteDevice.class.equals(device.getClass())) {
+ addNoMatchReason(
+ deviceSerial, "device is remote-device while requested type was not");
return false;
}
if (localVirtualDeviceRequested()
!= StubLocalAndroidVirtualDevice.class.equals(device.getClass())) {
+ addNoMatchReason(
+ deviceSerial, "device is local-virtual while requested type was not");
return false;
}
}
@@ -704,6 +777,15 @@ public class DeviceSelectionOptions implements IDeviceSelection {
return apiLevel;
}
+ private void addNoMatchReason(String device, String reason) {
+ mNoMatchReason.put(device, reason);
+ }
+
+ @Override
+ public Map<String, String> getNoMatchReason() {
+ return mNoMatchReason;
+ }
+
/**
* Helper factory method to create a {@link IDeviceSelection} that will only match device
* with given serial
diff --git a/src/com/android/tradefed/device/IDeviceSelection.java b/src/com/android/tradefed/device/IDeviceSelection.java
index c61179ed8..1302532a6 100644
--- a/src/com/android/tradefed/device/IDeviceSelection.java
+++ b/src/com/android/tradefed/device/IDeviceSelection.java
@@ -116,4 +116,10 @@ public interface IDeviceSelection extends IMatcher<IDevice> {
*/
public void setSerial(String... serialNumber);
+ /**
+ * Returns the reason for which the device was not matched.
+ *
+ * @return a Map of serial number to reason for which it wasn't allocated
+ */
+ public Map<String, String> getNoMatchReason();
}
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index 8c9616264..3bd9abe61 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -109,6 +109,7 @@ import javax.annotation.concurrent.GuardedBy;
public class NativeDevice implements IManagedTestDevice {
protected static final String SD_CARD = "/sdcard/";
+ protected static final String STORAGE_EMULATED = "/storage/emulated/";
/**
* Allow pauses of up to 2 minutes while receiving bugreport.
* <p/>
@@ -1101,7 +1102,7 @@ public class NativeDevice implements IManagedTestDevice {
public boolean pullFile(final String remoteFilePath, final File localFile)
throws DeviceNotAvailableException {
- if (remoteFilePath.startsWith(SD_CARD)) {
+ if (isSdcardOrEmulated(remoteFilePath)) {
ContentProviderHandler handler = getContentProvider();
if (handler != null) {
return handler.pullFile(remoteFilePath, localFile);
@@ -1209,7 +1210,7 @@ public class NativeDevice implements IManagedTestDevice {
@Override
public boolean pushFile(final File localFile, final String remoteFilePath)
throws DeviceNotAvailableException {
- if (remoteFilePath.startsWith(SD_CARD)) {
+ if (isSdcardOrEmulated(remoteFilePath)) {
ContentProviderHandler handler = getContentProvider();
if (handler != null) {
return handler.pushFile(localFile, remoteFilePath);
@@ -1284,6 +1285,15 @@ public class NativeDevice implements IManagedTestDevice {
/** {@inheritDoc} */
@Override
public boolean doesFileExist(String deviceFilePath) throws DeviceNotAvailableException {
+ if (isSdcardOrEmulated(deviceFilePath)) {
+ ContentProviderHandler handler = getContentProvider();
+ if (handler != null) {
+ CLog.d("Delegating check to ContentProvider doesFileExist(%s)", deviceFilePath);
+
+ return handler.doesFileExist(deviceFilePath);
+ }
+ }
+ CLog.d("Using 'ls' to check doesFileExist(%s)", deviceFilePath);
String lsGrep = executeShellCommand(String.format("ls \"%s\"", deviceFilePath));
return !lsGrep.contains("No such file or directory");
}
@@ -1291,7 +1301,7 @@ public class NativeDevice implements IManagedTestDevice {
/** {@inheritDoc} */
@Override
public void deleteFile(String deviceFilePath) throws DeviceNotAvailableException {
- if (deviceFilePath.startsWith(SD_CARD)) {
+ if (isSdcardOrEmulated(deviceFilePath)) {
ContentProviderHandler handler = getContentProvider();
if (handler != null) {
if (handler.deleteFile(deviceFilePath)) {
@@ -1624,7 +1634,7 @@ public class NativeDevice implements IManagedTestDevice {
@Override
public boolean pullDir(String deviceFilePath, File localDir)
throws DeviceNotAvailableException {
- if (deviceFilePath.startsWith(SD_CARD)) {
+ if (isSdcardOrEmulated(deviceFilePath)) {
ContentProviderHandler handler = getContentProvider();
if (handler != null) {
return handler.pullDir(deviceFilePath, localDir);
@@ -1677,6 +1687,11 @@ public class NativeDevice implements IManagedTestDevice {
return true;
}
+ /** Checks whether path is external storage path. */
+ private boolean isSdcardOrEmulated(String path) {
+ return path.startsWith(SD_CARD) || path.startsWith(STORAGE_EMULATED);
+ }
+
/**
* {@inheritDoc}
*/
@@ -4051,7 +4066,8 @@ public class NativeDevice implements IManagedTestDevice {
throw new DeviceRuntimeException(
String.format(
"Failed to query property '%s'. device returned null.",
- DeviceProperties.BUILD_CODENAME));
+ DeviceProperties.BUILD_CODENAME),
+ DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
codeName = codeName.trim();
int apiLevel = getApiLevel() + ("REL".equals(codeName) ? 0 : 1);
diff --git a/src/com/android/tradefed/device/NoDeviceException.java b/src/com/android/tradefed/device/NoDeviceException.java
index 45f433912..a2fae9330 100644
--- a/src/com/android/tradefed/device/NoDeviceException.java
+++ b/src/com/android/tradefed/device/NoDeviceException.java
@@ -16,36 +16,23 @@
package com.android.tradefed.device;
import com.android.tradefed.build.BuildSerializedVersion;
+import com.android.tradefed.error.HarnessRuntimeException;
+import com.android.tradefed.result.error.ErrorIdentifier;
-/**
- * Thrown when there's no device to execute a given command.
- */
-public class NoDeviceException extends Exception {
- private static final long serialVersionUID = BuildSerializedVersion.VERSION;
-
- /**
- * Creates a {@link NoDeviceException}.
- */
- public NoDeviceException() {
- super();
- }
+import java.lang.StackWalker.Option;
- /**
- * Creates a {@link NoDeviceException}.
- *
- * @param msg a descriptive message.
- */
- public NoDeviceException(String msg) {
- super(msg);
- }
+/** Thrown when there's no device to execute a given command. */
+public class NoDeviceException extends HarnessRuntimeException {
+ private static final long serialVersionUID = BuildSerializedVersion.VERSION;
/**
* Creates a {@link NoDeviceException}.
*
* @param msg a descriptive message.
- * @param cause the root {@link Throwable} that caused the device to become unavailable.
+ * @param errorId The {@link ErrorIdentifier} categorizing the exception.
*/
- public NoDeviceException(String msg, Throwable cause) {
- super(msg, cause);
+ public NoDeviceException(String msg, ErrorIdentifier errorId) {
+ super(msg, errorId);
+ setCallerClass(StackWalker.getInstance(Option.RETAIN_CLASS_REFERENCE).getCallerClass());
}
}
diff --git a/src/com/android/tradefed/device/TestDevice.java b/src/com/android/tradefed/device/TestDevice.java
index 80fb98d1c..958f21df6 100644
--- a/src/com/android/tradefed/device/TestDevice.java
+++ b/src/com/android/tradefed/device/TestDevice.java
@@ -27,6 +27,7 @@ import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.util.AaptParser;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
@@ -1187,7 +1188,8 @@ public class TestDevice extends NativeDevice {
String[] lines = commandOutput.split("\\r?\\n");
if (!lines[0].equals("Users:")) {
throw new DeviceRuntimeException(
- String.format("'%s' in not a valid output for 'pm list users'", commandOutput));
+ String.format("'%s' in not a valid output for 'pm list users'", commandOutput),
+ DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
ArrayList<String[]> users = new ArrayList<String[]>(lines.length - 1);
for (int i = 1; i < lines.length; i++) {
@@ -1199,7 +1201,8 @@ public class TestDevice extends NativeDevice {
String.format(
"device output: '%s' \nline: '%s' was not in the expected "
+ "format for user info.",
- commandOutput, lines[i]));
+ commandOutput, lines[i]),
+ DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
users.add(tokens);
}
diff --git a/src/com/android/tradefed/device/cloud/GceManager.java b/src/com/android/tradefed/device/cloud/GceManager.java
index 4a323c1d9..a20688423 100644
--- a/src/com/android/tradefed/device/cloud/GceManager.java
+++ b/src/com/android/tradefed/device/cloud/GceManager.java
@@ -26,7 +26,7 @@ import com.android.tradefed.result.ByteArrayInputStreamSource;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.result.error.DeviceErrorIdentifier;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.CommandResult;
@@ -221,7 +221,7 @@ public class GceManager {
throw new TargetSetupError(
String.format("acloud errors: %s", errors),
mDeviceDescriptor,
- DeviceErrorIdentifier.FAILED_TO_LAUNCH_GCE);
+ InfraErrorIdentifier.NO_ACLOUD_REPORT);
}
}
mGceAvdInfo =
@@ -229,7 +229,11 @@ public class GceManager {
reportFile, mDeviceDescriptor, mDeviceOptions.getRemoteAdbPort());
return mGceAvdInfo;
} catch (IOException e) {
- throw new TargetSetupError("failed to create log file", e, mDeviceDescriptor);
+ throw new TargetSetupError(
+ "failed to create log file",
+ e,
+ mDeviceDescriptor,
+ InfraErrorIdentifier.FAIL_TO_CREATE_FILE);
} finally {
FileUtil.deleteFile(reportFile);
}
diff --git a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
index 4925bb624..878e11430 100644
--- a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
+++ b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
@@ -37,6 +37,8 @@ import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.targetprep.TargetSetupError;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.StreamUtil;
@@ -63,6 +65,7 @@ public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements I
private GceManager mGceHandler = null;
private GceSshTunnelMonitor mGceSshMonitor;
+ private DeviceNotAvailableException mTunnelInitFailed = null;
private static final long WAIT_FOR_TUNNEL_ONLINE = 2 * 60 * 1000;
private static final long WAIT_AFTER_REBOOT = 60 * 1000;
@@ -91,6 +94,7 @@ public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements I
try {
mGceAvd = null;
mGceSshMonitor = null;
+ mTunnelInitFailed = null;
// We create a brand new GceManager each time to ensure clean state.
mGceHandler = new GceManager(getDeviceDescriptor(), getOptions(), info);
getGceHandler().logStableHostImageInfos(info);
@@ -269,7 +273,10 @@ public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements I
String.format(
"Device failed to boot. Error from Acloud: %s",
mGceAvd.getErrors());
- throw new TargetSetupError(errorMsg, getDeviceDescriptor());
+ throw new TargetSetupError(
+ errorMsg,
+ getDeviceDescriptor(),
+ DeviceErrorIdentifier.FAILED_TO_LAUNCH_GCE);
}
}
createGceSshMonitor(this, buildInfo, mGceAvd.hostAndPort(), this.getOptions());
@@ -322,14 +329,20 @@ public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements I
}
getRunUtil().sleep(RETRY_INTERVAL_MS);
}
- throw new DeviceNotAvailableException(
- String.format("Tunnel did not come back online after %sms", waitTime),
- getSerialNumber(),
- DeviceErrorIdentifier.FAILED_TO_CONNECT_TO_GCE);
+ mTunnelInitFailed =
+ new DeviceNotAvailableException(
+ String.format("Tunnel did not come back online after %sms", waitTime),
+ getSerialNumber(),
+ DeviceErrorIdentifier.FAILED_TO_CONNECT_TO_GCE);
+ throw mTunnelInitFailed;
}
@Override
public void recoverDevice() throws DeviceNotAvailableException {
+ if (getGceSshMonitor() == null && mTunnelInitFailed != null) {
+ // We threw before but was not reported, so throw the root cause here.
+ throw mTunnelInitFailed;
+ }
// Re-init tunnel when attempting recovery
CLog.i("Attempting recovery on GCE AVD %s", getSerialNumber());
getGceSshMonitor().closeConnection();
@@ -447,4 +460,38 @@ public class RemoteAndroidVirtualDevice extends RemoteAndroidDevice implements I
}
return descriptor;
}
+
+ /**
+ * Attempt to powerwash a GCE instance
+ *
+ * @return returns true if powerwash Gce success.
+ * @throws TargetSetupError
+ * @throws DeviceNotAvailableException
+ */
+ public boolean powerwashGce() throws TargetSetupError, DeviceNotAvailableException {
+ if (mGceAvd == null) {
+ String errorMsg = String.format("Can not get GCE AVD Info. launch GCE first?");
+ throw new TargetSetupError(
+ errorMsg, getDeviceDescriptor(), DeviceErrorIdentifier.DEVICE_UNAVAILABLE);
+ }
+ String username = this.getOptions().getInstanceUser();
+ String powerwashCommand = String.format("/home/%s/bin/powerwash_cvd", username);
+ CommandResult powerwashRes =
+ GceManager.remoteSshCommandExecution(
+ mGceAvd,
+ this.getOptions(),
+ getRunUtil(),
+ 60000L,
+ powerwashCommand.split(" "));
+ if (!CommandStatus.SUCCESS.equals(powerwashRes.getStatus())) {
+ CLog.e("%s", powerwashRes.getStderr());
+ // Log 'adb devices' to confirm device is gone
+ CommandResult printAdbDevices = getRunUtil().runTimedCmd(60000L, "adb", "devices");
+ CLog.e("%s\n%s", printAdbDevices.getStdout(), printAdbDevices.getStderr());
+ // Proceed here, device could have been already gone.
+ return false;
+ }
+ getMonitor().waitForDeviceAvailable();
+ return true;
+ }
}
diff --git a/src/com/android/tradefed/device/metric/FilePullerLogCollector.java b/src/com/android/tradefed/device/metric/FilePullerLogCollector.java
index 547443387..da8c2408a 100644
--- a/src/com/android/tradefed/device/metric/FilePullerLogCollector.java
+++ b/src/com/android/tradefed/device/metric/FilePullerLogCollector.java
@@ -46,6 +46,8 @@ public class FilePullerLogCollector extends FilePullerDeviceMetricCollector {
type = LogDataType.PB;
} else if (".mp4".equals(ext)) {
type = LogDataType.MP4;
+ } else if (".hprof".equals(ext)) {
+ type = LogDataType.HPROF;
}
testLog(metricFile.getName(), type, source);
}
diff --git a/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java b/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
index 1b071ef56..2255ee7fe 100644
--- a/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
+++ b/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
@@ -33,6 +33,8 @@ import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.testtype.coverage.CoverageOptions;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.JavaCodeCoverageFlusher;
+import com.android.tradefed.util.ProcessInfo;
+import com.android.tradefed.util.PsParser;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
@@ -42,6 +44,7 @@ import org.jacoco.core.tools.ExecFileLoader;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
import java.util.Map;
import java.util.List;
@@ -148,7 +151,6 @@ public final class JavaCodeCoverageCollector extends BaseDeviceMetricCollector
devicePaths.addAll(Splitter.on('\n').omitEmptyStrings().split(fileList));
collectAndLogCoverageMeasurementsAsRoot(device, devicePaths.build());
-
} catch (DeviceNotAvailableException | IOException e) {
throw new RuntimeException(e);
}
@@ -191,9 +193,24 @@ public final class JavaCodeCoverageCollector extends BaseDeviceMetricCollector
private void collectAndLogCoverageMeasurements(ITestDevice device, List<String> devicePaths)
throws IOException, DeviceNotAvailableException {
+ List<Integer> activePids = getRunningProcessIds(device);
for (String devicePath : devicePaths) {
File coverageFile = device.pullFile(devicePath);
+
+ if (devicePath.endsWith(".mm.ec")) {
+ // Check if the process was still running. The file will have the format
+ // /data/misc/trace/jacoco-XXXXX.mm.ec where XXXXX is the process id.
+ int start = devicePath.indexOf('-') + 1;
+ int end = devicePath.indexOf('.');
+ int pid = Integer.parseInt(devicePath.substring(start, end));
+ if (!activePids.contains(pid)) {
+ device.deleteFile(devicePath);
+ }
+ } else {
+ device.deleteFile(devicePath);
+ }
+
verifyNotNull(
coverageFile, "Failed to pull the Java code coverage file from %s", devicePath);
@@ -216,6 +233,17 @@ public final class JavaCodeCoverageCollector extends BaseDeviceMetricCollector
}
}
+ private List<Integer> getRunningProcessIds(ITestDevice device)
+ throws DeviceNotAvailableException {
+ List<ProcessInfo> processes = PsParser.getProcesses(device.executeShellCommand("ps -e"));
+ List<Integer> pids = new ArrayList<>();
+
+ for (ProcessInfo process : processes) {
+ pids.add(process.getPid());
+ }
+ return pids;
+ }
+
private FailureDescription createCodeCoverageFailure(String message) {
return CurrentInvocation.createFailure(message, InfraErrorIdentifier.CODE_COVERAGE_ERROR);
}
diff --git a/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java b/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java
index c4c969771..56292d2ec 100644
--- a/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java
+++ b/src/com/android/tradefed/invoker/DelegatedInvocationExecution.java
@@ -24,6 +24,7 @@ import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.invoker.TestInvocation.Stage;
import com.android.tradefed.log.ITestLogger;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
@@ -131,6 +132,7 @@ public class DelegatedInvocationExecution extends InvocationExecution {
IRunUtil runUtil = createRunUtil(receiver.getSocketServerPort());
CommandResult result = null;
RuntimeException runtimeException = null;
+ CLog.d("Command line: %s", commandLine);
try {
result =
runUtil.runTimedCmd(
@@ -159,7 +161,7 @@ public class DelegatedInvocationExecution extends InvocationExecution {
}
if (result.getStatus().equals(CommandStatus.TIMED_OUT)) {
throw new HarnessRuntimeException(
- "Delegated invocation timed out.", InfraErrorIdentifier.UNDETERMINED);
+ "Delegated invocation timed out.", InfraErrorIdentifier.INVOCATION_TIMEOUT);
}
} finally {
StreamUtil.close(mStderr);
diff --git a/src/com/android/tradefed/invoker/InvocationExecution.java b/src/com/android/tradefed/invoker/InvocationExecution.java
index 24d809e20..7a4ae8a00 100644
--- a/src/com/android/tradefed/invoker/InvocationExecution.java
+++ b/src/com/android/tradefed/invoker/InvocationExecution.java
@@ -83,6 +83,7 @@ import com.google.common.base.Strings;
import java.io.File;
import java.io.IOException;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -231,9 +232,10 @@ public class InvocationExecution implements IInvocationExecution {
mTrackTargetPreparers = new ConcurrentHashMap<>();
int index = 0;
- if (config.getCommandOptions().shouldUseReplicateSetup()
+ if ((config.getCommandOptions().shouldUseParallelSetup()
+ || config.getCommandOptions().shouldUseReplicateSetup())
&& config.getDeviceConfig().size() > 1) {
- CLog.d("Using parallel setup due to replicated setup enabled.");
+ CLog.d("Using parallel setup.");
ParallelDeviceExecutor<Boolean> executor =
new ParallelDeviceExecutor<>(testInfo.getContext().getDevices().size());
List<Callable<Boolean>> callableTasks = new ArrayList<>();
@@ -252,8 +254,8 @@ public class InvocationExecution implements IInvocationExecution {
callableTasks.add(callableTask);
index++;
}
- // Run setup with 30 minutes right now.
- executor.invokeAll(callableTasks, 30, TimeUnit.MINUTES);
+ Duration timeout = config.getCommandOptions().getParallelSetupTimeout();
+ executor.invokeAll(callableTasks, timeout.toMillis(), TimeUnit.MILLISECONDS);
if (executor.hasErrors()) {
List<Throwable> errors = executor.getErrors();
// TODO: Handle throwing multi-exceptions, right now throw the first one.
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index 9da66eb1b..c4ffc43e0 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -43,6 +43,7 @@ import com.android.tradefed.device.cloud.ManagedRemoteDevice;
import com.android.tradefed.device.cloud.NestedRemoteDevice;
import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
import com.android.tradefed.error.HarnessException;
+import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.error.IHarnessException;
import com.android.tradefed.guice.InvocationScope;
import com.android.tradefed.invoker.logger.CurrentInvocation;
@@ -164,6 +165,7 @@ public class TestInvocation implements ITestInvocation {
private Long mStopRequestTime = null;
private boolean mTestStarted = false;
private boolean mInvocationFailed = false;
+ private boolean mDelegatedInvocation = false;
private List<IScheduledInvocationListener> mSchedulerListeners = new ArrayList<>();
/**
@@ -214,7 +216,6 @@ public class TestInvocation implements ITestInvocation {
throws Throwable {
ReportHostLog reportThread = new ReportHostLog(listener, config);
Runtime.getRuntime().addShutdownHook(reportThread);
- boolean resumed = false;
String bugreportName = null;
long startTime = System.currentTimeMillis();
long elapsedTime = -1;
@@ -306,37 +307,39 @@ public class TestInvocation implements ITestInvocation {
}
CurrentInvocation.setActionInProgress(ActionInProgress.TEAR_DOWN);
getRunUtil().allowInterrupt(false);
- if (config.getCommandOptions().takeBugreportOnInvocationEnded() ||
- config.getCommandOptions().takeBugreportzOnInvocationEnded()) {
- if (bugreportName != null) {
- CLog.i("Bugreport to be taken for failure instead of invocation ended.");
- } else {
- bugreportName = INVOCATION_ENDED_BUGREPORT_NAME;
- }
- }
- if (bugreportName != null) {
- if (context.getDevices().size() == 1 || badDevice != null) {
- ITestDevice collectBugreport = badDevice;
- if (collectBugreport == null) {
- collectBugreport = context.getDevices().get(0);
+ if (!mDelegatedInvocation) {
+ if (config.getCommandOptions().takeBugreportOnInvocationEnded()
+ || config.getCommandOptions().takeBugreportzOnInvocationEnded()) {
+ if (bugreportName != null) {
+ CLog.i("Bugreport to be taken for failure instead of invocation ended.");
+ } else {
+ bugreportName = INVOCATION_ENDED_BUGREPORT_NAME;
}
- // If we have identified a faulty device only take the bugreport on it.
- takeBugreport(collectBugreport, listener, bugreportName);
- } else if (context.getDevices().size() > 1) {
- ParallelDeviceExecutor<Boolean> executor =
- new ParallelDeviceExecutor<>(context.getDevices().size());
- List<Callable<Boolean>> callableTasks = new ArrayList<>();
- final String reportName = bugreportName;
- for (ITestDevice device : context.getDevices()) {
- Callable<Boolean> callableTask =
- () -> {
- takeBugreport(device, listener, reportName);
- return true;
- };
- callableTasks.add(callableTask);
+ }
+ if (bugreportName != null) {
+ if (context.getDevices().size() == 1 || badDevice != null) {
+ ITestDevice collectBugreport = badDevice;
+ if (collectBugreport == null) {
+ collectBugreport = context.getDevices().get(0);
+ }
+ // If we have identified a faulty device only take the bugreport on it.
+ takeBugreport(collectBugreport, listener, bugreportName);
+ } else if (context.getDevices().size() > 1) {
+ ParallelDeviceExecutor<Boolean> executor =
+ new ParallelDeviceExecutor<>(context.getDevices().size());
+ List<Callable<Boolean>> callableTasks = new ArrayList<>();
+ final String reportName = bugreportName;
+ for (ITestDevice device : context.getDevices()) {
+ Callable<Boolean> callableTask =
+ () -> {
+ takeBugreport(device, listener, reportName);
+ return true;
+ };
+ callableTasks.add(callableTask);
+ }
+ // Capture the bugreports best effort, ignore the results.
+ executor.invokeAll(callableTasks, 5, TimeUnit.MINUTES);
}
- // Capture the bugreports best effort, ignore the results.
- executor.invokeAll(callableTasks, 5, TimeUnit.MINUTES);
}
}
// Save the device executeShellCommand logs
@@ -384,6 +387,10 @@ public class TestInvocation implements ITestInvocation {
mStopCause);
FailureDescription failure =
FailureDescription.create(message, FailureStatus.CANCELLED);
+ failure.setErrorIdentifier(InfraErrorIdentifier.INVOCATION_CANCELLED);
+ failure.setCause(
+ new HarnessRuntimeException(
+ message, InfraErrorIdentifier.INVOCATION_CANCELLED));
reportFailure(failure, listener);
PrettyPrintDelimiter.printStageDelimiter(message);
if (mStopRequestTime != null) {
@@ -399,22 +406,7 @@ public class TestInvocation implements ITestInvocation {
Runtime.getRuntime().removeShutdownHook(reportThread);
elapsedTime = System.currentTimeMillis() - startTime;
- if (!resumed) {
- // Init a log for the end of the host_log.
- ILeveledLogOutput endHostLog = config.getLogOutput();
- endHostLog.init();
- getLogRegistry().registerLogger(endHostLog);
- PrettyPrintDelimiter.printStageDelimiter("===== Result Reporters =====");
- try {
- // Copy the invocation metrics to the context
- ((InvocationContext) context).logInvocationMetrics();
- listener.invocationEnded(elapsedTime);
- } finally {
- InvocationMetricLogger.clearInvocationMetrics();
- endHostLog.closeLog();
- getLogRegistry().unregisterLogger();
- }
- }
+ reportInvocationEnded(config, context, listener, elapsedTime);
} finally {
TfObjectTracker.clearTracking();
CurrentInvocation.clearInvocationInfos();
@@ -498,7 +490,7 @@ public class TestInvocation implements ITestInvocation {
private void reportHostLog(ITestInvocationListener listener, IConfiguration config) {
String name = TRADEFED_LOG_NAME;
- if (config.getConfigurationObject(TradefedDelegator.DELEGATE_OBJECT) != null) {
+ if (mDelegatedInvocation) {
name = TRADEFED_DELEGATED_LOG_NAME;
}
reportHostLog(listener, config, name);
@@ -650,7 +642,7 @@ public class TestInvocation implements ITestInvocation {
invocationPath.reportLogs(device, listener, Stage.ERROR);
}
reportHostLog(listener, config);
- listener.invocationEnded(0L);
+ reportInvocationEnded(config, testInfo.getContext(), listener, 0L);
return false;
}
@@ -703,7 +695,7 @@ public class TestInvocation implements ITestInvocation {
invocationPath.reportLogs(device, listener, Stage.ERROR);
}
reportHostLog(listener, config);
- listener.invocationEnded(0L);
+ reportInvocationEnded(config, context, listener, 0L);
return false;
}
}
@@ -797,6 +789,7 @@ public class TestInvocation implements ITestInvocation {
mode = RunMode.REMOTE_INVOCATION;
}
if (config.getConfigurationObject(TradefedDelegator.DELEGATE_OBJECT) != null) {
+ mDelegatedInvocation = true;
mode = RunMode.DELEGATED_INVOCATION;
}
IInvocationExecution invocationPath = createInvocationExec(mode);
@@ -1156,6 +1149,35 @@ public class TestInvocation implements ITestInvocation {
return devicesStates;
}
+ private void reportInvocationEnded(
+ IConfiguration config,
+ IInvocationContext context,
+ ITestInvocationListener listener,
+ long elapsedTime) {
+ // Init a log for the end of the host_log.
+ ILeveledLogOutput endHostLog = config.getLogOutput();
+ try {
+ endHostLog.init();
+ getLogRegistry().registerLogger(endHostLog);
+ } catch (IOException e) {
+ CLog.e(e);
+ endHostLog = null;
+ }
+
+ PrettyPrintDelimiter.printStageDelimiter("===== Result Reporters =====");
+ try {
+ // Copy the invocation metrics to the context
+ ((InvocationContext) context).logInvocationMetrics();
+ listener.invocationEnded(elapsedTime);
+ } finally {
+ InvocationMetricLogger.clearInvocationMetrics();
+ if (endHostLog != null) {
+ endHostLog.closeLog();
+ getLogRegistry().unregisterLogger();
+ }
+ }
+ }
+
/** Helper Thread that ensures host_log is reported in case of killed JVM */
private class ReportHostLog extends Thread {
diff --git a/src/com/android/tradefed/invoker/shard/StrictShardHelper.java b/src/com/android/tradefed/invoker/shard/StrictShardHelper.java
index f3aeffc64..cf30b05f9 100644
--- a/src/com/android/tradefed/invoker/shard/StrictShardHelper.java
+++ b/src/com/android/tradefed/invoker/shard/StrictShardHelper.java
@@ -35,7 +35,10 @@ import com.android.tradefed.util.TimeUtil;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/** Sharding strategy to create strict shards that do not report together, */
public class StrictShardHelper extends ShardHelper {
@@ -49,6 +52,7 @@ public class StrictShardHelper extends ShardHelper {
ITestLogger logger) {
Integer shardCount = config.getCommandOptions().getShardCount();
Integer shardIndex = config.getCommandOptions().getShardIndex();
+ boolean optimizeMainline = config.getCommandOptions().getOptimizeMainlineTest();
if (shardIndex == null) {
return super.shardConfig(config, testInfo, rescheduler, logger);
@@ -69,11 +73,50 @@ public class StrictShardHelper extends ShardHelper {
splitList = splitTests(listAllTests, shardCount).get(shardIndex);
}
aggregateSuiteModules(splitList);
+ if (optimizeMainline) {
+ CLog.i("Reordering the test modules list for index: %s", shardIndex);
+ reorderTestModules(splitList);
+ }
config.setTests(splitList);
return false;
}
/**
+ * Helper to re order the list full list of {@link IRemoteTest} for mainline.
+ *
+ * @param tests the {@link IRemoteTest} containing all the tests that need to run.
+ */
+ private void reorderTestModules(List<IRemoteTest> tests) {
+ Collections.sort(tests, new Comparator<IRemoteTest>() {
+ @Override
+ public int compare(IRemoteTest o1, IRemoteTest o2) {
+ String moduleId1 = ((ITestSuite)o1).getDirectModule().getId();
+ String moduleId2 = ((ITestSuite)o2).getDirectModule().getId();
+ return getMainlineId(moduleId1).compareTo(getMainlineId(moduleId2));
+ }
+ });
+ }
+
+ /**
+ * Returns the parameterized mainline modules' name defined in the square brackets.
+ *
+ * @param id The module's name.
+ * @throws RuntimeException if the module name doesn't match the pattern for mainline modules.
+ */
+ private String getMainlineId(String id) {
+ // Pattern used to identify the parameterized mainline modules defined in the square
+ // brackets.
+ Pattern parameterizedMainlineRegex = Pattern.compile("\\[(.*(\\.apk|.apex|.apks))\\]$");
+ Matcher m = parameterizedMainlineRegex.matcher(id);
+ if (m.find()) {
+ return m.group(1);
+ }
+ throw new RuntimeException(
+ String.format("Module: %s doesn't match the pattern for mainline modules. The " +
+ "pattern should end with apk/apex/apks.", id));
+ }
+
+ /**
* Helper to return the full list of {@link IRemoteTest} based on {@link IShardableTest} split.
*
* @param config the {@link IConfiguration} describing the invocation.
diff --git a/src/com/android/tradefed/monitoring/LabResourceDeviceMonitor.java b/src/com/android/tradefed/monitoring/LabResourceDeviceMonitor.java
new file mode 100644
index 000000000..8366e0e52
--- /dev/null
+++ b/src/com/android/tradefed/monitoring/LabResourceDeviceMonitor.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2020 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.monitoring;
+
+import com.android.loganalysis.util.config.OptionClass;
+import com.android.tradefed.device.DeviceAllocationState;
+import com.android.tradefed.device.IDeviceMonitor;
+import com.android.tradefed.log.LogUtil;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.dualhomelab.monitoringagent.resourcemonitoring.LabResource;
+import com.google.dualhomelab.monitoringagent.resourcemonitoring.LabResourceRequest;
+import com.google.dualhomelab.monitoringagent.resourcemonitoring.LabResourceServiceGrpc;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Optional;
+import java.util.concurrent.Executors;
+
+import io.grpc.Server;
+import io.grpc.netty.shaded.io.grpc.netty.NettyServerBuilder;
+import io.grpc.stub.StreamObserver;
+
+/** The lab resource monitor which initializes/manages the gRPC server for LabResourceService. */
+@OptionClass(alias = "lab-resource-monitor")
+public class LabResourceDeviceMonitor extends LabResourceServiceGrpc.LabResourceServiceImplBase
+ implements IDeviceMonitor {
+ public static final String SERVER_HOSTNAME = "localhost";
+ public static final int DEFAULT_PORT = 8887;
+ public static final int DEFAULT_THREAD_COUNT = 1;
+ private Optional<Server> mServer = Optional.empty();
+
+ @VisibleForTesting
+ Optional<Server> getServer() {
+ return mServer;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void run() {
+ if (!mServer.isPresent()) {
+ mServer =
+ Optional.of(
+ NettyServerBuilder.forAddress(
+ new InetSocketAddress(SERVER_HOSTNAME, DEFAULT_PORT))
+ .addService(this)
+ .executor(Executors.newFixedThreadPool(DEFAULT_THREAD_COUNT))
+ .build());
+ try {
+ mServer.get().start();
+ } catch (IOException e) {
+ LogUtil.CLog.e(e);
+ }
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void stop() {
+ mServer.ifPresent(Server::shutdown);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void setDeviceLister(DeviceLister lister) {
+ // Ignore
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void notifyDeviceStateChange(
+ String serial, DeviceAllocationState oldState, DeviceAllocationState newState) {
+ // Ignore
+ }
+
+ /** The gRPC request handler. */
+ @Override
+ public void getLabResource(
+ LabResourceRequest request, StreamObserver<LabResource> responseObserver) {
+ super.getLabResource(request, responseObserver);
+ }
+}
diff --git a/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java b/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java
index 8833ed5c3..4a934d801 100644
--- a/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java
+++ b/src/com/android/tradefed/postprocessor/StatsdEventMetricPostProcessor.java
@@ -24,6 +24,7 @@ import com.android.tradefed.config.OptionClass;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.util.MultiMap;
+import com.android.tradefed.util.ProtoUtil;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
import com.google.protobuf.Descriptors.FieldDescriptor;
@@ -148,48 +149,6 @@ public class StatsdEventMetricPostProcessor extends StatsdGenericPostProcessor {
return metrics;
}
- /**
- * Get a nested field reference, i.e. field_1.field_2.field_3, from a proto message as a string.
- * Returns an empty list when a field cannot be found, either because it's invalid or does not
- * exist in the message.
- *
- * <p>If the field reference contains repeated fields, each instance is expanded, resulting in a
- * list of strings.
- */
- private List<String> getNestedFieldFromMessageAsStrings(
- Object messageOrObject, List<String> references) {
- if (references.isEmpty()) {
- return Arrays.asList(String.valueOf(messageOrObject));
- }
- if (!(messageOrObject instanceof Message)) {
- CLog.e(
- "Attempting to read field %s from object of type %s, "
- + "which is not a proto message.",
- references.get(0), messageOrObject.getClass());
- return new ArrayList<String>();
- }
- Message message = (Message) messageOrObject;
- String reference = references.get(0);
- FieldDescriptor fieldDescriptor = message.getDescriptorForType().findFieldByName(reference);
- if (fieldDescriptor == null) {
- CLog.e("Could not find field %s in message %s.", reference, message);
- return new ArrayList<String>();
- }
- Object fieldValue = message.getField(fieldDescriptor);
- if (fieldValue instanceof List) {
- return ((List<? extends Object>) fieldValue)
- .stream()
- .flatMap(
- v ->
- getNestedFieldFromMessageAsStrings(
- v, references.subList(1, references.size()))
- .stream())
- .collect(Collectors.toList());
- }
- return getNestedFieldFromMessageAsStrings(
- fieldValue, references.subList(1, references.size()));
- }
-
/** Fill in the placeholders in the formatter using the proto message as source. */
private List<String> fillInPlaceholders(
String formatter, EventMetricData eventMetric, Message atomContent) {
@@ -202,12 +161,12 @@ public class StatsdEventMetricPostProcessor extends StatsdGenericPostProcessor {
List<String> actual = new ArrayList();
if (fieldReference.startsWith("_")) {
actual.addAll(
- getNestedFieldFromMessageAsStrings(
+ ProtoUtil.getNestedFieldFromMessageAsStrings(
eventMetric,
Arrays.asList(fieldReference.substring(1).split("\\."))));
} else {
actual.addAll(
- getNestedFieldFromMessageAsStrings(
+ ProtoUtil.getNestedFieldFromMessageAsStrings(
atomContent, Arrays.asList(fieldReference.split("\\."))));
}
// If both the existing expansion results and newly expanded results have multiple
diff --git a/src/com/android/tradefed/result/CollectingTestListener.java b/src/com/android/tradefed/result/CollectingTestListener.java
index d9ec6ec1e..a51d2ae23 100644
--- a/src/com/android/tradefed/result/CollectingTestListener.java
+++ b/src/com/android/tradefed/result/CollectingTestListener.java
@@ -311,6 +311,12 @@ public class CollectingTestListener implements ITestInvocationListener, ILogSave
}
@Override
+ public void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+ setCountDirty();
+ mCurrentTestRunResult.testAssumptionFailure(test, failure);
+ }
+
+ @Override
public void testIgnored(TestDescription test) {
setCountDirty();
mCurrentTestRunResult.testIgnored(test);
diff --git a/src/com/android/tradefed/result/JsonHttpTestResultReporter.java b/src/com/android/tradefed/result/JsonHttpTestResultReporter.java
index b89e4b036..06c9585a5 100644
--- a/src/com/android/tradefed/result/JsonHttpTestResultReporter.java
+++ b/src/com/android/tradefed/result/JsonHttpTestResultReporter.java
@@ -36,6 +36,7 @@ import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -207,7 +208,8 @@ public class JsonHttpTestResultReporter extends CollectingTestListener {
// Parse run metrics
if (runResult.getRunMetrics().size() > 0) {
- JSONObject runResultMetrics = new JSONObject(runResult.getRunMetrics());
+ JSONObject runResultMetrics = new JSONObject(
+ getValidMetrics(runResult.getRunMetrics()));
String reportingUnit = runResult.getName();
if (mReportingUnitKeySuffix != null && !mReportingUnitKeySuffix.isEmpty()) {
reportingUnit += mReportingUnitKeySuffix;
@@ -233,7 +235,8 @@ public class JsonHttpTestResultReporter extends CollectingTestListener {
}
resultsName.append(String.format("%s%s", reportingUnit, RESULT_SEPARATOR));
if (testResult.getMetrics().size() > 0) {
- JSONObject testResultMetrics = new JSONObject(testResult.getMetrics());
+ JSONObject testResultMetrics = new JSONObject(
+ getValidMetrics(testResult.getMetrics()));
allTestMetrics.put(reportingUnit, testResultMetrics);
}
}
@@ -259,4 +262,23 @@ public class JsonHttpTestResultReporter extends CollectingTestListener {
return result;
}
-}
+
+ /**
+ * Add only the numeric metrics and skip posting the non-numeric metrics.
+ *
+ * @param collectedMetrics contains all the metrics.
+ * @return only the numeric metrics.
+ */
+ public Map<String, String> getValidMetrics(Map<String, String> collectedMetrics) {
+ Map<String, String> validMetrics = new HashMap<>();
+ for (Map.Entry<String, String> entry : collectedMetrics.entrySet()) {
+ try {
+ Double.parseDouble(entry.getValue());
+ validMetrics.put(entry.getKey(), entry.getValue());
+ } catch (Exception e) {
+ // Skip adding the non numeric metric.
+ }
+ }
+ return validMetrics;
+ }
+} \ No newline at end of file
diff --git a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
index 8891069f9..a14cb8aee 100644
--- a/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
+++ b/src/com/android/tradefed/result/LogcatCrashResultForwarder.java
@@ -19,6 +19,8 @@ import com.android.loganalysis.item.JavaCrashItem;
import com.android.loganalysis.item.LogcatItem;
import com.android.loganalysis.parser.LogcatParser;
import com.android.tradefed.device.ITestDevice;
+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.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.error.DeviceErrorIdentifier;
@@ -41,6 +43,11 @@ public class LogcatCrashResultForwarder extends ResultForwarder {
/** Special error message from the instrumentation when something goes wrong on device side. */
public static final String ERROR_MESSAGE = "Process crashed.";
public static final String SYSTEM_CRASH_MESSAGE = "System has crashed.";
+ public static final String TIMEOUT_MESSAGES[] = {
+ "Failed to receive adb shell test output",
+ "TimeoutException when running tests",
+ "TestTimedOutException: test timed out after",
+ };
public static final int MAX_NUMBER_CRASH = 3;
@@ -76,8 +83,16 @@ public class LogcatCrashResultForwarder extends ResultForwarder {
if (trace.compareTo(failure.getErrorMessage()) != 0) {
// Crash stack trace found, consider this a test failure.
failure.setFailureStatus(FailureStatus.TEST_FAILURE);
+ } else if (isTimeout(failure.getErrorMessage())) {
+ failure.setFailureStatus(FailureStatus.TIMED_OUT);
}
failure.setErrorMessage(trace);
+ // Add metrics for assessing uncaught IntrumentationTest crash failures (test level).
+ InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.TEST_CRASH_FAILURES, 1);
+ if (FailureStatus.UNSET.equals(failure.getFailureStatus())) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.UNCAUGHT_TEST_CRASH_FAILURES, 1);
+ }
super.testFailed(test, failure);
}
@@ -109,6 +124,12 @@ public class LogcatCrashResultForwarder extends ResultForwarder {
if (isCrash(errorMessage)) {
error.setErrorIdentifier(DeviceErrorIdentifier.INSTRUMENTATION_CRASH);
}
+ // Add metrics for assessing uncaught IntrumentationTest crash failures.
+ InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.CRASH_FAILURES, 1);
+ if (FailureStatus.UNSET.equals(error.getFailureStatus())) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.UNCAUGHT_CRASH_FAILURES, 1);
+ }
super.testRunFailed(error);
}
@@ -131,6 +152,14 @@ public class LogcatCrashResultForwarder extends ResultForwarder {
return errorMessage.contains(ERROR_MESSAGE) || errorMessage.contains(SYSTEM_CRASH_MESSAGE);
}
+ private boolean isTimeout(String errorMessage) {
+ for (String timeoutMessage : TIMEOUT_MESSAGES) {
+ if (errorMessage.contains(timeoutMessage)) {
+ return true;
+ }
+ }
+ return false;
+ }
/**
* Extract a formatted object from the logcat snippet.
*
diff --git a/src/com/android/tradefed/result/ResultForwarder.java b/src/com/android/tradefed/result/ResultForwarder.java
index ba38e54ca..c65140522 100644
--- a/src/com/android/tradefed/result/ResultForwarder.java
+++ b/src/com/android/tradefed/result/ResultForwarder.java
@@ -341,6 +341,20 @@ public class ResultForwarder implements ITestInvocationListener {
}
@Override
+ public void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+ for (ITestInvocationListener listener : mListeners) {
+ try {
+ listener.testAssumptionFailure(test, failure);
+ } catch (RuntimeException e) {
+ CLog.e(
+ "Exception while invoking %s#testAssumptionFailure",
+ listener.getClass().getName());
+ CLog.e(e);
+ }
+ }
+ }
+
+ @Override
public void testIgnored(TestDescription test) {
for (ITestInvocationListener listener : mListeners) {
try {
diff --git a/src/com/android/tradefed/result/suite/SuiteResultReporter.java b/src/com/android/tradefed/result/suite/SuiteResultReporter.java
index 424cd9d44..82e46855c 100644
--- a/src/com/android/tradefed/result/suite/SuiteResultReporter.java
+++ b/src/com/android/tradefed/result/suite/SuiteResultReporter.java
@@ -401,6 +401,9 @@ public class SuiteResultReporter extends CollectingTestListener {
@Override
public TestSummary getSummary() {
+ if (mSummary == null || mSummary.toString().isEmpty()) {
+ return null;
+ }
TestSummary summary = new TestSummary(new TypedString(mSummary.toString(), Type.TEXT));
summary.setSource(SUITE_REPORTER_SOURCE);
return summary;
diff --git a/src/com/android/tradefed/retry/BaseRetryDecision.java b/src/com/android/tradefed/retry/BaseRetryDecision.java
index 0228ae9a5..599aed8d4 100644
--- a/src/com/android/tradefed/retry/BaseRetryDecision.java
+++ b/src/com/android/tradefed/retry/BaseRetryDecision.java
@@ -20,14 +20,20 @@ import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.StubDevice;
+import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
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.log.LogUtil.CLog;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
+import com.android.tradefed.targetprep.TargetSetupError;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestFilterReceiver;
import com.android.tradefed.testtype.retry.IAutoRetriableTest;
+import com.android.tradefed.testtype.suite.ModuleDefinition;
import java.util.ArrayList;
import java.util.HashSet;
@@ -53,6 +59,13 @@ public class BaseRetryDecision implements IRetryDecision {
private boolean mRebootAtLastRetry = false;
@Option(
+ name = "reset-at-last-retry",
+ description =
+ "Reset or powerwash the device at the last retry attempt. If this option is "
+ + "set, option `reboot-at-last-retry` will be ignored.")
+ private boolean mResetAtLastRetry = false;
+
+ @Option(
name = "max-testcase-run-count",
description =
"If the IRemoteTest can have its testcases run multiple times, "
@@ -113,6 +126,16 @@ public class BaseRetryDecision implements IRetryDecision {
public boolean shouldRetry(
IRemoteTest test, int attemptJustExecuted, List<TestRunResult> previousResults)
throws DeviceNotAvailableException {
+ return shouldRetry(test, null, attemptJustExecuted, previousResults);
+ }
+
+ @Override
+ public boolean shouldRetry(
+ IRemoteTest test,
+ ModuleDefinition module,
+ int attemptJustExecuted,
+ List<TestRunResult> previousResults)
+ throws DeviceNotAvailableException {
// Keep track of some results for the test in progress for statistics purpose.
if (test != mCurrentlyConsideredTest) {
mCurrentlyConsideredTest = test;
@@ -143,7 +166,7 @@ public class BaseRetryDecision implements IRetryDecision {
boolean shouldRetry = handleRetryFailures(filterableTest, previousResults);
if (shouldRetry) {
// In case of retry, go through the recovery routine
- recoverStateOfDevices(getDevices(), attemptJustExecuted);
+ recoverStateOfDevices(getDevices(), attemptJustExecuted, module);
}
return shouldRetry;
} else if (test instanceof IAutoRetriableTest) {
@@ -295,13 +318,74 @@ public class BaseRetryDecision implements IRetryDecision {
}
/** Recovery attempt on the device to get it a better state before next retry. */
- private void recoverStateOfDevices(List<ITestDevice> devices, int lastAttempt)
+ private void recoverStateOfDevices(
+ List<ITestDevice> devices, int lastAttempt, ModuleDefinition module)
throws DeviceNotAvailableException {
+ if (lastAttempt == (mMaxRetryAttempts - 2)) {
+ if (mResetAtLastRetry) {
+ resetDevice(module, devices);
+ } else if (mRebootAtLastRetry) {
+ for (ITestDevice device : devices) {
+ device.reboot();
+ continue;
+ }
+ }
+ }
+ }
+
+ private void resetDevice(ModuleDefinition module, List<ITestDevice> devices)
+ throws DeviceNotAvailableException {
+ CLog.d("Reset devices...");
+ int deviceResetCount = 0;
for (ITestDevice device : devices) {
- if (mRebootAtLastRetry && (lastAttempt == (mMaxRetryAttempts - 2))) {
- device.reboot();
+ if (!(device instanceof RemoteAndroidVirtualDevice)) {
+ CLog.i(
+ "Device %s of type %s does not support powerwash.",
+ device.getSerialNumber(), device.getClass());
continue;
}
+ boolean success = false;
+ try {
+ success = ((RemoteAndroidVirtualDevice) device).powerwashGce();
+ deviceResetCount++;
+ } catch (TargetSetupError e) {
+ CLog.e(e);
+ throw new DeviceNotAvailableException(
+ String.format(
+ "Failed to powerwash device: %s\nError: %s",
+ device.getSerialNumber(), e.toString()),
+ e,
+ device.getSerialNumber(),
+ DeviceErrorIdentifier.DEVICE_FAILED_TO_RESET);
+ }
+
+ if (!success) {
+ throw new DeviceNotAvailableException(
+ String.format("Failed to powerwash device: %s", device.getSerialNumber()),
+ device.getSerialNumber(),
+ DeviceErrorIdentifier.DEVICE_FAILED_TO_RESET);
+ }
+ }
+
+ if (module != null) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.DEVICE_RESET_MODULES, module.getId());
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.DEVICE_RESET_COUNT, deviceResetCount);
+
+ // Run all preparers including suite level ones.
+ Throwable preparationException =
+ module.runPreparation(true /* includeSuitePreparers */);
+ if (preparationException != null) {
+ CLog.e(preparationException);
+ throw new DeviceNotAvailableException(
+ String.format(
+ "Failed to reset devices before retry: %s",
+ preparationException.toString()),
+ preparationException,
+ devices.get(0).getSerialNumber(),
+ DeviceErrorIdentifier.DEVICE_FAILED_TO_RESET);
+ }
}
}
}
diff --git a/src/com/android/tradefed/retry/IRetryDecision.java b/src/com/android/tradefed/retry/IRetryDecision.java
index 70b3e0039..bc0facef4 100644
--- a/src/com/android/tradefed/retry/IRetryDecision.java
+++ b/src/com/android/tradefed/retry/IRetryDecision.java
@@ -19,6 +19,7 @@ import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.suite.ModuleDefinition;
import java.util.List;
@@ -58,6 +59,24 @@ public interface IRetryDecision {
throws DeviceNotAvailableException;
/**
+ * Decide whether or not retry should be attempted. Also make any necessary changes to the
+ * {@link IRemoteTest} to be retried (Applying filters, etc.).
+ *
+ * @param test The {@link IRemoteTest} that just ran.
+ * @param module The {@link ModuleDefinition} object for the test module.
+ * @param attemptJustExecuted The number of the attempt that we just ran.
+ * @param previousResults The list of {@link TestRunResult} of the test that just ran.
+ * @return True if we should retry, False otherwise.
+ * @throws DeviceNotAvailableException Can be thrown during device recovery
+ */
+ public boolean shouldRetry(
+ IRemoteTest test,
+ ModuleDefinition module,
+ int attemptJustExecuted,
+ List<TestRunResult> previousResults)
+ throws DeviceNotAvailableException;
+
+ /**
* {@link #shouldRetry(IRemoteTest, int, List)} will most likely be called before the last retry
* attempt, so we might be missing the very last attempt results for statistics purpose. This
* method allows those results to be provided for proper statistics calculations.
diff --git a/src/com/android/tradefed/retry/ResultAggregator.java b/src/com/android/tradefed/retry/ResultAggregator.java
index 43848fda7..b028bdfe9 100644
--- a/src/com/android/tradefed/retry/ResultAggregator.java
+++ b/src/com/android/tradefed/retry/ResultAggregator.java
@@ -257,6 +257,12 @@ public class ResultAggregator extends CollectingTestListener {
}
@Override
+ public void testAssumptionFailure(TestDescription test, FailureDescription failure) {
+ super.testAssumptionFailure(test, failure);
+ mDetailedForwarder.testAssumptionFailure(test, failure);
+ }
+
+ @Override
public void testFailed(TestDescription test, String trace) {
super.testFailed(test, trace);
mDetailedForwarder.testFailed(test, trace);
diff --git a/src/com/android/tradefed/sandbox/ISandbox.java b/src/com/android/tradefed/sandbox/ISandbox.java
index c78c1a9b8..ba0fc67f9 100644
--- a/src/com/android/tradefed/sandbox/ISandbox.java
+++ b/src/com/android/tradefed/sandbox/ISandbox.java
@@ -65,7 +65,7 @@ public interface ISandbox {
*/
public File getTradefedSandboxEnvironment(
IInvocationContext context, IConfiguration nonVersionedConfig, String[] args)
- throws ConfigurationException;
+ throws Exception;
/**
* Create a classpath based on the environment and the working directory returned by {@link
diff --git a/src/com/android/tradefed/sandbox/TradefedSandbox.java b/src/com/android/tradefed/sandbox/TradefedSandbox.java
index 0659c7c95..942955d88 100644
--- a/src/com/android/tradefed/sandbox/TradefedSandbox.java
+++ b/src/com/android/tradefed/sandbox/TradefedSandbox.java
@@ -256,7 +256,7 @@ public class TradefedSandbox implements ISandbox {
config.getCommandLine(),
/** no logging */
false));
- } catch (ConfigurationException e) {
+ } catch (Exception e) {
return e;
}
@@ -293,7 +293,7 @@ public class TradefedSandbox implements ISandbox {
@Override
public File getTradefedSandboxEnvironment(
IInvocationContext context, IConfiguration nonVersionedConfig, String[] args)
- throws ConfigurationException {
+ throws Exception {
SandboxOptions options = getSandboxOptions(nonVersionedConfig);
// Check that we have no args conflicts.
if (options.getSandboxTfDirectory() != null && options.getSandboxBuildId() != null) {
diff --git a/src/com/android/tradefed/sandbox/TradefedSandboxRunner.java b/src/com/android/tradefed/sandbox/TradefedSandboxRunner.java
index 69feed334..c26804bda 100644
--- a/src/com/android/tradefed/sandbox/TradefedSandboxRunner.java
+++ b/src/com/android/tradefed/sandbox/TradefedSandboxRunner.java
@@ -125,6 +125,8 @@ public class TradefedSandboxRunner {
initGlobalConfig(new String[] {});
mScheduler = getCommandScheduler();
mScheduler.start();
+ // Wait 2 secs to let device discovery finish
+ RunUtil.getDefault().sleep(2000);
mScheduler.execCommand(
context, new StubScheduledInvocationListener(), argList.toArray(new String[0]));
} catch (NoDeviceException e) {
diff --git a/src/com/android/tradefed/targetprep/DeviceSetup.java b/src/com/android/tradefed/targetprep/DeviceSetup.java
index 2db82ba09..9c8a83f94 100644
--- a/src/com/android/tradefed/targetprep/DeviceSetup.java
+++ b/src/com/android/tradefed/targetprep/DeviceSetup.java
@@ -894,7 +894,7 @@ public class DeviceSetup extends BaseTargetPreparer {
CLog.d("Skipping connect wifi due to force-skip-run-commands");
return;
}
- if (mWifiSsid == null && mWifiSsidToPsk.isEmpty()) {
+ if ((mWifiSsid == null || mWifiSsid.isEmpty()) && mWifiSsidToPsk.isEmpty()) {
return;
}
diff --git a/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java b/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java
index 1b9299be7..d896f006e 100644
--- a/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java
+++ b/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java
@@ -15,7 +15,7 @@
*/
package com.android.tradefed.targetprep;
-import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
@@ -80,7 +80,7 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer {
public void setUp(TestInformation testInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
ITestDevice device = testInfo.getDevice();
- IDeviceBuildInfo buildInfo = (IDeviceBuildInfo) testInfo.getBuildInfo();
+ IBuildInfo buildInfo = testInfo.getBuildInfo();
File tmpDir = null;
try {
@@ -138,10 +138,10 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer {
* Flash GKI images.
*
* @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
+ * @param buildInfo the {@link IBuildInfo} the build info
* @throws TargetSetupError, DeviceNotAvailableException, IOException
*/
- private void flashGki(ITestDevice device, IDeviceBuildInfo buildInfo)
+ private void flashGki(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError, DeviceNotAvailableException {
IDeviceManager deviceManager = getDeviceManager();
device.waitForDeviceOnline();
@@ -180,13 +180,13 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer {
* Validate GKI boot image is expected. (Obsoleted. Please call with tmpDir provided)
*
* @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
+ * @param buildInfo the {@link IBuildInfo} the build info
* @throws TargetSetupError if there is no valid gki boot.img
*/
- public void validateGkiBootImg(ITestDevice device, IDeviceBuildInfo buildInfo)
+ public void validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError {
throw new TargetSetupError(
- "Obsoleted. Please use validateGkiBootImg(ITestDevice, IDeviceBuildInfo, File)",
+ "Obsoleted. Please use validateGkiBootImg(ITestDevice, IBuildInfo, File)",
device.getDeviceDescriptor());
}
@@ -194,12 +194,12 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer {
* Validate GKI boot image is expected. Throw exception if there is no valid boot.img.
*
* @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
+ * @param buildInfo the {@link IBuildInfo} the build info
* @param tmpDir the temporary directory {@link File}
* @throws TargetSetupError if there is no valid gki boot.img
*/
@VisibleForTesting
- protected void validateGkiBootImg(ITestDevice device, IDeviceBuildInfo buildInfo, File tmpDir)
+ protected void validateGkiBootImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir)
throws TargetSetupError {
if (buildInfo.getFile(GKI_BOOT_IMG) != null && mBootImageFileName != null) {
mBootImg =
@@ -306,7 +306,10 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer {
ZipUtil2.extractZip(sourceFile, destDir);
requestedFile = FileUtil.findFile(destDir, requestedFileName);
} catch (IOException e) {
- throw new TargetSetupError(e.getMessage(), e, device.getDeviceDescriptor());
+ throw new TargetSetupError(
+ String.format("Fail to get %s from %s", requestedFileName, sourceFile),
+ e,
+ device.getDeviceDescriptor());
}
} else if (sourceFile.isDirectory()) {
requestedFile = FileUtil.findFile(sourceFile, requestedFileName);
@@ -349,7 +352,7 @@ public class GkiDeviceFlashPreparer extends BaseTargetPreparer {
throw new TargetSetupError(
String.format(
"fastboot command %s failed in device %s. stdout: %s, stderr: %s",
- cmdArgs[0],
+ cmdArgs,
device.getSerialNumber(),
result.getStdout(),
result.getStderr()),
diff --git a/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java b/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java
index b971a4a73..25bb99f77 100644
--- a/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java
+++ b/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparer.java
@@ -15,7 +15,7 @@
*/
package com.android.tradefed.targetprep;
-import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
@@ -49,9 +49,6 @@ import java.util.regex.Pattern;
@OptionClass(alias = "gsi-device-flash-preparer")
public class GsiDeviceFlashPreparer extends BaseTargetPreparer {
- private static final String GSI_SYSTEM_IMG = "gsi_system.img";
- private static final String GSI_VBMETA_IMG = "gsi_vbmeta.img";
- private static final String GKI_BOOT_IMG = "gki_boot.img";
private static final int DYNAMIC_PARTITION_API_LEVEL = 29;
// Wait time for device state to stablize in millisecond
private static final int STATE_STABLIZATION_WAIT_TIME_MLLISECS = 60000;
@@ -63,26 +60,40 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer {
private long mDeviceBootTime = 5 * 60 * 1000;
@Option(
+ name = "system-image-zip-name",
+ description = "The name of the zip file containing the system image in BuildInfo.")
+ private String mSystemImageZipName = "gsi_system.img";
+
+ @Option(
name = "system-image-file-name",
description =
- "The system image file name to search for if provided gsi_system.img is in "
- + "a zip file or directory.")
+ "The system image file name to search for if provided system image "
+ + "is in a zip file or directory.")
private String mSystemImageFileName = "system.img";
@Option(
+ name = "vbmeta-image-zip-name",
+ description = "The name of the zip file containing the system image in BuildInfo.")
+ private String mVbmetaImageZipName = "gsi_vbmeta.img";
+
+ @Option(
name = "vbmeta-image-file-name",
description =
- "The vbmeta image file name to search for if provided gsi_vbmeta.img is in "
- + "a zip file or directory.")
+ "The vbmeta image file name to search for if provided vbmeta image is "
+ + "in a zip file or directory.")
private String mVbmetaImageFileName = "vbmeta.img";
@Option(
+ name = "boot-image-zip-name",
+ description = "The name of the zip file containing the boot image in BuildInfo.")
+ private String mBootImageZipName = "gki_boot.img";
+
+ @Option(
name = "boot-image-file-name",
description =
- "The boot image file name to search for if gki_boot.img is provided in BuildInfo and the provided "
- + "file is a zip file or directory, for example boot-5.4.img. By default when gki_boot.img "
- + "is provided in BuildInfo with a zip file or file directory, the target preparer will use"
- + " the first found file that matches boot(.*).img as file name.")
+ "The boot image file name to search for if boot image is is in a zip "
+ + "file or directory, for example boot-5.4.img. The first file"
+ + "match the provided name string will be used.")
private String mBootImageFileName = "boot(.*).img";
@Option(
@@ -99,7 +110,7 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer {
public void setUp(TestInformation testInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
ITestDevice device = testInfo.getDevice();
- IDeviceBuildInfo buildInfo = (IDeviceBuildInfo) testInfo.getBuildInfo();
+ IBuildInfo buildInfo = testInfo.getBuildInfo();
File tmpDir = null;
try {
@@ -157,10 +168,10 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer {
* Flash GSI images.
*
* @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
+ * @param buildInfo the {@link IBuildInfo} the build info
* @throws TargetSetupError, DeviceNotAvailableException, IOException
*/
- private void flashGsi(ITestDevice device, IDeviceBuildInfo buildInfo)
+ private void flashGsi(ITestDevice device, IBuildInfo buildInfo)
throws TargetSetupError, DeviceNotAvailableException {
IDeviceManager deviceManager = getDeviceManager();
device.waitForDeviceOnline();
@@ -198,8 +209,8 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer {
if (shouldUseFastbootd) {
device.rebootIntoFastbootd();
if (mShouldEraseProductPartition) {
- executeFastbootCmd(
- device, "delete-logical-partition", "product" + currSlot);
+ device.executeLongFastbootCommand(
+ "delete-logical-partition", "product" + currSlot);
}
}
executeFastbootCmd(device, "erase", "system" + currSlot);
@@ -224,33 +235,38 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer {
* Validate GSI image is expected. Throw exception if there is no valid GSI image.
*
* @param device the {@link ITestDevice}
- * @param buildInfo the {@link IDeviceBuildInfo} the device build info
+ * @param buildInfo the {@link IBuildInfo} the build info
* @param tmpDir the temporary directory {@link File}
* @throws TargetSetupError if there is no valid gki boot.img
*/
- private void validateGsiImg(ITestDevice device, IDeviceBuildInfo buildInfo, File tmpDir)
+ private void validateGsiImg(ITestDevice device, IBuildInfo buildInfo, File tmpDir)
throws TargetSetupError {
- if (buildInfo.getFile(GSI_SYSTEM_IMG) == null) {
- throw new TargetSetupError(
- String.format("BuildInfo doesn't contain file key %s.", GSI_SYSTEM_IMG),
- device.getDeviceDescriptor());
- }
- if (buildInfo.getFile(GSI_VBMETA_IMG) == null) {
+ if (buildInfo.getFile(mSystemImageZipName) == null) {
throw new TargetSetupError(
- String.format("BuildInfo doesn't contain file key %s.", GSI_VBMETA_IMG),
+ String.format("BuildInfo doesn't contain file key %s.", mSystemImageZipName),
device.getDeviceDescriptor());
}
mSystemImg =
getRequestedFile(
- device, mSystemImageFileName, buildInfo.getFile(GSI_SYSTEM_IMG), tmpDir);
- mVbmetaImg =
- getRequestedFile(
- device, mVbmetaImageFileName, buildInfo.getFile(GSI_VBMETA_IMG), tmpDir);
-
- if (buildInfo.getFile(GKI_BOOT_IMG) != null && mBootImageFileName != null) {
+ device,
+ mSystemImageFileName,
+ buildInfo.getFile(mSystemImageZipName),
+ tmpDir);
+ if (buildInfo.getFile(mVbmetaImageZipName) != null) {
+ mVbmetaImg =
+ getRequestedFile(
+ device,
+ mVbmetaImageFileName,
+ buildInfo.getFile(mVbmetaImageZipName),
+ tmpDir);
+ }
+ if (buildInfo.getFile(mBootImageZipName) != null && mBootImageFileName != null) {
mBootImg =
getRequestedFile(
- device, mBootImageFileName, buildInfo.getFile(GKI_BOOT_IMG), tmpDir);
+ device,
+ mBootImageFileName,
+ buildInfo.getFile(mBootImageZipName),
+ tmpDir);
}
}
@@ -300,7 +316,10 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer {
ZipUtil2.extractZip(sourceFile, destDir);
requestedFile = FileUtil.findFile(destDir, requestedFileName);
} catch (IOException e) {
- throw new TargetSetupError(e.getMessage(), e, device.getDeviceDescriptor());
+ throw new TargetSetupError(
+ String.format("Fail to get %s from %s", requestedFileName, sourceFile),
+ e,
+ device.getDeviceDescriptor());
}
} else if (sourceFile.isDirectory()) {
requestedFile = FileUtil.findFile(sourceFile, requestedFileName);
@@ -343,7 +362,7 @@ public class GsiDeviceFlashPreparer extends BaseTargetPreparer {
throw new TargetSetupError(
String.format(
"fastboot command %s failed in device %s. stdout: %s, stderr: %s",
- cmdArgs[0],
+ cmdArgs,
device.getSerialNumber(),
result.getStdout(),
result.getStderr()),
diff --git a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
index 3c8140778..936df0f63 100644
--- a/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparer.java
@@ -65,15 +65,20 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller {
private static final int R_SDK_INT = 30;
private List<ApexInfo> mTestApexInfoList = new ArrayList<>();
+ private List<ApexInfo> mModulesToUninstall = new ArrayList<>();
private Set<String> mApkToInstall = new LinkedHashSet<>();
private List<String> mApkInstalled = new ArrayList<>();
private List<String> mSplitsInstallArgs = new ArrayList<>();
private BundletoolUtil mBundletoolUtil;
private String mDeviceSpecFilePath = "";
+ private boolean mOptimizeMainlineTest = false;
@Option(name = "bundletool-file-name", description = "The file name of the bundletool jar.")
private String mBundletoolFilename;
+ @Option(name = "train-path", description = "The absoulte path of the train folder.")
+ private File mTrainFolderPath;
+
@Option(
name = "apex-staging-wait-time",
description = "The time in ms to wait for apex staged session ready.",
@@ -88,18 +93,36 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller {
+ "preloaded on device. Otherwise an exception will be thrown.")
private boolean mIgnoreIfNotPreloaded = false;
+ @Option(
+ name = "skip-apex-teardown",
+ description = "Skip teardown if all files to be installed are apex files. "
+ + "Currently, this option is only used for Test Mapping use case.")
+ private boolean mSkipApexTearDown = false;
+
@Override
public void setUp(TestInformation testInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
setTestInformation(testInfo);
ITestDevice device = testInfo.getDevice();
- if (getTestsFileName().isEmpty()) {
+ if (mTrainFolderPath != null) {
+ addApksToTestFiles();
+ }
+
+ List<File> moduleFileNames = getTestsFileName();
+ if (moduleFileNames.isEmpty()) {
CLog.i("No apk/apex module file to install. Skipping.");
return;
}
- cleanUpStagedAndActiveSession(device);
+ if (!mSkipApexTearDown || hasApkFilesToInstall(moduleFileNames)) {
+ // Cleanup the device if skip-apex-teardown isn't set or not all files to be installed
+ // are apex files. It will always run with the target preparer.
+ cleanUpStagedAndActiveSession(device);
+ }
+ else {
+ mOptimizeMainlineTest = true;
+ }
Set<ApexInfo> activatedApexes = device.getActiveApexes();
@@ -113,9 +136,36 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller {
CLog.i("No modules are preloaded on the device, so no modules will be installed.");
return;
}
+
+ if (mOptimizeMainlineTest) {
+ CLog.i("Optimizing install apex module target preparer.");
+ // Get the apex files that are already installed on the device.
+ Set<ApexInfo> apexInData = getApexInData(activatedApexes);
+
+ // Get the apex files that are not used by the current test and will be uninstalled.
+ mModulesToUninstall.addAll(
+ getModulesToUninstall(apexInData, testAppFiles, device));
+
+ for (ApexInfo m : mModulesToUninstall) {
+ CLog.i("Uninstalling module: %s", m.name);
+ super.uninstallPackage(device, m.name);
+ }
+
+ if (testAppFiles.isEmpty()) {
+ if (!mModulesToUninstall.isEmpty()) {
+ RunUtil.getDefault().sleep(mApexStagingWaitTime);
+ device.reboot();
+ }
+ // If both the list of files to be installed and uninstalled are empty, that means
+ // the mainline modules are the same as the previous ones.
+ CLog.i("All required modules are installed");
+ return;
+ }
+ }
+
if (containsApks(testAppFiles)) {
installUsingBundleTool(testInfo, testAppFiles);
- if (mTestApexInfoList.isEmpty()) {
+ if (mTestApexInfoList.isEmpty() && mModulesToUninstall.isEmpty()) {
CLog.i("No Apex module in the train. Skipping reboot.");
return;
} else {
@@ -164,8 +214,69 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller {
CLog.i("Train activation succeed.");
}
+ /**
+ * Get a set of modules that will be uninstalled.
+ *
+ * @param apexInData A Set<ApexInfo> of modules that are installed on the /data directory.
+ * @param testFiles A List<File> of modules that will be installed on the device.
+ * @param device the {@link ITestDevice}
+ * @return A Set<ApexInfo> of modules that will be uninstalled on the device.
+ */
+ @VisibleForTesting
+ Set<ApexInfo> getModulesToUninstall(Set<ApexInfo> apexInData,
+ List<File> testFiles, ITestDevice device) throws TargetSetupError {
+ Set<ApexInfo> unInstallModules = new HashSet<>(apexInData);
+ List<File> filesToSkipInstall = new ArrayList<>();
+ for (File testFile : testFiles) {
+ String packageName = parsePackageName(testFile, device.getDeviceDescriptor());
+ for (ApexInfo apexModule : apexInData) {
+ if (apexModule.name.equals(packageName)) {
+ unInstallModules.remove(apexModule);
+ filesToSkipInstall.add(testFile);
+ }
+ }
+ }
+ // Update the modules to be installed based on what will not be installed.
+ testFiles.removeAll(filesToSkipInstall);
+ return unInstallModules;
+ }
+
+ /**
+ * Return a set of files that is already installed on the /data directory.
+ */
+ @VisibleForTesting
+ Set<ApexInfo> getApexInData(Set<ApexInfo> activatedApexes) {
+ Set<ApexInfo> apexInData = new HashSet<>();
+ for (ApexInfo apex : activatedApexes) {
+ if (apex.sourceDir.startsWith(ACTIVATED_APEX_SOURCEDIR_PREFIX, 1)) {
+ apexInData.add(apex);
+ }
+ }
+ return apexInData;
+ }
+
+ /**
+ * Check if the files to be installed contain .apk or .apks.
+ *
+ * @param testAppFiles List<File> of the modules that will be installed on the device.
+ * @return true if the files contain .apk or .apks, otherwise false.
+ */
+ private boolean hasApkFilesToInstall(List<File> testAppFiles) {
+ List<String> checkLists = Arrays.asList(".apk", ".apks");
+ for (File testAppFile : testAppFiles) {
+ if (checkLists.stream().anyMatch(entry -> testAppFile.getName().endsWith(entry))) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@Override
public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ if (mOptimizeMainlineTest) {
+ CLog.d("Skipping tearDown since the installed modules may be used for the next test.");
+ return;
+ }
ITestDevice device = testInfo.getDevice();
if (e instanceof DeviceNotAvailableException) {
CLog.e("Device %s is not available. Teardown() skipped.", device.getSerialNumber());
@@ -194,7 +305,13 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller {
if (mBundletoolUtil != null) {
return;
}
- File bundletoolJar = getLocalPathForFilename(testInfo, getBundletoolFileName());
+ File bundletoolJar;
+ File f = new File(getBundletoolFileName());
+ if (!f.isAbsolute()) {
+ bundletoolJar = getLocalPathForFilename(testInfo, getBundletoolFileName());
+ } else {
+ bundletoolJar = f;
+ }
if (bundletoolJar == null) {
throw new TargetSetupError(
String.format("Failed to find bundletool jar %s.", getBundletoolFileName()),
@@ -474,7 +591,12 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller {
throws TargetSetupError, DeviceNotAvailableException {
ITestDevice device = testInfo.getDevice();
for (File moduleFileName : testAppFileNames) {
- File moduleFile = getLocalPathForFilename(testInfo, moduleFileName.getName());
+ File moduleFile;
+ if (!moduleFileName.isAbsolute()) {
+ moduleFile = getLocalPathForFilename(testInfo, moduleFileName.getName());
+ } else {
+ moduleFile = moduleFileName;
+ }
if (moduleFileName.getName().endsWith(SPLIT_APKS_SUFFIX)) {
List<File> splits = getSplitsForApks(testInfo, moduleFile);
String splitsArgs = createInstallArgsForSplit(splits, device);
@@ -731,6 +853,16 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller {
return failToActivateApex;
}
+ private void addApksToTestFiles() {
+ File[] filesUnderTrainFolder = mTrainFolderPath.listFiles();
+ Arrays.sort(filesUnderTrainFolder, (a, b) -> a.getName().compareTo(b.getName()));
+ for (File f : filesUnderTrainFolder) {
+ if (f.getName().endsWith(".apks")) {
+ getTestsFileName().add(f);
+ }
+ }
+ }
+
@VisibleForTesting
protected String getBundletoolFileName() {
return mBundletoolFilename;
@@ -745,4 +877,9 @@ public class InstallApexModuleTargetPreparer extends SuiteApkInstaller {
protected List<String> getApkInstalled() {
return mApkInstalled;
}
+
+ @VisibleForTesting
+ public void setSkipApexTearDown(boolean skip) {
+ mSkipApexTearDown = skip;
+ }
}
diff --git a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
index 007c59af3..a33a532ed 100644
--- a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
+++ b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
@@ -33,6 +33,7 @@ import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.util.AaptParser;
+import com.android.tradefed.util.AaptParser.AaptVersion;
import com.android.tradefed.util.AbiFormatter;
import com.android.tradefed.util.BuildTestsZipUtils;
@@ -129,6 +130,14 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
+ "preparer does not verify if the apks are successfully removed.")
private boolean mCleanup = true;
+ @VisibleForTesting static final String CHECK_MIN_SDK_OPTION = "check-min-sdk";
+
+ @Option(
+ name = CHECK_MIN_SDK_OPTION,
+ description =
+ "check app's min sdk prior to install and skip if device api level is too low.")
+ private boolean mCheckMinSdk = false;
+
/** @deprecated use test-file-name instead now that it is a File. */
@Deprecated
@Option(
@@ -152,6 +161,9 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
@Option(name = "instant-mode", description = "Whether or not to install apk in instant mode.")
private boolean mInstantMode = false;
+ @Option(name = "aapt-version", description = "The version of AAPT for APK parsing.")
+ private AaptVersion mAaptVersion = AaptVersion.AAPT;
+
@Option(
name = "force-install-mode",
description =
@@ -163,7 +175,7 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
private Integer mUserId = null;
private Boolean mGrantPermission = null;
- private Set<String> mPackagesInstalled = null;
+ private Set<String> mPackagesInstalled = new HashSet<>();
private TestInformation mTestInfo;
protected void setTestInformation(TestInformation testInfo) {
@@ -180,6 +192,12 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
addTestFile(new File(fileName));
}
+ /** Helper to parse an apk file with aapt. */
+ @VisibleForTesting
+ AaptParser doAaptParse(File apkFile) {
+ return AaptParser.parse(apkFile);
+ }
+
@VisibleForTesting
void clearTestFile() {
mTestFiles.clear();
@@ -221,6 +239,11 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
mGrantPermission = shouldGrant;
}
+ /** Sets the version of AAPT for APK parsing. */
+ public void setAaptVersion(AaptVersion aaptVersion) {
+ mAaptVersion = aaptVersion;
+ }
+
/** Adds one apk installation arg to be used. */
public void addInstallArg(String arg) {
mInstallArgs.add(arg);
@@ -277,10 +300,6 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
CLog.i("No test apps to install, skipping");
return;
}
- if (mCleanup) {
- mPackagesInstalled = new HashSet<>();
- }
-
// resolve abi flags
if (mAbi != null && mForceAbi != null) {
throw new IllegalStateException("cannot specify both abi flags: --abi and --force-abi");
@@ -291,7 +310,6 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
} else if (mForceAbi != null) {
abiName = AbiFormatter.getDefaultAbi(getDevice(), mForceAbi);
}
-
// Set all the extra install args outside the loop to avoid adding them several times.
if (abiName != null && testInfo.getDevice().getApiLevel() > 20) {
mInstallArgs.add(String.format("--abi %s", abiName));
@@ -370,7 +388,7 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
@Override
public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
mTestInfo = testInfo;
- if (mCleanup && mPackagesInstalled != null && !(e instanceof DeviceNotAvailableException)) {
+ if (mCleanup && !(e instanceof DeviceNotAvailableException)) {
for (String packageName : mPackagesInstalled) {
try {
uninstallPackage(getDevice(), packageName);
@@ -454,8 +472,9 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
}
/** Helper to resolve some apk to their File and Package. */
+ @VisibleForTesting
protected Map<File, String> resolveApkFiles(TestInformation testInfo, List<File> apkFiles)
- throws TargetSetupError {
+ throws TargetSetupError, DeviceNotAvailableException {
Map<File, String> appFiles = new LinkedHashMap<>();
ITestDevice device = testInfo.getDevice();
for (File apkFile : apkFiles) {
@@ -488,7 +507,32 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
}
}
- appFiles.put(testAppFile, parsePackageName(testAppFile, device.getDeviceDescriptor()));
+ if (mCheckMinSdk) {
+ AaptParser aaptParser = doAaptParse(testAppFile);
+ if (aaptParser == null) {
+ throw new TargetSetupError(
+ String.format(
+ "Failed to extract info from `%s` using aapt",
+ testAppFile.getAbsoluteFile().getName()),
+ device.getDeviceDescriptor());
+ }
+ if (device.getApiLevel() < aaptParser.getSdkVersion()) {
+ CLog.w(
+ "Skipping installing apk %s on device %s because "
+ + "SDK level require is %d, but device SDK level is %d",
+ apkFile.toString(),
+ device.getSerialNumber(),
+ aaptParser.getSdkVersion(),
+ device.getApiLevel());
+ } else {
+ appFiles.put(
+ testAppFile,
+ parsePackageName(testAppFile, device.getDeviceDescriptor()));
+ }
+ } else {
+ appFiles.put(
+ testAppFile, parsePackageName(testAppFile, device.getDeviceDescriptor()));
+ }
}
return appFiles;
}
@@ -517,14 +561,16 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
String.format(
"Could not list files of specified directory: %s", fileOrDirectory),
e,
- deviceDescriptor);
+ deviceDescriptor,
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
if (mThrowIfNoFile && apkFiles.isEmpty()) {
throw new TargetSetupError(
String.format(
"Could not find any files in specified directory: %s", fileOrDirectory),
- deviceDescriptor);
+ deviceDescriptor,
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
}
return apkFiles;
@@ -588,7 +634,7 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
/** Get the package name from the test app. */
protected String parsePackageName(File testAppFile, DeviceDescriptor deviceDescriptor)
throws TargetSetupError {
- AaptParser parser = AaptParser.parse(testAppFile);
+ AaptParser parser = AaptParser.parse(testAppFile, mAaptVersion);
if (parser == null) {
throw new TargetSetupError(
"apk installed but AaptParser failed",
@@ -598,4 +644,3 @@ public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiRecei
return parser.getPackageName();
}
}
-
diff --git a/src/com/android/tradefed/testtype/SubprocessTfLauncher.java b/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
index bbb8ea0e6..814ecf52a 100644
--- a/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
+++ b/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
@@ -23,6 +23,7 @@ import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.IConfigurationReceiver;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
@@ -31,6 +32,7 @@ import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.result.proto.StreamProtoReceiver;
import com.android.tradefed.result.proto.StreamProtoResultReporter;
import com.android.tradefed.util.CommandResult;
@@ -409,11 +411,12 @@ public abstract class SubprocessTfLauncher
if (result.getStatus().equals(CommandStatus.TIMED_OUT)) {
errMessage = String.format("Timeout after %s",
TimeUtil.formatElapsedTime(mMaxTfRunTime));
- throw new RuntimeException(
+ throw new HarnessRuntimeException(
String.format(
"%s Tests subprocess failed due to:\n%s\n",
- mConfigName, errMessage));
- } else {
+ mConfigName, errMessage),
+ InfraErrorIdentifier.INVOCATION_TIMEOUT);
+ } else if (eventParser != null && !eventParser.reportedInvocationFailed()) {
SubprocessExceptionParser.handleStderrException(result);
}
}
diff --git a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
index 5c6348d6a..9ded1886a 100644
--- a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
@@ -284,6 +284,8 @@ public class BaseTestSuite extends ITestSuite {
if (mEnableMainlineParameter) {
mModuleRepo.setMainlineParameterizedModules(mEnableMainlineParameter);
mModuleRepo.setInvocationContext(getInvocationContext());
+ mModuleRepo.setOptimizeMainlineTest(
+ getConfiguration().getCommandOptions().getOptimizeMainlineTest());
}
mModuleRepo.setParameterizedModules(mEnableParameter);
diff --git a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
index a9434ed39..5400ddd7c 100644
--- a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
+++ b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
@@ -73,6 +73,7 @@ public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector
private IRetryDecision mRetryDecision;
private IRemoteTest mTest;
+ private ModuleDefinition mModule;
private List<IMetricCollector> mRunMetricCollectors;
private TestFailureListener mFailureListener;
private IInvocationContext mModuleInvocationContext;
@@ -95,7 +96,18 @@ public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector
TestFailureListener failureListener,
List<ITestInvocationListener> moduleLevelListeners,
int maxRunLimit) {
+ this(test, null, mainListener, failureListener, moduleLevelListeners, maxRunLimit);
+ }
+
+ public GranularRetriableTestWrapper(
+ IRemoteTest test,
+ ModuleDefinition module,
+ ITestInvocationListener mainListener,
+ TestFailureListener failureListener,
+ List<ITestInvocationListener> moduleLevelListeners,
+ int maxRunLimit) {
mTest = test;
+ mModule = module;
mMainGranularRunListener = new ModuleListener(mainListener);
mFailureListener = failureListener;
mModuleLevelListeners = moduleLevelListeners;
@@ -230,7 +242,7 @@ public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector
// Bail out early if there is no need to retry at all.
if (!mRetryDecision.shouldRetry(
- mTest, 0, mMainGranularRunListener.getTestRunForAttempts(0))) {
+ mTest, mModule, 0, mMainGranularRunListener.getTestRunForAttempts(0))) {
return;
}
// Avoid rechecking the shouldRetry below the first time as it could retrigger reboot.
@@ -245,6 +257,7 @@ public class GranularRetriableTestWrapper implements IRemoteTest, ITestCollector
boolean retry =
mRetryDecision.shouldRetry(
mTest,
+ mModule,
attemptNumber - 1,
mMainGranularRunListener.getTestRunForAttempts(
attemptNumber - 1));
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index 3561868d5..3183e9c13 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -69,13 +69,17 @@ import com.android.tradefed.testtype.ITestCollector;
import com.android.tradefed.util.AbiFormatter;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.MultiMap;
+import com.android.tradefed.util.StreamUtil;
import com.android.tradefed.util.TimeUtil;
+import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;
import com.google.inject.Injector;
import java.io.File;
import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -131,6 +135,9 @@ public abstract class ITestSuite
private static final String PRODUCT_CPU_ABI_KEY = "ro.product.cpu.abi";
+ private static final Set<String> ALLOWED_PREPARERS_CONFIGS =
+ ImmutableSet.of("/suite/allowed-preparers.txt", "/suite/google-allowed-preparers.txt");
+
// Options for test failure case
@Option(
name = "bugreport-on-failure",
@@ -509,6 +516,9 @@ public abstract class ITestSuite
return runModules;
}
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice =
+ getAllowedPreparerPerDevice(mMainConfiguration);
+
for (Entry<String, IConfiguration> config : runConfig.entrySet()) {
// Validate the configuration, it will throw if not valid.
ValidateSuiteConfigHelper.validateConfig(config.getValue());
@@ -519,6 +529,7 @@ public abstract class ITestSuite
config.getKey(),
config.getValue().getTests(),
preparersPerDevice,
+ suitePreparersPerDevice,
config.getValue().getMultiTargetPreparers(),
config.getValue());
if (mDisableAutoRetryTimeReporting) {
@@ -585,6 +596,40 @@ public abstract class ITestSuite
return res;
}
+ /** Create the mapping of device to its target_preparer that's allowed to rerun. */
+ private Map<String, List<ITargetPreparer>> getAllowedPreparerPerDevice(IConfiguration config) {
+ // For unittests, mMainConfiguration might not have been set.
+ if (config == null) {
+ return new LinkedHashMap<String, List<ITargetPreparer>>();
+ }
+ // Read the list of allowed suite level target preparers from resource files.
+ Set<String> allowedSuitePreparers = new HashSet<>();
+ for (String resource : ALLOWED_PREPARERS_CONFIGS) {
+ try (InputStream resStream = ITestSuite.class.getResourceAsStream(resource)) {
+ if (resStream == null) {
+ CLog.d("Resource not found for allowed preparers: %s", resource);
+ continue;
+ }
+ List<String> preparers =
+ Arrays.asList(StreamUtil.getStringFromStream(resStream).split("\n"));
+ allowedSuitePreparers.addAll(preparers);
+ } catch (IOException e) {
+ CLog.e(e);
+ }
+ }
+
+ Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>();
+ for (IDeviceConfiguration holder : config.getDeviceConfig()) {
+ List<ITargetPreparer> preparers = new ArrayList<>();
+ for (ITargetPreparer preparer : holder.getTargetPreparers()) {
+ if (allowedSuitePreparers.contains(preparer.getClass().getCanonicalName()))
+ preparers.add(preparer);
+ }
+ res.put(holder.getDeviceName(), preparers);
+ }
+ return res;
+ }
+
/**
* Opportunity to clean up all the things that were needed during the suites setup but are not
* required to run the tests.
@@ -948,6 +993,7 @@ public abstract class ITestSuite
ModuleSplitter.splitConfiguration(
testInfo,
runConfig,
+ getAllowedPreparerPerDevice(mMainConfiguration),
shardCountHint,
mShouldMakeDynamicModule,
mIntraModuleSharding);
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index 0d29944e3..2157877b2 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -59,6 +59,7 @@ import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.result.error.ErrorIdentifier;
import com.android.tradefed.result.error.InfraErrorIdentifier;
+import com.android.tradefed.result.error.TestErrorIdentifier;
import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.retry.IRetryDecision;
import com.android.tradefed.retry.RetryStatistics;
@@ -125,10 +126,13 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
private IConfiguration mInternalTestConfiguration;
private IConfiguration mInternalTargetPreparerConfiguration;
private ILogSaver mLogSaver;
+ private TestInformation mModuleInfo;
+ private ITestInvocationListener mInvocationListener;
private final String mId;
private Collection<IRemoteTest> mTests = null;
private Map<String, List<ITargetPreparer>> mPreparersPerDevice = null;
+ private Map<String, List<ITargetPreparer>> mSuitePreparersPerDevice = null;
private List<IMultiTargetPreparer> mMultiPreparers = new ArrayList<>();
private IBuildInfo mBuild;
@@ -174,6 +178,24 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
Map<String, List<ITargetPreparer>> preparersPerDevice,
List<IMultiTargetPreparer> multiPreparers,
IConfiguration moduleConfig) {
+ this(name, tests, preparersPerDevice, null, multiPreparers, moduleConfig);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param name unique name of the test configuration.
+ * @param tests list of {@link IRemoteTest} that needs to run.
+ * @param preparersPerDevice list of {@link ITargetPreparer} to be used to setup the device.
+ * @param moduleConfig the {@link IConfiguration} of the underlying module config.
+ */
+ public ModuleDefinition(
+ String name,
+ Collection<IRemoteTest> tests,
+ Map<String, List<ITargetPreparer>> preparersPerDevice,
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice,
+ List<IMultiTargetPreparer> multiPreparers,
+ IConfiguration moduleConfig) {
mId = name;
mTests = tests;
mModuleConfiguration = moduleConfig;
@@ -204,6 +226,7 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
mMultiPreparers.addAll(multiPreparers);
mPreparersPerDevice = preparersPerDevice;
+ mSuitePreparersPerDevice = suitePreparersPerDevice;
// Get the tokens of the module
List<String> tokens = configDescriptor.getMetaData(ITestSuite.TOKEN_KEY);
@@ -343,6 +366,9 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
TestFailureListener failureListener,
int maxRunLimit)
throws DeviceNotAvailableException {
+ mModuleInfo = moduleInfo;
+ mInvocationListener = listener;
+
mStartModuleRunDate = System.currentTimeMillis();
// Load extra configuration for the module from module_controller
// TODO: make module_controller a full TF object
@@ -390,22 +416,10 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
moduleInfo.getDevice(), mInternalTargetPreparerConfiguration);
}
// Setup
- long prepStartTime = getCurrentTime();
if (preparationException == null) {
- preparationException = runTargetPreparation(moduleInfo, listener);
+ preparationException = runPreparation(false);
}
- // Skip multi-preparation if preparation already failed.
- if (preparationException == null) {
- for (IMultiTargetPreparer multiPreparer : mMultiPreparers) {
- preparationException = runMultiPreparerSetup(multiPreparer, moduleInfo, listener);
- if (preparationException != null) {
- mIsFailedModule = true;
- CLog.e("Some preparation step failed. failing the module %s", getId());
- break;
- }
- }
- }
- mElapsedPreparation = getCurrentTime() - prepStartTime;
+
// Run the tests
try {
if (preparationException != null) {
@@ -604,7 +618,7 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
int maxRunLimit) {
GranularRetriableTestWrapper retriableTest =
new GranularRetriableTestWrapper(
- test, listener, failureListener, moduleLevelListeners, maxRunLimit);
+ test, this, listener, failureListener, moduleLevelListeners, maxRunLimit);
retriableTest.setModuleId(getId());
retriableTest.setMarkTestsSkipped(skipTestCases);
retriableTest.setMetricCollectors(mRunMetricCollectors);
@@ -781,11 +795,41 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
}
}
+ /**
+ * Run preparers of the test, including suite level preparers if specified.
+ *
+ * @param includeSuitePreparers Set to {@code true} to also run suite level preparers.
+ * @return {@link Throwable} of any exception raised when running preparers.
+ */
+ public Throwable runPreparation(boolean includeSuitePreparers) {
+ Throwable preparationException = null;
+ long prepStartTime = getCurrentTime();
+ if (includeSuitePreparers) {
+ // Run suite level preparers.
+ preparationException = runTargetPreparation(mSuitePreparersPerDevice);
+ }
+
+ if (preparationException == null) {
+ preparationException = runTargetPreparation(mPreparersPerDevice);
+ }
+ // Skip multi-preparation if preparation already failed.
+ if (preparationException == null) {
+ for (IMultiTargetPreparer multiPreparer : mMultiPreparers) {
+ preparationException = runMultiPreparerSetup(multiPreparer);
+ if (preparationException != null) {
+ mIsFailedModule = true;
+ CLog.e("Some preparation step failed. failing the module %s", getId());
+ break;
+ }
+ }
+ }
+ mElapsedPreparation = getCurrentTime() - prepStartTime;
+ return preparationException;
+ }
+
/** Run all the prepare steps. */
private Throwable runPreparerSetup(
- TestInformation moduleInfo,
ITargetPreparer preparer,
- ITestLogger logger,
int deviceIndex) {
if (preparer.isDisabled()) {
// If disabled skip completely.
@@ -796,14 +840,14 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
try {
// set the logger in case they need it.
if (preparer instanceof ITestLoggerReceiver) {
- ((ITestLoggerReceiver) preparer).setTestLogger(logger);
+ ((ITestLoggerReceiver) preparer).setTestLogger(mInvocationListener);
}
if (preparer instanceof IInvocationContextReceiver) {
((IInvocationContextReceiver) preparer)
.setInvocationContext(mModuleInvocationContext);
}
- moduleInfo.setActiveDeviceIndex(deviceIndex);
- preparer.setUp(moduleInfo);
+ mModuleInfo.setActiveDeviceIndex(deviceIndex);
+ preparer.setUp(mModuleInfo);
return null;
} catch (BuildError
| TargetSetupError
@@ -817,13 +861,12 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
CLog.e(e);
return e;
} finally {
- moduleInfo.setActiveDeviceIndex(0);
+ mModuleInfo.setActiveDeviceIndex(0);
}
}
/** Run all multi target preparer step. */
- private Throwable runMultiPreparerSetup(
- IMultiTargetPreparer preparer, TestInformation moduleInfo, ITestLogger logger) {
+ private Throwable runMultiPreparerSetup(IMultiTargetPreparer preparer) {
if (preparer.isDisabled()) {
// If disabled skip completely.
return null;
@@ -833,13 +876,13 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
try {
// set the logger in case they need it.
if (preparer instanceof ITestLoggerReceiver) {
- ((ITestLoggerReceiver) preparer).setTestLogger(logger);
+ ((ITestLoggerReceiver) preparer).setTestLogger(mInvocationListener);
}
if (preparer instanceof IInvocationContextReceiver) {
((IInvocationContextReceiver) preparer)
.setInvocationContext(mModuleInvocationContext);
}
- preparer.setUp(moduleInfo);
+ preparer.setUp(mModuleInfo);
return null;
} catch (BuildError
| TargetSetupError
@@ -991,6 +1034,14 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
}
/**
+ * Returns the list of suite level {@link ITargetPreparer} associated with the given device name
+ */
+ @VisibleForTesting
+ List<ITargetPreparer> getSuitePreparerForDevice(String deviceName) {
+ return mSuitePreparersPerDevice.get(deviceName);
+ }
+
+ /**
* When running unit tests for ModuleDefinition we don't want to unnecessarily report some auto
* retry times.
*/
@@ -1011,7 +1062,9 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
}
listener.testRunStarted(getId(), 0, 0, System.currentTimeMillis());
FailureDescription description =
- FailureDescription.create(message).setFailureStatus(FailureStatus.NOT_EXECUTED);
+ FailureDescription.create(message)
+ .setFailureStatus(FailureStatus.NOT_EXECUTED)
+ .setErrorIdentifier(TestErrorIdentifier.MODULE_DID_NOT_EXECUTE);
listener.testRunFailed(description);
listener.testRunEnded(0, new HashMap<String, Metric>());
listener.testModuleEnded();
@@ -1059,28 +1112,28 @@ public class ModuleDefinition implements Comparable<ModuleDefinition>, ITestColl
InvocationMetricKey.AUTO_RETRY_TIME, retryTimeMs);
}
- private Throwable runTargetPreparation(TestInformation moduleInfo, ITestLogger logger) {
+ private Throwable runTargetPreparation(Map<String, List<ITargetPreparer>> preparersPerDevice) {
Throwable preparationException = null;
for (int i = 0; i < mModuleInvocationContext.getDeviceConfigNames().size(); i++) {
String deviceName = mModuleInvocationContext.getDeviceConfigNames().get(i);
- if (i >= mPreparersPerDevice.size()) {
+ if (i >= preparersPerDevice.size()) {
CLog.d(
"Main configuration has more devices than the module configuration. '%s' "
+ "will not run any preparation.",
deviceName);
continue;
}
- List<ITargetPreparer> preparers = mPreparersPerDevice.get(deviceName);
+ List<ITargetPreparer> preparers = preparersPerDevice.get(deviceName);
if (preparers == null) {
CLog.w(
"Module configuration devices mismatch the main configuration "
+ "(Missing device '%s'), resolving preparers by index.",
deviceName);
- String key = new ArrayList<>(mPreparersPerDevice.keySet()).get(i);
- preparers = mPreparersPerDevice.get(key);
+ String key = new ArrayList<>(preparersPerDevice.keySet()).get(i);
+ preparers = preparersPerDevice.get(key);
}
for (ITargetPreparer preparer : preparers) {
- preparationException = runPreparerSetup(moduleInfo, preparer, logger, i);
+ preparationException = runPreparerSetup(preparer, i);
if (preparationException != null) {
mIsFailedModule = true;
CLog.e("Some preparation step failed. failing the module %s", getId());
diff --git a/src/com/android/tradefed/testtype/suite/ModuleSplitter.java b/src/com/android/tradefed/testtype/suite/ModuleSplitter.java
index a4fa85f88..970522aea 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleSplitter.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleSplitter.java
@@ -63,6 +63,7 @@ public class ModuleSplitter {
*
* @param testInfo the current {@link TestInformation} to proceed with sharding.
* @param runConfig {@link LinkedHashMap} loaded from {@link ITestSuite#loadTests()}.
+ * @param suitePreparersPerDevice map of suite level preparers per test device.
* @param shardCount a shard count hint to help with sharding.
* @param dynamicModule Whether or not module can be shared in pool or must be independent
* (strict sharding).
@@ -72,6 +73,7 @@ public class ModuleSplitter {
public static List<ModuleDefinition> splitConfiguration(
TestInformation testInfo,
LinkedHashMap<String, IConfiguration> runConfig,
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice,
int shardCount,
boolean dynamicModule,
boolean intraModuleSharding) {
@@ -93,7 +95,8 @@ public class ModuleSplitter {
configMap.getValue(),
shardCount,
dynamicModule,
- intraModuleSharding);
+ intraModuleSharding,
+ suitePreparersPerDevice);
} catch (RuntimeException e) {
CLog.e("Exception while creating module for '%s'", configMap.getKey());
throw e;
@@ -109,7 +112,8 @@ public class ModuleSplitter {
IConfiguration config,
int shardCount,
boolean dynamicModule,
- boolean intraModuleSharding) {
+ boolean intraModuleSharding,
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice) {
List<IRemoteTest> tests = config.getTests();
// Get rid of the IRemoteTest reference on the shared configuration. It will not be used
// to run.
@@ -127,11 +131,13 @@ public class ModuleSplitter {
moduleName,
tests,
clonePreparersMap(config),
+ clonePreparersMap(suitePreparersPerDevice),
clonePreparers(config.getMultiTargetPreparers()),
config);
currentList.add(module);
} else {
- addModuleToListFromSingleTest(currentList, tests.get(i), moduleName, config);
+ addModuleToListFromSingleTest(
+ currentList, tests.get(i), moduleName, config, suitePreparersPerDevice);
}
}
clearPreparersFromConfig(config);
@@ -153,6 +159,7 @@ public class ModuleSplitter {
moduleName,
shardedTests,
clonePreparersMap(config),
+ clonePreparersMap(suitePreparersPerDevice),
clonePreparers(config.getMultiTargetPreparers()),
config);
currentList.add(module);
@@ -161,14 +168,19 @@ public class ModuleSplitter {
// We create independent modules with each sharded test.
for (IRemoteTest moduleTest : shardedTests) {
addModuleToListFromSingleTest(
- currentList, moduleTest, moduleName, config);
+ currentList,
+ moduleTest,
+ moduleName,
+ config,
+ suitePreparersPerDevice);
}
}
continue;
}
}
// test is not shardable or did not shard
- addModuleToListFromSingleTest(currentList, test, moduleName, config);
+ addModuleToListFromSingleTest(
+ currentList, test, moduleName, config, suitePreparersPerDevice);
}
clearPreparersFromConfig(config);
}
@@ -181,7 +193,8 @@ public class ModuleSplitter {
List<ModuleDefinition> currentList,
IRemoteTest test,
String moduleName,
- IConfiguration config) {
+ IConfiguration config,
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice) {
List<IRemoteTest> testList = new ArrayList<>();
testList.add(test);
ModuleDefinition module =
@@ -189,6 +202,7 @@ public class ModuleSplitter {
moduleName,
testList,
clonePreparersMap(config),
+ clonePreparersMap(suitePreparersPerDevice),
clonePreparers(config.getMultiTargetPreparers()),
config);
currentList.add(module);
@@ -234,6 +248,18 @@ public class ModuleSplitter {
return res;
}
+ /** Deep cloning of potentially multi-device preparers. */
+ private static Map<String, List<ITargetPreparer>> clonePreparersMap(
+ Map<String, List<ITargetPreparer>> suitePreparersPerDevice) {
+ Map<String, List<ITargetPreparer>> res = new LinkedHashMap<>();
+ for (String device : suitePreparersPerDevice.keySet()) {
+ List<ITargetPreparer> preparers = new ArrayList<>();
+ res.put(device, preparers);
+ preparers.addAll(clonePreparers(suitePreparersPerDevice.get(device)));
+ }
+ return res;
+ }
+
private static void clearPreparersFromConfig(IConfiguration config) {
try {
for (IDeviceConfiguration holder : config.getDeviceConfig()) {
diff --git a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
index 26a7f4ab2..2831e8bba 100644
--- a/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
+++ b/src/com/android/tradefed/testtype/suite/SuiteModuleLoader.java
@@ -79,6 +79,7 @@ public class SuiteModuleLoader {
private boolean mAllowParameterizedModules = false;
private boolean mAllowMainlineParameterizedModules = false;
+ private boolean mOptimizeMainlineTest = false;
private boolean mAllowOptionalParameterizedModules = false;
private ModuleParameters mForcedModuleParameter = null;
private Set<ModuleParameters> mExcludedModuleParameters = new HashSet<>();
@@ -121,6 +122,11 @@ public class SuiteModuleLoader {
mAllowMainlineParameterizedModules = allowed;
}
+ /** Sets whether or not to optimize mainline test. */
+ public final void setOptimizeMainlineTest(boolean allowed) {
+ mOptimizeMainlineTest = allowed;
+ }
+
/** Sets whether or not to allow optional parameterized modules. */
public final void setOptionalParameterizedModules(boolean allowed) {
mAllowOptionalParameterizedModules = allowed;
@@ -378,7 +384,8 @@ public class SuiteModuleLoader {
new MainlineModuleHandler(
param,
abi,
- mContext
+ mContext,
+ mOptimizeMainlineTest
);
skipCreatingBaseConfig = true;
IConfiguration paramConfig =
diff --git a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
index da1741fa0..60825170b 100644
--- a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
+++ b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
@@ -219,9 +219,9 @@ public class TestMappingSuiteRunner extends BaseTestSuite {
if (configPath == null) {
throw new RuntimeException(String.format("Configuration path is null."));
}
- File configFie = new File(configPath);
- if (!configFie.exists()) {
- configFie = null;
+ File configFile = new File(configPath);
+ if (!configFile.exists()) {
+ configFile = null;
}
// De-duplicate test infos so that there won't be duplicate test options.
testInfos = dedupTestInfos(testInfos);
@@ -229,10 +229,10 @@ public class TestMappingSuiteRunner extends BaseTestSuite {
// Clean up all the test options injected in SuiteModuleLoader.
super.cleanUpSuiteSetup();
super.clearModuleArgs();
- if (configFie != null) {
+ if (configFile != null) {
clearConfigPaths();
// Set config path to BaseTestSuite to limit the search.
- addConfigPaths(configFie);
+ addConfigPaths(configFile);
}
// Inject the test options from each test info to SuiteModuleLoader.
parseOptions(testInfo);
diff --git a/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java b/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java
index eb4f4a4f0..9f4af97ca 100644
--- a/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java
+++ b/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandler.java
@@ -38,11 +38,17 @@ public final class MainlineModuleHandler {
private String mDynamicBaseLink = null;
private IAbi mAbi = null;
private String mName = null;
+ private boolean mOptimizeMainlineTest = false;
- public MainlineModuleHandler(String name, IAbi abi, IInvocationContext context) {
+ public MainlineModuleHandler(
+ String name,
+ IAbi abi,
+ IInvocationContext context,
+ boolean optimize) {
mName = name;
mAbi = abi;
buildDynamicBaseLink(context.getBuildInfos().get(0));
+ mOptimizeMainlineTest = optimize;
}
/** Builds the dynamic base link where the mainline modules would be downloaded. */
@@ -78,6 +84,7 @@ public final class MainlineModuleHandler {
private InstallApexModuleTargetPreparer createMainlineModuleInstaller() {
InstallApexModuleTargetPreparer mainlineModuleInstaller =
new InstallApexModuleTargetPreparer();
+ mainlineModuleInstaller.setSkipApexTearDown(mOptimizeMainlineTest);
// Inject the real dynamic link to the target preparer so that it will dynamically download
// the mainline modules.
String fullDynamicLink = mDynamicBaseLink;
diff --git a/src/com/android/tradefed/util/AaptParser.java b/src/com/android/tradefed/util/AaptParser.java
index a88fb8f5b..a61d230ef 100644
--- a/src/com/android/tradefed/util/AaptParser.java
+++ b/src/com/android/tradefed/util/AaptParser.java
@@ -54,6 +54,48 @@ public class AaptParser {
private static final int AAPT_TIMEOUT_MS = 60000;
private static final int INVALID_SDK = -1;
+ /**
+ * Enum of options for AAPT version used to parse APK files.
+ */
+ public static enum AaptVersion {
+ AAPT {
+ @Override
+ public String[] dumpBadgingCommand(File apkFile) {
+ return new String[] {"aapt", "dump", "badging", apkFile.getAbsolutePath()};
+ }
+
+ @Override
+ public String[] dumpXmlTreeCommand(File apkFile) {
+ return new String[] {
+ "aapt", "dump", "xmltree", apkFile.getAbsolutePath(), "AndroidManifest.xml"
+ };
+ }
+ },
+
+ AAPT2 {
+ @Override
+ public String[] dumpBadgingCommand(File apkFile) {
+ return new String[] {"aapt2", "dump", "badging", apkFile.getAbsolutePath()};
+ }
+
+ @Override
+ public String[] dumpXmlTreeCommand(File apkFile) {
+ return new String[] {
+ "aapt2",
+ "dump",
+ "xmltree",
+ apkFile.getAbsolutePath(),
+ "--file",
+ "AndroidManifest.xml"
+ };
+ }
+ };
+
+ public abstract String[] dumpBadgingCommand(File apkFile);
+
+ public abstract String[] dumpXmlTreeCommand(File apkFile);
+ };
+
private String mPackageName;
private String mVersionCode;
private String mVersionName;
@@ -130,16 +172,21 @@ public class AaptParser {
* @return the {@link AaptParser} or <code>null</code> if failed to extract the information
*/
public static AaptParser parse(File apkFile) {
+ return parse(apkFile, AaptVersion.AAPT);
+ }
+
+ /**
+ * Parse info from the apk.
+ *
+ * @param apkFile the apk file
+ * @param aaptVersion the aapt version
+ * @return the {@link AaptParser} or <code>null</code> if failed to extract the information
+ */
+ public static AaptParser parse(File apkFile, AaptVersion aaptVersion) {
CommandResult result =
RunUtil.getDefault()
.runTimedCmdRetry(
- AAPT_TIMEOUT_MS,
- 0L,
- 2,
- "aapt",
- "dump",
- "badging",
- apkFile.getAbsolutePath());
+ AAPT_TIMEOUT_MS, 0L, 2, aaptVersion.dumpBadgingCommand(apkFile));
String stderr = result.getStderr();
if (stderr != null && !stderr.isEmpty()) {
@@ -158,11 +205,7 @@ public class AaptParser {
AAPT_TIMEOUT_MS,
0L,
2,
- "aapt",
- "dump",
- "xmltree",
- apkFile.getAbsolutePath(),
- "AndroidManifest.xml");
+ aaptVersion.dumpXmlTreeCommand(apkFile));
stderr = result.getStderr();
if (stderr != null && !stderr.isEmpty()) {
diff --git a/src/com/android/tradefed/util/ProtoUtil.java b/src/com/android/tradefed/util/ProtoUtil.java
new file mode 100644
index 000000000..bf1699abf
--- /dev/null
+++ b/src/com/android/tradefed/util/ProtoUtil.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2020 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 com.android.tradefed.log.LogUtil.CLog;
+
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Message;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.stream.Collectors;
+
+/** Utility methods for dealing with protobuf messages type-agnostically. */
+public class ProtoUtil {
+
+ /**
+ * Get values of a nested field reference, i.e. field_1.field_2.field_3, from a proto message as
+ * a list of strings. Returns an empty list when a field cannot be found.
+ *
+ * <p>If the field reference contains repeated fields, each instance is expanded, resulting in a
+ * list of strings.
+ *
+ * @param message The protobuf {@link Message} or object to be parsed.
+ * @param references A list of field references starting at the root of the message. e.g. if we
+ * want to read {@code field_2} under the value of {@code field_1} in {@code
+ * messageOrObject} the list would be {@code field1}, {@code field2}.
+ * @return A list of all the fields values referred to by the reference. If {@code references}
+ * is empty, returns {@code message.toString()} as a list. If {@code references} is invalid,
+ * returns an empty list.
+ */
+ public static List<String> getNestedFieldFromMessageAsStrings(
+ Message message, List<String> references) {
+ return getNestedFieldFromMessageAsStringsHelper(message, references);
+ }
+
+ /**
+ * A helper method to {@code getNestedFieldFromMessageAsStrings} where the "message" can be an
+ * object in case we reach a primitive value field during recursive parsing.
+ */
+ private static List<String> getNestedFieldFromMessageAsStringsHelper(
+ Object messageOrObject, List<String> references) {
+ if (references.isEmpty()) {
+ return Arrays.asList(String.valueOf(messageOrObject));
+ }
+ if (!(messageOrObject instanceof Message)) {
+ CLog.e(
+ "Attempting to read field %s from object of type %s, "
+ + "which is not a proto message.",
+ references.get(0), messageOrObject.getClass());
+ return new ArrayList<String>();
+ }
+ Message message = (Message) messageOrObject;
+ String reference = references.get(0);
+ FieldDescriptor fieldDescriptor = message.getDescriptorForType().findFieldByName(reference);
+ if (fieldDescriptor == null) {
+ CLog.e("Could not find field %s in message %s.", reference, message);
+ return new ArrayList<String>();
+ }
+ Object fieldValue = message.getField(fieldDescriptor);
+ if (fieldValue instanceof List) {
+ return ((List<? extends Object>) fieldValue)
+ .stream()
+ .flatMap(
+ v ->
+ getNestedFieldFromMessageAsStringsHelper(
+ v, references.subList(1, references.size()))
+ .stream())
+ .collect(Collectors.toList());
+ }
+ return getNestedFieldFromMessageAsStringsHelper(
+ fieldValue, references.subList(1, references.size()));
+ }
+}
diff --git a/src/com/android/tradefed/util/StringEscapeUtils.java b/src/com/android/tradefed/util/StringEscapeUtils.java
index 846c0e7de..4cc574114 100644
--- a/src/com/android/tradefed/util/StringEscapeUtils.java
+++ b/src/com/android/tradefed/util/StringEscapeUtils.java
@@ -47,6 +47,15 @@ public class StringEscapeUtils {
case '\\':
out.append("\\\\");
break;
+ case '>':
+ out.append("\\>");
+ break;
+ case '<':
+ out.append("\\<");
+ break;
+ case '|':
+ out.append("\\|");
+ break;
default:
out.append(ch);
break;
diff --git a/src/com/android/tradefed/util/SubprocessTestResultsParser.java b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
index 2a5d280ed..c560e6ab5 100644
--- a/src/com/android/tradefed/util/SubprocessTestResultsParser.java
+++ b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
@@ -80,6 +80,7 @@ public class SubprocessTestResultsParser implements Closeable {
private TestDescription mCurrentTest = null;
private IInvocationContext mCurrentModuleContext = null;
+ private InvocationFailedEventInfo mReportedInvocationFailedEventInfo = null;
private Pattern mPattern = null;
private Map<String, EventHandler> mHandlerMap = null;
@@ -423,6 +424,7 @@ public class SubprocessTestResultsParser implements Closeable {
} else {
mListener.invocationFailed(ifi.mCause);
}
+ mReportedInvocationFailedEventInfo = ifi;
}
}
@@ -653,4 +655,14 @@ public class SubprocessTestResultsParser implements Closeable {
public TestDescription getCurrentTest() {
return mCurrentTest;
}
+
+ /** Returns whether or not an invocation failed was reported. */
+ public boolean reportedInvocationFailed() {
+ return (mReportedInvocationFailedEventInfo != null);
+ }
+
+ /** Returns reported invocation failure event info. */
+ public InvocationFailedEventInfo getReportedInvocationFailedEventInfo() {
+ return mReportedInvocationFailedEventInfo;
+ }
}
diff --git a/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java b/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java
index ee6cc2380..7a94bf5c4 100644
--- a/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java
+++ b/src/com/android/tradefed/util/executor/ParallelDeviceExecutor.java
@@ -20,13 +20,13 @@ import com.android.tradefed.log.LogUtil.CLog;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
/** Wrapper of {@link ExecutorService} to execute a function in parallel. */
public class ParallelDeviceExecutor<V> {
@@ -43,7 +43,7 @@ public class ParallelDeviceExecutor<V> {
* Invoke all the {@link Callable} with the timeout limit.
*
* @param callableTasks The List of tasks.
- * @param timeout The timeout to apply.
+ * @param timeout The timeout to apply, or zero for unlimited.
* @param unit The unit of the timeout.
* @return The list of results for each callable task.
*/
@@ -61,12 +61,15 @@ public class ParallelDeviceExecutor<V> {
});
List<V> results = new ArrayList<>();
try {
- List<Future<V>> futures = executor.invokeAll(callableTasks);
+ List<Future<V>> futures =
+ timeout == 0L
+ ? executor.invokeAll(callableTasks)
+ : executor.invokeAll(callableTasks, timeout, unit);
for (Future<V> future : futures) {
try {
- results.add(future.get(timeout, unit));
- } catch (TimeoutException timeoutException) {
- mErrors.add(timeoutException);
+ results.add(future.get());
+ } catch (CancellationException cancellationException) {
+ mErrors.add(cancellationException);
} catch (ExecutionException execException) {
mErrors.add(execException.getCause());
}
diff --git a/test_framework/Android.bp b/test_framework/Android.bp
index 31d31a78f..75e34cc10 100644
--- a/test_framework/Android.bp
+++ b/test_framework/Android.bp
@@ -19,6 +19,7 @@ java_library_host {
"com/**/*.java",
],
static_libs: [
+ "diffutils-prebuilt-jar",
"longevity-host-lib",
"perfetto_metrics-full",
"test-composers",
diff --git a/test_framework/com/android/tradefed/device/metric/AtraceCollector.java b/test_framework/com/android/tradefed/device/metric/AtraceCollector.java
index c6ea63b1a..5a3203527 100644
--- a/test_framework/com/android/tradefed/device/metric/AtraceCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/AtraceCollector.java
@@ -27,6 +27,7 @@ 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.LogDataType;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
@@ -268,7 +269,9 @@ public class AtraceCollector extends BaseDeviceMetricCollector {
postProcess(trace);
trace.delete();
} else {
- throw new DeviceRuntimeException("failed to pull log: " + fullLogPath());
+ throw new DeviceRuntimeException(
+ String.format("failed to pull log: %s", fullLogPath()),
+ DeviceErrorIdentifier.FAIL_PULL_FILE);
}
if (!mPreserveOndeviceLog) {
diff --git a/test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java
deleted file mode 100644
index 304985e4b..000000000
--- a/test_framework/com/android/tradefed/device/metric/BuddyInfoMetricCollector.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect fragmentation at regular intervals. */
-public class BuddyInfoMetricCollector extends ScheduledDeviceMetricCollector {
- public BuddyInfoMetricCollector() {
- setTag("fragmentation");
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- CLog.i("Running unusable-index collector...");
- String outputFileName =
- String.format("%s/unusable-index-%s", createTempDir(), getFileSuffix());
- File outputFile =
- saveProcessOutput(device, "cat /d/extfrag/unusable_index", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.TEXT,
- source);
- }
-
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java b/test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java
deleted file mode 100644
index 8bf0dd6a7..000000000
--- a/test_framework/com/android/tradefed/device/metric/BugreportzMetricCollector.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect zipped bugreport at regular intervals. */
-public class BugreportzMetricCollector extends ScheduledDeviceMetricCollector {
- public BugreportzMetricCollector() {
- setTag("bugreportz");
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- CLog.i("Running bugreportz...");
-
- String hostBugreportFilename = String.format("bugreport-%s", getFileSuffix());
- if (!device.logBugreport(hostBugreportFilename, getInvocationListener())) {
- CLog.e("Failed to run bugreportz or bugreport.");
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java b/test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java
deleted file mode 100644
index 404580a88..000000000
--- a/test_framework/com/android/tradefed/device/metric/DumpHeapCollector.java
+++ /dev/null
@@ -1,130 +0,0 @@
-/*
- * Copyright (C) 2018 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.annotations.VisibleForTesting;
-import com.android.loganalysis.item.CompactMemInfoItem;
-import com.android.loganalysis.parser.CompactMemInfoParser;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.util.FileUtil;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/**
- * A {@link ScheduledDeviceMetricCollector} to collect memory dumps of processes at regular
- * intervals.
- */
-public class DumpHeapCollector extends ScheduledDeviceMetricCollector {
- private static final String DUMPHEAP_OUTPUT = "/data/local/tmp";
- private static final String SUFFIX = "trigger";
-
- @Option(
- name = "dumpheap-thresholds",
- description =
- "Threshold map for taking process dumpheaps. "
- + "The key should be the process name and its corresponding value is the "
- + "maximum acceptable heap size for that process."
- + "Note that to get heap dump for native and managed processes set their "
- + "threshold to 0."
- )
- protected Map<String, Long> mDumpheapThresholds = new HashMap<String, Long>();
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- CLog.i("Running dumpheap collection...");
- List<File> dumpFiles = new ArrayList<>();
- try {
- for (String process : mDumpheapThresholds.keySet()) {
- String output =
- device.executeShellCommand(
- String.format("dumpsys meminfo -c | grep %s", process));
-
- dumpFiles = takeDumpheap(device, output, process, mDumpheapThresholds.get(process));
-
- dumpFiles.forEach(dumpheap -> saveDumpheap(dumpheap));
- }
- } catch (DeviceNotAvailableException e) {
- CLog.e(e);
- } finally {
- dumpFiles.forEach(dumpFile -> FileUtil.deleteFile(dumpFile));
- }
- }
-
- /**
- * Collects heap dump for each requested process if the PSS is greater than a threshold.
- *
- * @param device
- * @param output of the meminfo command.
- * @param process for which we need the heap dump.
- * @param threshold which is the maximum tolerable PSS.
- * @return the list of {@link File}s in the host containing the report. Empty list if something
- * failed.
- * @throws DeviceNotAvailableException
- */
- @VisibleForTesting
- List<File> takeDumpheap(ITestDevice device, String output, String process, Long threshold)
- throws DeviceNotAvailableException {
- List<File> dumpFiles = new ArrayList<>();
- if (output.isEmpty()) {
- CLog.i("Skipping %s -- no process found.", process);
- return dumpFiles;
- }
-
- CompactMemInfoItem item =
- new CompactMemInfoParser().parse(Arrays.asList(output.split("\n")));
-
- for (Integer pid : item.getPids()) {
- if (item.getName(pid).equals(process) && item.getPss(pid) > threshold) {
- File dump = device.dumpHeap(process, getDevicePath(process));
- dumpFiles.add(dump);
- }
- }
- return dumpFiles;
- }
-
- /**
- * Returns the path on the device to put the dump.
- *
- * @param process for which dump is being requested.
- * @return a write-able path in device.
- */
- private String getDevicePath(String process) {
- return String.format(
- "%s/%s_%s_%s.hprof", DUMPHEAP_OUTPUT, process, SUFFIX, getFileSuffix());
- }
-
- @VisibleForTesting
- void saveDumpheap(File dumpheap) {
- if (dumpheap == null) {
- CLog.e("Failed to take dumpheap.");
- return;
- }
- try (FileInputStreamSource stream = new FileInputStreamSource(dumpheap)) {
- getInvocationListener()
- .testLog(FileUtil.getBaseName(dumpheap.getName()), LogDataType.HPROF, stream);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java b/test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java
deleted file mode 100644
index 8e73edac3..000000000
--- a/test_framework/com/android/tradefed/device/metric/GraphicsStatsMetricCollector.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2017 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.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect graphics stats at regular intervals. */
-public class GraphicsStatsMetricCollector extends ScheduledDeviceMetricCollector {
- GraphicsStatsMetricCollector() {
- setTag("jank");
- }
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- CLog.i("Running graphicsstats...");
- String outputFileName =
- String.format("%s/graphics-%s", createTempDir(), getFileSuffix());
- File outputFile = saveProcessOutput(device, "dumpsys graphicsstats", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.GFX_INFO,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java
deleted file mode 100644
index 3312855db..000000000
--- a/test_framework/com/android/tradefed/device/metric/IonHeapInfoMetricCollector.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/**
- * A {@link ScheduledDeviceMetricCollector} to collect audio and system memory heaps at regular
- * intervals.
- */
-public class IonHeapInfoMetricCollector extends ScheduledDeviceMetricCollector {
- public IonHeapInfoMetricCollector() {
- setTag("ion");
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- collectIonAudio(device);
- collectIonSystem(device);
- }
-
- private void collectIonAudio(ITestDevice device) {
- try {
- CLog.i("Running ionheap audio collector...");
- String outputFileName =
- String.format("%s/ion-audio-%s", createTempDir(), getFileSuffix());
- File outputFile = saveProcessOutput(device, "cat /d/ion/heaps/audio", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.TEXT,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-
- private void collectIonSystem(ITestDevice device) {
- try {
- CLog.i("Running ionheap system collector...");
- String outputFileName =
- String.format("%s/ion-system-%s", createTempDir(), getFileSuffix());
- File outputFile = saveProcessOutput(device, "cat /d/ion/heaps/system", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.TEXT,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java
deleted file mode 100644
index 0890b7408..000000000
--- a/test_framework/com/android/tradefed/device/metric/MemInfoMetricCollector.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2017 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.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect memory dumps at regular intervals. */
-public class MemInfoMetricCollector extends ScheduledDeviceMetricCollector {
- MemInfoMetricCollector() {
- setTag("compact-meminfo");
- }
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- CLog.i("Running meminfo...");
- String outputFileName =
- String.format("%s/compact-meminfo-%s", createTempDir(), getFileSuffix());
- File outputFile = saveProcessOutput(device, "dumpsys meminfo -c -S", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.COMPACT_MEMINFO,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java b/test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java
deleted file mode 100644
index 9ab0f3381..000000000
--- a/test_framework/com/android/tradefed/device/metric/PagetypeInfoMetricCollector.java
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect free page counts at regular intervals. */
-public class PagetypeInfoMetricCollector extends ScheduledDeviceMetricCollector {
- public PagetypeInfoMetricCollector() {
- setTag("pagetypeinfo");
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- CLog.i("Running pagetype info collector...");
- String outputFileName =
- String.format("%s/pagetypeinfo-%s", createTempDir(), getFileSuffix());
- File outputFile = saveProcessOutput(device, "cat /proc/pagetypeinfo", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.TEXT,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
index 9e5662610..4d496a411 100644
--- a/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/PerfettoPullerMetricCollector.java
@@ -144,6 +144,11 @@ public class PerfettoPullerMetricCollector extends FilePullerDeviceMetricCollect
description = "Convert the raw trace file to perfetto metric file.")
private boolean mConvertToMetricFile = true;
+ @Option(name = "collect-perfetto-file-size",
+ description = "Set it to true to collect the perfetto file size as part"
+ + " of the metrics.")
+ private boolean mCollectPerfettoFileSize = false;
+
@Option(
name = "trace-processor-binary",
description = "Path to the trace processor shell. This will"
@@ -193,7 +198,7 @@ public class PerfettoPullerMetricCollector extends FilePullerDeviceMetricCollect
}
// Update the file size metrics.
- if (processSrcFile != null) {
+ if (processSrcFile != null && mCollectPerfettoFileSize) {
double perfettoFileSizeInBytes = processSrcFile.length();
Metric.Builder metricDurationBuilder = Metric.newBuilder();
metricDurationBuilder.getMeasurementsBuilder().setSingleDouble(
diff --git a/test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java b/test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java
deleted file mode 100644
index 55281776b..000000000
--- a/test_framework/com/android/tradefed/device/metric/ProcessMaxMemoryCollector.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2018 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.loganalysis.item.DumpsysProcessMeminfoItem;
-import com.android.loganalysis.parser.DumpsysProcessMeminfoParser;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.metrics.proto.MetricMeasurement.NumericValues;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-
-/**
- * A {@link ScheduledDeviceMetricCollector} to measure peak memory usage of specified processes.
- * Collects PSS and USS (private dirty) memory usage values from dumpsys meminfo. The result will be
- * reported as a test run metric with key in the form of PSS#ProcName[#DeviceNum], in KB.
- */
-public class ProcessMaxMemoryCollector extends ScheduledDeviceMetricCollector {
-
- @Option(
- name = "memory-usage-process-name",
- description = "Process names (from `dumpsys meminfo`) to measure memory usage for"
- )
- private List<String> mProcessNames = new ArrayList<>();
-
- private class DeviceMemoryData {
- /** Peak PSS per process */
- private Map<String, Long> mProcPss = new HashMap<>();
- /** Peak USS per process */
- private Map<String, Long> mProcUss = new HashMap<>();
- }
-
- // Memory usage data per device
- private Map<ITestDevice, DeviceMemoryData> mMemoryData;
- private Map<ITestDevice, Map<String, NumericValues.Builder>> mPssMemoryPerProcess;
- private Map<ITestDevice, Map<String, NumericValues.Builder>> mUssMemoryPerProcess;
-
- @Override
- void onStart(DeviceMetricData runData) {
- mMemoryData = new HashMap<>();
- mPssMemoryPerProcess = new HashMap<>();
- mUssMemoryPerProcess = new HashMap<>();
-
- for (ITestDevice device : getDevices()) {
- mMemoryData.put(device, new DeviceMemoryData());
- mPssMemoryPerProcess.put(device, new HashMap<>());
- mUssMemoryPerProcess.put(device, new HashMap<>());
- }
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- Map<String, Long> procPss = mMemoryData.get(device).mProcPss;
- Map<String, Long> procUss = mMemoryData.get(device).mProcUss;
- for (String proc : mProcessNames) {
- String dumpResult = device.executeShellCommand("dumpsys meminfo --checkin " + proc);
- if (dumpResult.startsWith("No process found")) {
- // process not found, skip
- continue;
- }
- DumpsysProcessMeminfoItem item =
- new DumpsysProcessMeminfoParser()
- .parse(Arrays.asList(dumpResult.split("\n")));
- Long pss =
- item.get(DumpsysProcessMeminfoItem.TOTAL)
- .get(DumpsysProcessMeminfoItem.PSS);
- Long uss =
- item.get(DumpsysProcessMeminfoItem.TOTAL)
- .get(DumpsysProcessMeminfoItem.PRIVATE_DIRTY);
- if (pss == null || uss == null) {
- CLog.e("Error parsing meminfo output: " + dumpResult);
- continue;
- }
-
- // Track PSS values
- if (mPssMemoryPerProcess.get(device) == null) {
- mPssMemoryPerProcess.put(device, new HashMap<>());
- }
- if (mPssMemoryPerProcess.get(device).get(proc) == null) {
- mPssMemoryPerProcess.get(device).put(proc, NumericValues.newBuilder());
- }
- mPssMemoryPerProcess.get(device).get(proc).addNumericValue(pss);
-
- // Track USS values
- if (mUssMemoryPerProcess.get(device) == null) {
- mUssMemoryPerProcess.put(device, new HashMap<>());
- }
- if (mUssMemoryPerProcess.get(device).get(proc) == null) {
- mUssMemoryPerProcess.get(device).put(proc, NumericValues.newBuilder());
- }
- mUssMemoryPerProcess.get(device).get(proc).addNumericValue(uss);
-
- if (procPss.getOrDefault(proc, 0L) < pss) {
- procPss.put(proc, pss);
- }
- if (procUss.getOrDefault(proc, 0L) < uss) {
- procUss.put(proc, uss);
- }
- }
- } catch (DeviceNotAvailableException e) {
- CLog.e(e);
- }
- }
-
- @Override
- void onEnd(DeviceMetricData runData) {
- for (ITestDevice device : getDevices()) {
- // Report all the PSS data for each process
- for (Entry<String, NumericValues.Builder> values :
- mPssMemoryPerProcess.get(device).entrySet()) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(
- Measurements.newBuilder()
- .setNumericValues(values.getValue().build()))
- .build();
- metric.setUnit("kB").setType(DataType.RAW);
- runData.addMetricForDevice(device, "PSS#" + values.getKey(), metric);
- }
-
- // Report all the USS data for each process
- for (Entry<String, NumericValues.Builder> values :
- mUssMemoryPerProcess.get(device).entrySet()) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(
- Measurements.newBuilder()
- .setNumericValues(values.getValue().build()))
- .build();
- metric.setUnit("kB").setType(DataType.RAW);
- runData.addMetricForDevice(device, "USS#" + values.getKey(), metric);
- }
-
- // Continue reporting the max PSS / USS for compatibility
- Map<String, Long> procPss = mMemoryData.get(device).mProcPss;
- Map<String, Long> procUss = mMemoryData.get(device).mProcUss;
- for (Entry<String, Long> pss : procPss.entrySet()) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(
- Measurements.newBuilder().setSingleInt(pss.getValue()).build());
- metric.setUnit("kB").setType(DataType.PROCESSED);
- runData.addMetricForDevice(device, "MAX_PSS#" + pss.getKey(), metric);
- }
- for (Entry<String, Long> uss : procUss.entrySet()) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(
- Measurements.newBuilder().setSingleInt(uss.getValue()).build());
- metric.setUnit("kB").setType(DataType.PROCESSED);
- runData.addMetricForDevice(device, "MAX_USS#" + uss.getKey(), metric);
- }
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java
deleted file mode 100644
index 347a4b903..000000000
--- a/test_framework/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollector.java
+++ /dev/null
@@ -1,224 +0,0 @@
-/*
- * Copyright (C) 2017 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.Option;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
-
-import java.io.File;
-import java.lang.reflect.InvocationTargetException;
-import java.math.BigInteger;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * A {@link IMetricCollector} that makes runs multiple metric collectors periodically. This is a
- * best effort scheduler. It makes the best effort to run the collectors at given intervals while
- * making sure that no two collectors are run at the same time.
- */
-public class ScheduleMultipleDeviceMetricCollector extends BaseDeviceMetricCollector {
- @Option(
- name = "metric-collection-intervals",
- description = "The interval at which the collectors should run."
- )
- private Map<String, Long> mIntervalMs = new HashMap<>();
-
- @Option(
- name = "metric-storage-path",
- description =
- "Absolute path to a directory on host where the collected metrics will be stored."
- )
- private File mMetricStoragePath = new File(System.getProperty("java.io.tmpdir"));
-
- @Option(
- name = "metric-collector-command-classes",
- description =
- "Complete package name of a class which registers the commands to do the actual "
- + "job of collection. Can be repeated."
- )
- private List<String> mMetricCollectorClasses = new ArrayList<>();
-
- // List of collectors to run.
- private List<ScheduledDeviceMetricCollector> mMetricCollectors = new ArrayList<>();
-
- // Time interval at which the commands should run.
- private Map<ScheduledDeviceMetricCollector, Long> mMetricCollectorIntervals = new HashMap<>();
-
- // Time when the commands to collect various metrics were last run.
- private Map<ScheduledDeviceMetricCollector, Long> mLastUpdate = new HashMap<>();
-
- private Timer mTimer;
-
- private long mScheduleRate;
-
- @Override
- public ITestInvocationListener init(
- IInvocationContext context, ITestInvocationListener listener) {
- super.init(context, listener);
- initMetricCollectors(context, listener);
-
- return this;
- }
-
- /** Gets an instance of all the requested metric collectors. */
- private void initMetricCollectors(
- IInvocationContext context, ITestInvocationListener listener) {
- for (String metricCollectorClass : mMetricCollectorClasses) {
- try {
- Class<?> klass = Class.forName(metricCollectorClass);
-
- ScheduledDeviceMetricCollector singleMetricCollector =
- klass.asSubclass(ScheduledDeviceMetricCollector.class)
- .getDeclaredConstructor()
- .newInstance();
-
- singleMetricCollector.init(context, listener);
-
- mMetricCollectors.add(singleMetricCollector);
- } catch (ClassNotFoundException
- | InstantiationException
- | IllegalAccessException
- | InvocationTargetException
- | NoSuchMethodException e) {
- CLog.e("Class %s not found, skipping.", metricCollectorClass);
- CLog.e(e);
- }
- }
- }
-
- @Override
- public final void onTestRunStart(DeviceMetricData runData) {
- if (mMetricCollectorClasses.isEmpty()) {
- CLog.w("No single metric class provided. Skipping collection.");
- return;
- }
-
- setupCollection();
-
- if (mScheduleRate == 0) {
- CLog.e(
- "Failed to get a valid interval for even one metric collector. "
- + "Please make sure that the collectors have non-zero intervals "
- + "specified as an argument to this class.");
- return;
- }
-
- // TODO(b/70394486): Investigate if ScheduledThreadPool is better suited here so that we can
- // schedule all the metrics in their own thread and create a common object which allows
- // running of only one collector at a time.
- mTimer = new Timer();
-
- TimerTask timerTask =
- new TimerTask() {
- @Override
- public void run() {
- collect(runData);
- }
- };
-
- mTimer.scheduleAtFixedRate(timerTask, 0, mScheduleRate);
- }
-
- /**
- * Sets up the collection process by parsing all the args, retrieving the intervals from the
- * args and initializing the last update value of each of the collectors to current time.
- */
- private void setupCollection() {
- parseAllArgs();
- for (ScheduledDeviceMetricCollector singleMetricCollector :
- mMetricCollectorIntervals.keySet()) {
- mLastUpdate.put(singleMetricCollector, System.currentTimeMillis());
- }
-
- mScheduleRate = gcdOfIntervals();
- }
-
- /**
- * Runs all the requested collectors sequentially. Dumps the output in {@code
- * mmResultsDirectory/outputDirFormat} of the collector prefixed.
- *
- * @param runData holds the filename of the metrics collected for each collector.
- */
- private void collect(DeviceMetricData runData) {
- for (ScheduledDeviceMetricCollector singleMetricCollector :
- mMetricCollectorIntervals.keySet()) {
-
- Long elapsedTime = System.currentTimeMillis() - mLastUpdate.get(singleMetricCollector);
-
- Long taskInterval = mMetricCollectorIntervals.get(singleMetricCollector);
-
- if (elapsedTime >= taskInterval) {
- try {
- for (ITestDevice device : getDevices()) {
- singleMetricCollector.collect(device, runData);
- }
- mLastUpdate.put(singleMetricCollector, System.currentTimeMillis());
- } catch (InterruptedException e) {
- CLog.e("Exception during %s", singleMetricCollector.getClass());
- CLog.e(e);
- }
- }
- }
- }
-
- /** Parse all the intervals provided in the command line. */
- private void parseAllArgs() {
- for (ScheduledDeviceMetricCollector metricCollector : mMetricCollectors) {
- Long value = mIntervalMs.getOrDefault(metricCollector.getTag(), 0L);
-
- if (value > 0) {
- mMetricCollectorIntervals.put(metricCollector, value);
- } else if (value < 0) {
- throw new IllegalArgumentException(
- metricCollector.getClass() + " expects a non negative interval.");
- }
- }
- }
-
- /** Get the {@code scheduleRate} common to all tasks which is the gcd of all the intervals. */
- private Long gcdOfIntervals() {
- Collection<Long> intervals = mMetricCollectorIntervals.values();
- if (intervals.isEmpty()) {
- return 0L;
- }
- BigInteger gcdSoFar = new BigInteger(intervals.iterator().next().toString());
-
- for (Long interval : intervals) {
- gcdSoFar = gcdSoFar.gcd(new BigInteger(interval.toString()));
- }
-
- return gcdSoFar.longValue();
- }
-
- @Override
- public final void onTestRunEnd(
- DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) {
- if (mTimer != null) {
- mTimer.cancel();
- mTimer.purge();
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java
deleted file mode 100644
index 409ac2b77..000000000
--- a/test_framework/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2017 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.annotations.VisibleForTesting;
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.util.FileUtil;
-
-import java.io.File;
-import java.io.IOException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-
-/**
- * A {@link IMetricCollector} that allows to run a collection task periodically at a set interval.
- */
-public abstract class ScheduledDeviceMetricCollector extends BaseDeviceMetricCollector {
-
- @Option(
- name = "fixed-schedule-rate",
- description = "Schedule the timetask as a fixed schedule rate"
- )
- private boolean mFixedScheduleRate = false;
-
- @Option(
- name = "interval",
- description = "the interval between two tasks being scheduled",
- isTimeVal = true
- )
- private long mIntervalMs = 60 * 1000l;
-
- private Timer timer;
-
- @Override
- public final void onTestRunStart(DeviceMetricData runData) {
- CLog.d("starting with interval = %s", mIntervalMs);
- onStart(runData);
- timer = new Timer();
- TimerTask timerTask =
- new TimerTask() {
- @Override
- public void run() {
- try {
- for (ITestDevice device : getDevices()) {
- collect(device, runData);
- }
- } catch (InterruptedException e) {
- timer.cancel();
- Thread.currentThread().interrupt();
- CLog.e("Interrupted exception thrown from task:");
- CLog.e(e);
- }
- }
- };
-
- if (mFixedScheduleRate) {
- timer.scheduleAtFixedRate(timerTask, 0, mIntervalMs);
- } else {
- timer.schedule(timerTask, 0, mIntervalMs);
- }
- }
-
- @Override
- public final void onTestRunEnd(
- DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) {
- if (timer != null) {
- timer.cancel();
- timer.purge();
- }
- onEnd(runData);
- CLog.d("finished");
- }
-
- /**
- * Task periodically & asynchronously run during the test running on a specific device.
- *
- * @param device the {@link ITestDevice} the metric is associated to.
- * @param runData the {@link DeviceMetricData} where to put metrics.
- * @throws InterruptedException
- */
- abstract void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException;
-
- /**
- * Executed when entering this collector.
- *
- * @param runData the {@link DeviceMetricData} where to put metrics.
- */
- void onStart(DeviceMetricData runData) {
- // Does nothing.
- }
-
- /**
- * Executed when finishing this collector.
- *
- * @param runData the {@link DeviceMetricData} where to put metrics.
- */
- void onEnd(DeviceMetricData runData) {
- // Does nothing.
- }
-
- /**
- * Send all the output of a process from all the devices to a file.
- *
- * <p>Please note, metric collections should not overlap.
- *
- * @throws DeviceNotAvailableException
- * @throws IOException
- */
- File saveProcessOutput(ITestDevice device, String command, String outputFileName)
- throws DeviceNotAvailableException, IOException {
- String output = device.executeShellCommand(command);
-
- // Create the output file and dump the output of the command to this file.
- File outputFile = new File(outputFileName);
-
- FileUtil.writeToFile(output, outputFile);
-
- return outputFile;
- }
-
- /**
- * Create a suffix string to be appended at the end of each metric file to keep the name unique
- * at each run.
- *
- * @return suffix string in the format year-month-date-hour-minute-seconds-milliseconds.
- */
- @VisibleForTesting
- String getFileSuffix() {
- return new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS", Locale.US).format(new Date());
- }
-
- /**
- * Creates temporary directory to store the metric files.
- *
- * @return {@link File} directory with 'tmp' prefixed to its name to signify that its temporary.
- * @throws IOException
- */
- @VisibleForTesting
- File createTempDir() throws IOException {
- return FileUtil.createTempDir(String.format("tmp_%s", getTag()));
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/TemperatureCollector.java b/test_framework/com/android/tradefed/device/metric/TemperatureCollector.java
deleted file mode 100644
index 2c38cd55d..000000000
--- a/test_framework/com/android/tradefed/device/metric/TemperatureCollector.java
+++ /dev/null
@@ -1,155 +0,0 @@
-/*
- * Copyright (C) 2018 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 com.android.tradefed.targetprep.TemperatureThrottlingWaiter.DEVICE_TEMPERATURE_FILE_PATH_NAME;
-
-import com.android.tradefed.config.Option;
-import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
-import com.android.tradefed.metrics.proto.MetricMeasurement.DoubleValues;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-
-import java.util.HashMap;
-import java.util.Map;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
-/**
- * A {@link ScheduledDeviceMetricCollector} to measure min and max device temperature. Useful for
- * long duration performance tests to monitor if the device overheats.
- */
-public class TemperatureCollector extends ScheduledDeviceMetricCollector {
-
- private static final String CELCIUS_UNIT = "celcius";
-
- // Option name intentionally shared with TemperatureThrottlingWaiter
- @Option(
- name = DEVICE_TEMPERATURE_FILE_PATH_NAME,
- description =
- "Name of file that contains device temperature. "
- + "Example: /sys/class/hwmon/hwmon1/device/msm_therm"
- )
- private String mDeviceTemperatureFilePath = null;
-
- @Option(
- name = "device-temperature-file-regex",
- description =
- "Regex to parse temperature file. First group must be the temperature parsable"
- + "to Double. Default: Result:(\\d+) Raw:.*"
- )
- private String mDeviceTemperatureFileRegex = "Result:(\\d+) Raw:.*";
-
- /**
- * Stores the highest recorded temperature per device. Device will not be present in the map if
- * no valid temperature was recorded.
- */
- private Map<ITestDevice, Double> mMaxDeviceTemps;
-
- /**
- * Stores the lowest recorded temperature per device. Device will not be present in the map if
- * no valid temperature was recorded.
- */
- private Map<ITestDevice, Double> mMinDeviceTemps;
-
- // Example: Result:32 Raw:7e51
- private static Pattern mTemperatureRegex;
-
- private Map<ITestDevice, DoubleValues.Builder> mValues;
-
- @Override
- void onStart(DeviceMetricData runData) {
- mTemperatureRegex = Pattern.compile(mDeviceTemperatureFileRegex);
- mMaxDeviceTemps = new HashMap<>();
- mMinDeviceTemps = new HashMap<>();
- mValues = new HashMap<>();
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- if (mDeviceTemperatureFilePath == null) {
- return;
- }
- try {
- if (!device.isAdbRoot()) {
- return;
- }
- Double temp = getTemperature(device);
- if (temp == null) {
- return;
- }
- if (mValues.get(device) == null) {
- mValues.put(device, DoubleValues.newBuilder());
- }
- mValues.get(device).addDoubleValue(temp);
- mMaxDeviceTemps.putIfAbsent(device, temp);
- mMinDeviceTemps.putIfAbsent(device, temp);
- if (mMaxDeviceTemps.get(device) < temp) {
- mMaxDeviceTemps.put(device, temp);
- }
- if (mMinDeviceTemps.get(device) > temp) {
- mMinDeviceTemps.put(device, temp);
- }
- } catch (DeviceNotAvailableException e) {
- CLog.e(e);
- }
- }
-
- private Double getTemperature(ITestDevice device) throws DeviceNotAvailableException {
- String cmd = "cat " + mDeviceTemperatureFilePath;
- String result = device.executeShellCommand(cmd).trim();
- Matcher m = mTemperatureRegex.matcher(result);
- if (m.matches()) {
- return Double.parseDouble(m.group(1));
- }
- CLog.e("Error parsing temperature file output: " + result);
- return null;
- }
-
- @Override
- void onEnd(DeviceMetricData runData) {
- for (ITestDevice device : getDevices()) {
- DoubleValues.Builder values = mValues.get(device);
- if (values != null) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(
- Measurements.newBuilder().setDoubleValues(values.build()).build());
- metric.setUnit(CELCIUS_UNIT).setType(DataType.RAW);
- runData.addMetricForDevice(device, "temperature", metric);
- }
- // Report the max and min for compatibility
- Double maxTemp = mMaxDeviceTemps.get(device);
- if (maxTemp != null) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(Measurements.newBuilder().setSingleDouble(maxTemp).build());
- // Since we report some processed value report it as PROCESSED.
- metric.setUnit(CELCIUS_UNIT).setType(DataType.PROCESSED);
- runData.addMetricForDevice(device, "max_temperature", metric);
- }
- Double minTemp = mMinDeviceTemps.get(device);
- if (minTemp != null) {
- Metric.Builder metric = Metric.newBuilder();
- metric.setMeasurements(Measurements.newBuilder().setSingleDouble(minTemp).build());
- // Since we report some processed value report it as PROCESSED.
- metric.setUnit(CELCIUS_UNIT).setType(DataType.PROCESSED);
- runData.addMetricForDevice(device, "min_temperature", metric);
- }
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java b/test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java
deleted file mode 100644
index f1cc72f1b..000000000
--- a/test_framework/com/android/tradefed/device/metric/TraceMetricCollector.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2018 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.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.log.LogUtil.CLog;
-import com.android.tradefed.result.FileInputStreamSource;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.google.common.io.Files;
-import java.io.File;
-import java.io.IOException;
-
-/** A {@link ScheduledDeviceMetricCollector} to collect kernel debug trace at regular intervals. */
-public class TraceMetricCollector extends ScheduledDeviceMetricCollector {
- TraceMetricCollector() {
- setTag("trace");
- }
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- try {
- CLog.i("Running trace collector...");
- String outputFileName = String.format("%s/trace-%s", createTempDir(), getFileSuffix());
- File outputFile =
- saveProcessOutput(
- device, "cat /sys/kernel/debug/tracing/trace", outputFileName);
- try (InputStreamSource source = new FileInputStreamSource(outputFile, true)) {
- getInvocationListener()
- .testLog(
- Files.getNameWithoutExtension(outputFile.getName()),
- LogDataType.TEXT,
- source);
- }
- } catch (DeviceNotAvailableException | IOException e) {
- CLog.e(e);
- }
- }
-}
diff --git a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
index 618b6bc8d..cd8033292 100644
--- a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
+++ b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
@@ -344,7 +344,14 @@ public class PerfettoGenericPostProcessor extends BasePostProcessor {
for (Entry<FieldDescriptor, Object> entry : fields.entrySet()) {
if (!(entry.getValue() instanceof Message) && !(entry.getValue() instanceof List)) {
if (isNumeric(entry.getValue().toString())) {
- // Construct the metric if it is numeric value.
+ // Check if the current field has to be used as prefix for other fields
+ // and add it to the list of prefixes.
+ if (mPerfettoPrefixKeyFields.contains(entry.getKey().toString())) {
+ keyPrefixOtherFields.add(String.format("%s-%s",
+ entry.getKey().getName().toString(), entry.getValue().toString()));
+ continue;
+ }
+ // Otherwise treat this numeric field as metric.
if (mNumberPattern.matcher(entry.getValue().toString()).matches()) {
convertedMetrics.put(
entry.getKey().getName(),
@@ -355,9 +362,9 @@ public class PerfettoGenericPostProcessor extends BasePostProcessor {
convertedMetrics.put(
entry.getKey().getName(),
TfMetricProtoUtil.stringToMetric(
- Long.toString(
- Double.valueOf(entry.getValue().toString())
- .longValue()))
+ Long.toString(
+ Double.valueOf(entry.getValue().toString())
+ .longValue()))
.toBuilder());
}
} else {
@@ -375,20 +382,6 @@ public class PerfettoGenericPostProcessor extends BasePostProcessor {
}
}
- // Add prefix key to all the keys in current proto message which has numeric values.
- Map<String, Metric.Builder> additionalConvertedMetrics =
- new HashMap<String, Metric.Builder>();
- for (String prefix : keyPrefixOtherFields) {
- for (Map.Entry<String, Metric.Builder> currentMetric : convertedMetrics.entrySet()) {
- additionalConvertedMetrics.put(String.format("%s-%s", prefix,
- currentMetric.getKey()), currentMetric.getValue());
- }
- }
-
- // Not cleaning up the other metrics without prefix fields.
- convertedMetrics.putAll(additionalConvertedMetrics);
-
-
// Recursively expand the proto messages and repeated fields(i.e list).
// Recursion when there are no messages or list with in the current message.
for (Entry<FieldDescriptor, Object> entry : fields.entrySet()) {
@@ -458,6 +451,20 @@ public class PerfettoGenericPostProcessor extends BasePostProcessor {
}
}
}
+
+ // Add prefix key to all the keys in current proto message which has numeric values.
+ Map<String, Metric.Builder> additionalConvertedMetrics =
+ new HashMap<String, Metric.Builder>();
+ for (String prefix : keyPrefixOtherFields) {
+ for (Map.Entry<String, Metric.Builder> currentMetric : convertedMetrics.entrySet()) {
+ additionalConvertedMetrics.put(String.format("%s-%s", prefix,
+ currentMetric.getKey()), currentMetric.getValue());
+ }
+ }
+
+ // Not cleaning up the other metrics without prefix fields.
+ convertedMetrics.putAll(additionalConvertedMetrics);
+
return convertedMetrics;
}
@@ -511,3 +518,4 @@ public class PerfettoGenericPostProcessor extends BasePostProcessor {
return mProcessedMetric ? DataType.PROCESSED : DataType.RAW;
}
}
+
diff --git a/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java b/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java
new file mode 100644
index 000000000..0d32890a1
--- /dev/null
+++ b/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2020 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.build.BuildInfoKey.BuildInfoFileKey;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.command.remote.DeviceDescriptor;
+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 com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipFile;
+
+/** Create chroot directory for ART tests. */
+@OptionClass(alias = "art-chroot-preparer")
+public class ArtChrootPreparer extends BaseTargetPreparer {
+
+ // Predefined location of the chroot root directory.
+ public static final String CHROOT_PATH = "/data/local/tmp/art-test-chroot";
+
+ // Directories to create in the chroot.
+ private static final String[] MKDIRS = {
+ "/", "/apex", "/data", "/data/dalvik-cache", "/data/local/tmp", "/tmp",
+ };
+
+ // System mount points to replicate in the chroot.
+ private static final String[] MOUNTS = {
+ "/dev", "/linkerconfig", "/proc", "/sys", "/system", "/apex/com.android.os.statsd",
+ };
+
+ @Override
+ public void setUp(TestInformation testInfo)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ ITestDevice device = testInfo.getDevice();
+
+ // Ensure there are no files left from previous runs.
+ cleanup(device);
+
+ // Create directories required for ART testing in chroot.
+ for (String dir : MKDIRS) {
+ adbShell(device, "mkdir -p " + CHROOT_PATH + dir);
+ }
+
+ // Replicate system mount point in the chroot.
+ for (String dir : MOUNTS) {
+ adbShell(device, "mkdir -p " + CHROOT_PATH + dir);
+ adbShell(device, "mount --bind " + dir + " " + CHROOT_PATH + dir);
+ }
+
+ // Activate APEXes in the chroot.
+ IBuildInfo buildInfo = testInfo.getBuildInfo();
+ IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) buildInfo;
+ DeviceDescriptor deviceDesc = device.getDeviceDescriptor();
+ File tests_dir = deviceBuild.getFile(BuildInfoFileKey.TARGET_LINKED_DIR);
+ // The art_chroot is a shared module containing comment ART test data.
+ File apexes_dir = FileUtil.getFileForPath(tests_dir, "art_chroot", "system", "apex");
+ if (apexes_dir.listFiles() == null) {
+ throw new TargetSetupError(
+ "No apex files found in " + apexes_dir.getPath(), deviceDesc);
+ }
+ File tempDir = null;
+ try {
+ tempDir = FileUtil.createTempDir("art-test-apex");
+ for (File apex : apexes_dir.listFiles()) {
+ activateApex(device, tempDir, apex);
+ }
+ } catch (IOException e) {
+ throw new TargetSetupError("Error when activating apex", e, deviceDesc);
+ } finally {
+ FileUtil.recursiveDelete(tempDir);
+ }
+ }
+
+ private void activateApex(ITestDevice device, File tempDir, File apex)
+ throws TargetSetupError, IOException, DeviceNotAvailableException {
+ CLog.i("Activate apex in ART chroot: " + apex.getName());
+ ZipFile apex_zip = new ZipFile(apex);
+ ZipArchiveEntry apex_payload = apex_zip.getEntry("apex_payload.img");
+ File temp = FileUtil.createTempFile("payload-", ".img", tempDir);
+ FileUtil.writeToFile(apex_zip.getInputStream(apex_payload), temp);
+ String deviceApexDir = CHROOT_PATH + "/apex/" + apex.getName();
+ // Rename "com.android.art.testing.apex" to just "com.android.art.apex".
+ deviceApexDir = deviceApexDir.replace(".testing.apex", "").replace(".apex", "");
+ String deviceApexImg = deviceApexDir + ".img";
+ if (!device.pushFile(temp, deviceApexImg)) {
+ throw new TargetSetupError(
+ "adb push failed for " + apex.getName(), device.getDeviceDescriptor());
+ }
+ // TODO(b/168048638): Work-around for cuttlefish: first losetup call always fails.
+ device.executeShellV2Command("losetup -f");
+ // Mount the apex file via a loopback device.
+ String loopbackDevice = adbShell(device, "losetup -f -s " + deviceApexImg);
+ adbShell(device, "mkdir -p " + deviceApexDir);
+ adbShell(device, "mount -o loop,ro " + loopbackDevice + " " + deviceApexDir);
+ }
+
+ @Override
+ public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ try {
+ cleanup(testInfo.getDevice());
+ } catch (TargetSetupError ex) {
+ CLog.e("Tear-down failed: " + ex.toString());
+ }
+ }
+
+ // Wrapper for executeShellV2Command that checks that the command succeeds.
+ private String adbShell(ITestDevice device, String cmd)
+ throws TargetSetupError, DeviceNotAvailableException {
+ CommandResult result = device.executeShellV2Command(cmd);
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ throw new TargetSetupError(
+ String.format(
+ "adb shell command failed: '%s': %s".format(cmd, result.getStderr())));
+ }
+ return result.getStdout();
+ }
+
+ private void cleanup(ITestDevice device) throws TargetSetupError, DeviceNotAvailableException {
+ String mounts = adbShell(device, "mount");
+ Pattern pattern = Pattern.compile("^([^ ]+) on ([^ ]+) type ([^ ]+) .*$");
+ for (String mount : mounts.split("\n")) {
+ Matcher matcher = pattern.matcher(mount);
+ if (!matcher.matches()) {
+ throw new TargetSetupError("Failed to parse mount command output: " + mount);
+ }
+ if (matcher.group(2).startsWith(CHROOT_PATH)) {
+ adbShell(device, "umount " + matcher.group(2));
+ }
+ }
+ adbShell(device, "rm -rf " + CHROOT_PATH);
+ }
+}
diff --git a/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java b/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java
index 1c837da3d..d54cb4934 100644
--- a/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java
@@ -26,11 +26,14 @@ import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.SparseImageUtil;
import com.android.tradefed.util.ZipUtil;
import com.android.tradefed.util.ZipUtil2;
+
+import org.apache.commons.compress.archivers.zip.ZipFile;
+
import java.io.File;
import java.io.IOException;
-import org.apache.commons.compress.archivers.zip.ZipFile;
/**
* An {@link ITargetPreparer} that sets up a system image on top of a device build with the Dynamic
@@ -43,11 +46,15 @@ public class DynamicSystemPreparer extends BaseTargetPreparer {
private static final String DEST_PATH = "/sdcard/system.raw.gz";
@Option(
- name = "system-image-zip-name",
- description = "The name of the zip file containing system.img."
- )
+ name = "system-image-zip-name",
+ description = "The name of the zip file containing system.img.")
private String mSystemImageZipName = "system-img.zip";
+ @Option(
+ name = "user-data-size-in-gb",
+ description = "Number of GB to be allocated for DSU user-data.")
+ private long mUserDataSizeInGb = 16L; // 16GB
+
private boolean isDSURunning(ITestDevice device) throws DeviceNotAvailableException {
CollectingOutputReceiver receiver = new CollectingOutputReceiver();
device.executeShellCommand("gsi_tool status", receiver);
@@ -67,15 +74,21 @@ public class DynamicSystemPreparer extends BaseTargetPreparer {
ZipFile zipFile = null;
File systemImage = null;
+ File rawSystemImage = null;
File systemImageGZ = null;
try {
zipFile = new ZipFile(systemImageZipFile);
systemImage = ZipUtil2.extractFileFromZip(zipFile, "system.img");
- // The prequest here is the system.img must be an unsparsed image.
- // Is there any way to detect the actual format and convert it accordingly.
+ if (SparseImageUtil.isSparse(systemImage)) {
+ rawSystemImage = FileUtil.createTempFile("system", ".raw");
+ SparseImageUtil.unsparse(systemImage, rawSystemImage);
+ } else {
+ // system.img is already non-sparse
+ rawSystemImage = systemImage;
+ }
systemImageGZ = FileUtil.createTempFile("system", ".raw.gz");
- long rawSize = systemImage.length();
- ZipUtil.gzipFile(systemImage, systemImageGZ);
+ long rawSize = rawSystemImage.length();
+ ZipUtil.gzipFile(rawSystemImage, systemImageGZ);
CLog.i("Pushing %s to %s", systemImageGZ.getAbsolutePath(), DEST_PATH);
if (!device.pushFile(systemImageGZ, DEST_PATH)) {
throw new TargetSetupError(
@@ -95,17 +108,26 @@ public class DynamicSystemPreparer extends BaseTargetPreparer {
+ "--el KEY_SYSTEM_SIZE "
+ rawSize
+ " "
- + "--el KEY_USERDATA_SIZE 8589934592 "
- + "--ez KEY_ENABLE_WHEN_COMPLETED true";
+ + "--el KEY_USERDATA_SIZE "
+ + mUserDataSizeInGb * 1024 * 1024 * 1024
+ + " --ez KEY_ENABLE_WHEN_COMPLETED true";
device.executeShellCommand(command);
// Check if device shows as unavailable (as expected after the activity finished).
- device.waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000);
- device.waitForDeviceOnline();
- // the waitForDeviceOnline may block and we need to correct the 'i'
- // which is used to measure timeout accordingly
- if (!isDSURunning(device)) {
+ if (!device.waitForDeviceNotAvailable(DSU_MAX_WAIT_SEC * 1000)) {
throw new TargetSetupError(
- "Timeout to boot into DSU", device.getDeviceDescriptor());
+ "Timed out waiting for DSU installation to complete and reboot",
+ device.getDeviceDescriptor());
+ }
+ try {
+ // waitForDeviceOnline() throws DeviceNotAvailableException if device does not
+ // become online within timeout.
+ device.waitForDeviceOnline();
+ } catch (DeviceNotAvailableException e) {
+ throw new TargetSetupError(
+ "Timed out booting into DSU", e, device.getDeviceDescriptor());
+ }
+ if (!isDSURunning(device)) {
+ throw new TargetSetupError("Failed to boot into DSU", device.getDeviceDescriptor());
}
CommandResult result = device.executeShellV2Command("gsi_tool enable");
if (CommandStatus.SUCCESS.equals(result.getStatus())) {
@@ -120,6 +142,7 @@ public class DynamicSystemPreparer extends BaseTargetPreparer {
"fail to install the DynamicSystemUpdate", e, device.getDeviceDescriptor());
} finally {
FileUtil.deleteFile(systemImage);
+ FileUtil.deleteFile(rawSystemImage);
FileUtil.deleteFile(systemImageGZ);
ZipUtil2.closeZip(zipFile);
}
diff --git a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
index f3de9b444..0e61a42be 100644
--- a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
@@ -29,6 +29,9 @@ import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
+import com.android.tradefed.result.error.ErrorIdentifier;
+import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
import com.android.tradefed.testtype.IInvocationContextReceiver;
@@ -137,9 +140,10 @@ public class PushFilePreparer extends BaseTargetPreparer
* Helper method to only throw if mAbortOnFailure is enabled. Callers should behave as if this
* method may return.
*/
- private void fail(String message, DeviceDescriptor descriptor) throws TargetSetupError {
+ private void fail(String message, DeviceDescriptor descriptor, ErrorIdentifier identifier)
+ throws TargetSetupError {
if (shouldAbortOnFailure()) {
- throw new TargetSetupError(message, descriptor);
+ throw new TargetSetupError(message, descriptor, identifier);
} else {
// Log the error and return
Log.w(LOG_TAG, message);
@@ -153,7 +157,10 @@ public class PushFilePreparer extends BaseTargetPreparer
for (String pushspec : mPushSpecs) {
String[] pair = pushspec.split("->");
if (pair.length != 2) {
- fail(String.format("Invalid pushspec: '%s'", Arrays.asList(pair)), descriptor);
+ fail(
+ String.format("Invalid pushspec: '%s'", Arrays.asList(pair)),
+ descriptor,
+ InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR);
continue;
}
remoteToLocalMapping.put(pair[1], new File(pair[0]));
@@ -311,6 +318,22 @@ public class PushFilePreparer extends BaseTargetPreparer
// approach to do individual download from remote artifact.
// Try to stage the files from remote zip files.
src = buildInfo.stageRemoteFile(fileName, testDir);
+ if (src != null) {
+ try {
+ // Search again with filtering on ABI
+ File srcWithAbi = FileUtil.findFile(fileName, mAbi, testDir);
+ if (srcWithAbi != null
+ && !srcWithAbi
+ .getAbsolutePath()
+ .startsWith(src.getAbsolutePath())) {
+ // When multiple matches are found, return the one with matching
+ // ABI unless src is its parent directory.
+ return srcWithAbi;
+ }
+ } catch (IOException e) {
+ CLog.w("Failed to find test files with matching ABI from directory.");
+ }
+ }
}
}
return src;
@@ -388,7 +411,8 @@ public class PushFilePreparer extends BaseTargetPreparer
if (src == null || !src.exists()) {
fail(
String.format("Local source file '%s' does not exist", localPath),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ InfraErrorIdentifier.CONFIGURED_ARTIFACT_NOT_FOUND);
return;
}
if (src.isDirectory()) {
@@ -402,7 +426,8 @@ public class PushFilePreparer extends BaseTargetPreparer
String.format(
"Attempting to push dir '%s' to an existing device file '%s'",
src.getAbsolutePath(), remotePath),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.FAIL_PUSH_FILE);
}
Set<String> filter = new HashSet<>();
if (mAbi != null) {
@@ -415,7 +440,8 @@ public class PushFilePreparer extends BaseTargetPreparer
fail(
String.format(
"Failed to push local '%s' to remote '%s'", localPath, remotePath),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.FAIL_PUSH_FILE);
return;
} else {
if (deleteContentOnly) {
@@ -428,7 +454,8 @@ public class PushFilePreparer extends BaseTargetPreparer
fail(
String.format(
"Failed to push local '%s' to remote '%s'", localPath, remotePath),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.FAIL_PUSH_FILE);
return;
} else {
mFilesPushed.add(remotePath);
diff --git a/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java b/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
index 7034330ef..90928da3f 100644
--- a/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
@@ -26,6 +26,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.PythonVirtualenvHelper;
import com.android.tradefed.util.RunUtil;
import java.io.File;
@@ -40,8 +41,7 @@ import java.util.List;
@OptionClass(alias = "python-venv")
public class PythonVirtualenvPreparer extends BaseTargetPreparer {
- private static final String PIP = "pip";
- private static final String PATH = "PATH";
+ private static final String PIP = "pip3";
protected static final String PYTHONPATH = "PYTHONPATH";
private static final int BASE_TIMEOUT = 1000 * 60;
@@ -70,6 +70,7 @@ public class PythonVirtualenvPreparer extends BaseTargetPreparer {
protected void installDeps(IBuildInfo buildInfo, ITestDevice device) throws TargetSetupError {
boolean hasDependencies = false;
+ mPip = getPipPath();
if (mRequirementsFile != null) {
CommandResult c = mRunUtil.runTimedCmd(BASE_TIMEOUT * 5, mPip,
"install", "-r", mRequirementsFile.getAbsolutePath());
@@ -90,6 +91,9 @@ public class PythonVirtualenvPreparer extends BaseTargetPreparer {
CLog.e("Installing %s failed", dep);
throw new TargetSetupError("Failed to install dependencies with pip",
device.getDeviceDescriptor());
+ } else {
+ CLog.d("Successfullly installed %s.", dep);
+ CLog.d("Stdout: %s", c.getStdout());
}
hasDependencies = true;
}
@@ -99,9 +103,12 @@ public class PythonVirtualenvPreparer extends BaseTargetPreparer {
} else {
// make the install directory of new packages available to other classes that
// receive the build
- buildInfo.setFile(PYTHONPATH, new File(mVenvDir,
- "local/lib/python2.7/site-packages"),
+ // TODO(b/166688272): Get install location from pip rather than hard code it.
+ buildInfo.setFile(
+ PYTHONPATH,
+ new File(mVenvDir, "local/lib/python3.8/site-packages"),
buildInfo.getBuildId());
+ buildInfo.setFile("VIRTUAL_ENV", mVenvDir, buildInfo.getBuildId());
}
}
@@ -109,13 +116,26 @@ public class PythonVirtualenvPreparer extends BaseTargetPreparer {
throws TargetSetupError {
if (mVenvDir != null) {
CLog.i("Using existing virtualenv based at %s", mVenvDir.getAbsolutePath());
- activate();
+ PythonVirtualenvHelper.activate(mRunUtil, mVenvDir);
return;
}
+ checkVirtualenvVersion(device);
try {
mVenvDir = FileUtil.createNamedTempDir(buildInfo.getTestTag() + "-virtualenv");
- mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", mVenvDir.getAbsolutePath());
- activate();
+ CommandResult c =
+ mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", mVenvDir.getAbsolutePath());
+ if (c.getStatus() != CommandStatus.SUCCESS) {
+ CLog.e("Creating virtual environment at %s failed.", mVenvDir.getAbsoluteFile());
+ CLog.e(
+ "Status: %s\nStdout: %s\nStderr: %s",
+ c.getStatus(), c.getStdout(), c.getStderr());
+ throw new TargetSetupError(
+ String.format(
+ "Failed to create virtual environment. Error:\n%s", c.getStderr()),
+ device.getDeviceDescriptor());
+ }
+ CLog.i("Created a virtualenv based at %s", mVenvDir.getAbsolutePath());
+ PythonVirtualenvHelper.activate(mRunUtil, mVenvDir);
} catch (IOException e) {
CLog.e("Failed to create temp directory for virtualenv");
throw new TargetSetupError("Error creating virtualenv", e,
@@ -131,13 +151,35 @@ public class PythonVirtualenvPreparer extends BaseTargetPreparer {
mRequirementsFile = f;
}
- private void activate() {
- File binDir = new File(mVenvDir, "bin");
- mRunUtil.setWorkingDir(binDir);
- String path = System.getenv(PATH);
- mRunUtil.setEnvVariable(PATH, binDir + ":" + path);
- File pipFile = new File(binDir, PIP);
+ private String getPipPath() {
+ if (mVenvDir == null || !mVenvDir.exists()) {
+ return null;
+ }
+ String virtualenvPath = mVenvDir.getAbsolutePath();
+ File pipFile = new File(PythonVirtualenvHelper.getPythonBinDir(virtualenvPath), PIP);
pipFile.setExecutable(true);
- mPip = pipFile.getAbsolutePath();
+ return pipFile.getAbsolutePath();
+ }
+
+ /** Check if the virtualenv on the host is too old. */
+ private void checkVirtualenvVersion(ITestDevice device) throws TargetSetupError {
+ CommandResult result = mRunUtil.runTimedCmd(BASE_TIMEOUT, "virtualenv", "--version");
+ if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
+ throw new TargetSetupError(
+ "Failed to run `virtualenv --version`. Reason:\n" + result.getStderr(),
+ device.getDeviceDescriptor());
+ }
+ String stdout = result.getStdout(); // should start with 'virtualenv <version> from'
+ if (stdout.contains("command not found")) {
+ throw new TargetSetupError(
+ "virtualenv is not installed.", device.getDeviceDescriptor());
+ }
+ String version = stdout.split(" ")[1];
+ int majorVersion = Integer.parseInt(version.split("\\.")[0]);
+ if (majorVersion < 20) {
+ throw new TargetSetupError(
+ "virtualenv is too old. Required: >=20.0.1, yours: " + version,
+ device.getDeviceDescriptor());
+ }
}
} \ No newline at end of file
diff --git a/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java
index e3c485ce2..fd4c69474 100644
--- a/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/RunHostCommandTargetPreparer.java
@@ -16,9 +16,11 @@
package com.android.tradefed.targetprep;
+import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.IDeviceManager;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.ITestLogger;
@@ -86,6 +88,11 @@ public class RunHostCommandTargetPreparer extends BaseTargetPreparer
@Option(name = "host-cmd-timeout", description = "Timeout for each command specified.")
private Duration mTimeout = Duration.ofMinutes(1L);
+ @Option(
+ name = "use-flashing-permit",
+ description = "Acquire a flashing permit before running commands.")
+ private boolean mUseFlashingPermit = false;
+
private List<Process> mBgProcesses = new ArrayList<>();
private List<BgCommandLog> mBgCommandLogs = new ArrayList<>();
private ITestLogger mLogger;
@@ -147,7 +154,16 @@ public class RunHostCommandTargetPreparer extends BaseTargetPreparer
}
ITestDevice device = testInfo.getDevice();
replaceSerialNumber(mSetUpCommands, device);
- runCommandList(mSetUpCommands, device);
+ try {
+ if (mUseFlashingPermit) {
+ getDeviceManager().takeFlashingPermit();
+ }
+ runCommandList(mSetUpCommands, device);
+ } finally {
+ if (mUseFlashingPermit) {
+ getDeviceManager().returnFlashingPermit();
+ }
+ }
try {
mBgCommandLogs = createBgCommandLogs();
@@ -164,9 +180,16 @@ public class RunHostCommandTargetPreparer extends BaseTargetPreparer
ITestDevice device = testInfo.getDevice();
replaceSerialNumber(mTearDownCommands, device);
try {
+ if (mUseFlashingPermit) {
+ getDeviceManager().takeFlashingPermit();
+ }
runCommandList(mTearDownCommands, device);
} catch (TargetSetupError tse) {
CLog.e(tse);
+ } finally {
+ if (mUseFlashingPermit) {
+ getDeviceManager().returnFlashingPermit();
+ }
}
// Terminate background commands after test finished
@@ -273,6 +296,12 @@ public class RunHostCommandTargetPreparer extends BaseTargetPreparer
return mRunUtil;
}
+ /** @return {@link IDeviceManager} instance used for flashing permits */
+ @VisibleForTesting
+ IDeviceManager getDeviceManager() {
+ return GlobalConfiguration.getDeviceManagerInstance();
+ }
+
/**
* Create a BgCommandLog object that is based on a temporary file for each background command
*
diff --git a/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java
index def925213..6dd2b561b 100644
--- a/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/RunHostScriptTargetPreparer.java
@@ -56,6 +56,11 @@ public class RunHostScriptTargetPreparer extends BaseTargetPreparer {
@Option(name = "script-timeout", description = "Script execution timeout.")
private Duration mTimeout = Duration.ofMinutes(1L);
+ @Option(
+ name = "use-flashing-permit",
+ description = "Acquire a flashing permit before executing the script.")
+ private boolean mUseFlashingPermit = false;
+
private IRunUtil mRunUtil;
@Override
@@ -82,28 +87,15 @@ public class RunHostScriptTargetPreparer extends BaseTargetPreparer {
getRunUtil().setEnvVariable("ANDROID_SERIAL", device.getSerialNumber());
setPathVariable(testInfo);
- // Execute script and handle result
- CommandResult result =
- getRunUtil().runTimedCmd(mTimeout.toMillis(), scriptFile.getAbsolutePath());
- switch (result.getStatus()) {
- case SUCCESS:
- CLog.i("Script executed successfully, stdout = [%s].", result.getStdout());
- break;
- case FAILED:
- throw new TargetSetupError(
- String.format(
- "Script execution failed, stdout = [%s], stderr = [%s].",
- result.getStdout(), result.getStderr()),
- device.getDeviceDescriptor());
- case TIMED_OUT:
- throw new TargetSetupError(
- "Script execution timed out.", device.getDeviceDescriptor());
- case EXCEPTION:
- throw new TargetSetupError(
- String.format(
- "Exception during script execution, stdout = [%s], stderr = [%s].",
- result.getStdout(), result.getStderr()),
- device.getDeviceDescriptor());
+ try {
+ if (mUseFlashingPermit) {
+ getDeviceManager().takeFlashingPermit();
+ }
+ executeScript(scriptFile, device);
+ } finally {
+ if (mUseFlashingPermit) {
+ getDeviceManager().returnFlashingPermit();
+ }
}
}
@@ -116,7 +108,7 @@ public class RunHostScriptTargetPreparer extends BaseTargetPreparer {
return mRunUtil;
}
- /** @return {@link IDeviceManager} instance used to fetch the configured adb/fastboot paths */
+ /** @return {@link IDeviceManager} instance used for adb/fastboot paths and flashing permits */
@VisibleForTesting
IDeviceManager getDeviceManager() {
return GlobalConfiguration.getDeviceManagerInstance();
@@ -175,4 +167,35 @@ public class RunHostScriptTargetPreparer extends BaseTargetPreparer {
getRunUtil().setEnvVariable("PATH", path);
}
}
+
+ /**
+ * Execute script and handle result.
+ *
+ * @param scriptFile script file to execute
+ * @param device device being prepared
+ */
+ private void executeScript(File scriptFile, ITestDevice device) throws TargetSetupError {
+ CommandResult result =
+ getRunUtil().runTimedCmd(mTimeout.toMillis(), scriptFile.getAbsolutePath());
+ switch (result.getStatus()) {
+ case SUCCESS:
+ CLog.i("Script executed successfully, stdout = [%s].", result.getStdout());
+ break;
+ case FAILED:
+ throw new TargetSetupError(
+ String.format(
+ "Script execution failed, stdout = [%s], stderr = [%s].",
+ result.getStdout(), result.getStderr()),
+ device.getDeviceDescriptor());
+ case TIMED_OUT:
+ throw new TargetSetupError(
+ "Script execution timed out.", device.getDeviceDescriptor());
+ case EXCEPTION:
+ throw new TargetSetupError(
+ String.format(
+ "Exception during script execution, stdout = [%s], stderr = [%s].",
+ result.getStdout(), result.getStderr()),
+ device.getDeviceDescriptor());
+ }
+ }
}
diff --git a/test_framework/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java
new file mode 100644
index 000000000..7f2b28d86
--- /dev/null
+++ b/test_framework/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2020 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.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.UserInfo;
+import com.android.tradefed.invoker.TestInformation;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An {@link ITargetPreparer} that creates a secondary user in setup, and marks that tests should be
+ * run in that user.
+ *
+ * <p>In teardown, the secondary user is removed.
+ */
+@OptionClass(alias = "run-on-secondary-user")
+public class RunOnSecondaryUserTargetPreparer extends BaseTargetPreparer {
+
+ @VisibleForTesting static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER";
+
+ @VisibleForTesting static final String TEST_PACKAGE_NAME_OPTION = "test-package-name";
+
+ @Option(
+ name = TEST_PACKAGE_NAME_OPTION,
+ description =
+ "the name of a package to be installed on the secondary user. "
+ + "This must already be installed on the device.",
+ importance = Option.Importance.IF_UNSET)
+ private List<String> mTestPackages = new ArrayList<>();
+
+ @Override
+ public void setUp(TestInformation testInfo)
+ throws TargetSetupError, DeviceNotAvailableException {
+ int secondaryUserId = getSecondaryUserId(testInfo.getDevice());
+
+ if (secondaryUserId != -1) {
+ // There is already a secondary user - so we don't want to remove it
+ setDisableTearDown(true);
+ } else {
+ secondaryUserId = createSecondaryUser(testInfo.getDevice());
+ }
+
+ for (String pkg : mTestPackages) {
+ testInfo.getDevice()
+ .executeShellCommand(
+ "pm install-existing --user " + secondaryUserId + " " + pkg);
+ }
+
+ testInfo.properties().put(RUN_TESTS_AS_USER_KEY, Integer.toString(secondaryUserId));
+ }
+
+ /** Get the id of a secondary user currently on the device. -1 if there is none */
+ private static int getSecondaryUserId(ITestDevice device) throws DeviceNotAvailableException {
+ for (Map.Entry<Integer, UserInfo> userInfo : device.getUserInfos().entrySet()) {
+ if (userInfo.getValue().isSecondary()) {
+ return userInfo.getKey();
+ }
+ }
+ return -1;
+ }
+
+ /** Creates a secondary user and returns the new user ID. */
+ private static int createSecondaryUser(ITestDevice device) throws DeviceNotAvailableException {
+ final String createUserOutput = device.executeShellCommand("pm create-user secondary");
+ final int userId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
+ device.executeShellCommand("am start-user -w " + userId);
+ return userId;
+ }
+
+ @Override
+ public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ int userId = Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY));
+
+ testInfo.getDevice().removeUser(userId);
+ }
+}
diff --git a/test_framework/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java
new file mode 100644
index 000000000..274a09604
--- /dev/null
+++ b/test_framework/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 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.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.UserInfo;
+import com.android.tradefed.invoker.TestInformation;
+
+import com.google.common.annotations.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * An {@link ITargetPreparer} that creates a work profile in setup, and marks that tests should be
+ * run in that user.
+ *
+ * <p>In teardown, the work profile is removed.
+ */
+@OptionClass(alias = "run-on-work-profile")
+public class RunOnWorkProfileTargetPreparer extends BaseTargetPreparer {
+
+ @VisibleForTesting static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER";
+
+ @VisibleForTesting static final String TEST_PACKAGE_NAME_OPTION = "test-package-name";
+
+ @Option(
+ name = TEST_PACKAGE_NAME_OPTION,
+ description =
+ "the name of a package to be installed on the work profile. "
+ + "This must already be installed on the device.",
+ importance = Option.Importance.IF_UNSET)
+ private List<String> mTestPackages = new ArrayList<>();
+
+ @Override
+ public void setUp(TestInformation testInfo)
+ throws TargetSetupError, DeviceNotAvailableException {
+ int workProfileId = getWorkProfileId(testInfo.getDevice());
+
+ if (workProfileId != -1) {
+ // There is already a work profile - so we don't want to remove it
+ setDisableTearDown(true);
+ } else {
+ workProfileId = createWorkProfile(testInfo.getDevice());
+ }
+
+ for (String pkg : mTestPackages) {
+ testInfo.getDevice()
+ .executeShellCommand("pm install-existing --user " + workProfileId + " " + pkg);
+ }
+
+ testInfo.properties().put(RUN_TESTS_AS_USER_KEY, Integer.toString(workProfileId));
+ }
+
+ /** Get the id of a work profile currently on the device. -1 if there is none */
+ private static int getWorkProfileId(ITestDevice device) throws DeviceNotAvailableException {
+ for (Map.Entry<Integer, UserInfo> userInfo : device.getUserInfos().entrySet()) {
+ if (userInfo.getValue().isManagedProfile()) {
+ return userInfo.getKey();
+ }
+ }
+ return -1;
+ }
+
+ /** Creates a work profile and returns the new user ID. */
+ private static int createWorkProfile(ITestDevice device) throws DeviceNotAvailableException {
+ int parentProfile = device.getCurrentUser();
+ final String createUserOutput =
+ device.executeShellCommand(
+ "pm create-user --profileOf " + parentProfile + " --managed work");
+ final int profileId = Integer.parseInt(createUserOutput.split(" id ")[1].trim());
+ device.executeShellCommand("am start-user -w " + profileId);
+ return profileId;
+ }
+
+ @Override
+ public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ int workProfileId = Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY));
+
+ testInfo.getDevice().removeUser(workProfileId);
+ }
+}
diff --git a/test_framework/com/android/tradefed/targetprep/WifiPreparer.java b/test_framework/com/android/tradefed/targetprep/WifiPreparer.java
index 1bfc63567..b1f14e09d 100644
--- a/test_framework/com/android/tradefed/targetprep/WifiPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/WifiPreparer.java
@@ -64,10 +64,14 @@ public class WifiPreparer extends BaseTargetPreparer {
if (mVerifyOnly) {
if (!device.isWifiEnabled()) {
throw new TargetSetupError(
- "The device does not have wifi enabled.", device.getDeviceDescriptor());
+ "The device does not have wifi enabled.",
+ device.getDeviceDescriptor(),
+ InfraErrorIdentifier.NO_WIFI);
} else if (!device.checkConnectivity()) {
throw new TargetSetupError(
- "The device has no wifi connection.", device.getDeviceDescriptor());
+ "The device has no wifi connection.",
+ device.getDeviceDescriptor(),
+ InfraErrorIdentifier.NO_WIFI);
}
return;
}
diff --git a/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java b/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
index d436e13ea..cec9d30e2 100644
--- a/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/multi/MixImageZipPreparer.java
@@ -95,10 +95,6 @@ public class MixImageZipPreparer extends BaseMultiTargetPreparer {
)
private Set<String> mSystemFileNames = new TreeSet<>();
- @Deprecated
- @Option(name = "dummy-file-name", description = "use stub-file-name instead.")
- private Set<String> mDummyFileNames = new TreeSet<>();
-
@Option(
name = "stub-file-name",
description =
@@ -221,7 +217,6 @@ public class MixImageZipPreparer extends BaseMultiTargetPreparer {
systemFiles = replaceExistingEntries(systemFiles, files);
filesNotInDeviceBuild.putAll(systemFiles);
- mStubFileNames.addAll(mDummyFileNames);
// Generate specified stub files and replace those in device build.
Map<String, InputStreamFactory> stubFiles =
createStubInputStreamFactories(mStubFileNames);
diff --git a/test_framework/com/android/tradefed/testtype/ArtGTest.java b/test_framework/com/android/tradefed/testtype/ArtGTest.java
new file mode 100644
index 000000000..cf6a33396
--- /dev/null
+++ b/test_framework/com/android/tradefed/testtype/ArtGTest.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2020 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;
+
+import com.android.tradefed.targetprep.ArtChrootPreparer;
+
+public class ArtGTest extends GTest {
+ @Override
+ protected String getGTestCmdLineWrapper(String fullPath, String flags) {
+ String chroot = ArtChrootPreparer.CHROOT_PATH;
+ if (fullPath.startsWith(chroot)) {
+ fullPath = fullPath.substring(chroot.length());
+ }
+ return String.format("chroot %s %s %s", chroot, fullPath, flags);
+ }
+}
diff --git a/test_framework/com/android/tradefed/testtype/ArtRunTest.java b/test_framework/com/android/tradefed/testtype/ArtRunTest.java
index a98210452..dbeaae5bf 100644
--- a/test_framework/com/android/tradefed/testtype/ArtRunTest.java
+++ b/test_framework/com/android/tradefed/testtype/ArtRunTest.java
@@ -17,35 +17,51 @@
package com.android.tradefed.testtype;
import com.android.ddmlib.CollectingOutputReceiver;
-
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
+import com.android.tradefed.invoker.TestInformation;
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.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.util.AbiUtils;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.FileUtil;
+import difflib.DiffUtils;
+import difflib.Patch;
+
+import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
+import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
import java.util.HashMap;
+import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Set;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
/** A test runner to run ART run-tests. */
-public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver {
+public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver, ITestFilterReceiver {
private static final String RUNTEST_TAG = "ArtRunTest";
private static final String DALVIKVM_CMD =
"dalvikvm|#BITNESS#| -classpath |#CLASSPATH#| |#MAINCLASS#|";
+ public static final String CHECKER_EXECUTABLE = "art/tools/checker/checker.py";
@Option(
name = "test-timeout",
@@ -63,6 +79,8 @@ public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver {
private ITestDevice mDevice = null;
private IAbi mAbi = null;
+ private Set<String> mIncludeFilters = new LinkedHashSet<>();
+ private Set<String> mExcludeFilters = new LinkedHashSet<>();
/** {@inheritDoc} */
@Override
@@ -89,6 +107,54 @@ public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver {
/** {@inheritDoc} */
@Override
+ public void addIncludeFilter(String filter) {
+ mIncludeFilters.add(filter);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void addAllIncludeFilters(Set<String> filters) {
+ mIncludeFilters.addAll(filters);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void addExcludeFilter(String filter) {
+ mExcludeFilters.add(filter);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void addAllExcludeFilters(Set<String> filters) {
+ mExcludeFilters.addAll(filters);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Set<String> getIncludeFilters() {
+ return mIncludeFilters;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public Set<String> getExcludeFilters() {
+ return mExcludeFilters;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearIncludeFilters() {
+ mIncludeFilters.clear();
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void clearExcludeFilters() {
+ mExcludeFilters.clear();
+ }
+
+ /** {@inheritDoc} */
+ @Override
public void run(TestInformation testInfo, ITestInvocationListener listener)
throws DeviceNotAvailableException {
if (mDevice == null) {
@@ -111,24 +177,29 @@ public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver {
* Run a single ART run-test (on device).
*
* @param listener {@link ITestInvocationListener} listener for test
- * @throws DeviceNotAvailableException
+ * @throws DeviceNotAvailableException If there was a problem communicating with
+ * the test device.
*/
void runArtTest(TestInformation testInfo, ITestInvocationListener listener)
throws DeviceNotAvailableException {
+ String abi = mAbi.getName();
+ String runName = String.format("%s_%s", RUNTEST_TAG, abi);
+ TestDescription testId = new TestDescription(runName, mRunTestName);
+ if (shouldSkipCurrentTest(testId)) {
+ return;
+ }
+
CLog.i("Running ArtRunTest %s on %s", mRunTestName, mDevice.getSerialNumber());
String cmd = DALVIKVM_CMD;
- String abi = mAbi.getName();
cmd = cmd.replace("|#BITNESS#|", AbiUtils.getBitness(abi));
cmd = cmd.replace("|#CLASSPATH#|", ArrayUtil.join(File.pathSeparator, mClasspath));
// TODO: Turn this into an an option of the `ArtRunTest` class?
cmd = cmd.replace("|#MAINCLASS#|", "Main");
CLog.d("About to run run-test command: %s", cmd);
- String runName = String.format("%s_%s", RUNTEST_TAG, abi);
// Note: We only run one test at the moment.
int testCount = 1;
- TestDescription testId = new TestDescription(runName, mRunTestName);
listener.testRunStarted(runName, testCount);
listener.testStarted(testId);
@@ -143,57 +214,171 @@ public class ArtRunTest implements IDeviceTest, IRemoteTest, IAbiReceiver {
// Check the output producted by the test.
if (output != null) {
try {
- File expectedFile = getDependencyFileFromRunTestDir(testInfo, "expected.txt");
+ String expectedFileName = String.format("%s-expected.txt", mRunTestName);
+ File expectedFile =
+ testInfo.getDependencyFile(expectedFileName, /* targetFirst */ true);
CLog.i("Found expected output for run-test %s: %s", mRunTestName, expectedFile);
String expected = FileUtil.readStringFromFile(expectedFile);
+
+ // TODO: The "check" step should be configurable, as is the case in current ART
+ // `run-test` scripts).
if (!output.equals(expected)) {
- String error = String.format("'%s' instead of '%s'", output, expected);
- // TODO: Implement better reporting, e.g. using a diff output.
- // Also, the "check" step should be configurable, as this is the case in
- // current ART run-test scripts).
- CLog.i("%s FAILED: %s", mRunTestName, error);
- listener.testFailed(testId, error);
+ // Compute the difference between the expected and actual outputs.
+ List<String> expectedLines = Arrays.asList(expected.split("\\r?\\n"));
+ List<String> outputLines = Arrays.asList(output.split("\\r?\\n"));
+ Patch<String> diff = DiffUtils.diff(expectedLines, outputLines);
+ List<String> unifiedDiff =
+ DiffUtils.generateUnifiedDiff(
+ "expected.txt", "stdout", expectedLines, diff, 3);
+ // Produce a unified diff output for the error message.
+ StringBuilder errorMessage =
+ new StringBuilder(
+ "The test's standard output does not match the expected "
+ + "output:\n");
+ for (String delta : unifiedDiff) {
+ errorMessage.append(delta).append('\n');
+ }
+ CLog.i("%s FAILED: %s", mRunTestName, errorMessage.toString());
+ listener.testFailed(testId, errorMessage.toString());
+ return;
}
} catch (IOException ioe) {
CLog.e(
"I/O error while accessing expected output file for test %s: %s",
mRunTestName, ioe);
listener.testFailed(testId, "I/O error while accessing expected output file.");
+ return;
}
} else {
listener.testFailed(testId, "No output received to compare to.");
+ return;
+ }
+
+ if (mRunTestName.contains("-checker-")) {
+ // not particularly reliable way of constructing a temporary dir
+ String cfgPathDir =
+ String.format("/data/local/tmp/%s", mRunTestName.replaceAll("/", "-"));
+ mDevice.executeShellCommand(String.format("mkdir -p \"%s\"", cfgPathDir));
+
+ String cfgPath = cfgPathDir + "/graph.cfg";
+ mDevice.executeShellCommand(
+ String.format(
+ "dex2oat --dex-file=%s --oat-file=/dev/null --dump-cfg=%s -j1",
+ mClasspath.get(0), cfgPath));
+
+ File runTestDir;
+ try {
+ runTestDir = getRunTestDir(testInfo);
+ } catch (FileNotFoundException e) {
+ listener.testFailed(testId, "I/O error while accessing test dir.");
+ return;
+ }
+
+ File localCfgPath = new File(runTestDir, "graph.cfg");
+ if (localCfgPath.isFile()) {
+ localCfgPath.delete();
+ }
+
+ mDevice.pullFile(cfgPath, localCfgPath);
+
+ File tempJar = new File(runTestDir, "temp.jar");
+ mDevice.pullFile(mClasspath.get(0), tempJar);
+
+ try (ZipFile archive = new ZipFile(tempJar)) {
+ File srcFile = new File(runTestDir, "src");
+ if (srcFile.exists()) {
+ Files.walk(srcFile.toPath())
+ .map(Path::toFile)
+ .sorted(Comparator.reverseOrder())
+ .forEach(File::delete);
+ }
+
+ List<? extends ZipEntry> entries = archive.stream()
+ .sorted(Comparator.comparing(ZipEntry::getName))
+ .collect(Collectors.toList());
+
+ for (ZipEntry entry : entries) {
+ if (entry.getName().startsWith("src")) {
+ Path entryDest = runTestDir.toPath().resolve(entry.getName());
+ if (entry.isDirectory()) {
+ Files.createDirectory(entryDest);
+ } else {
+ Files.copy(archive.getInputStream(entry), entryDest);
+ }
+ }
+ }
+ } catch (IOException e) {
+ listener.testFailed(testId, "Error unpacking test jar");
+ CLog.e("Jar unpacking failed with exception %s", e);
+ CLog.e(e);
+ return;
+ }
+
+ String checkerArch = AbiUtils.getArchForAbi(abi).toUpperCase();
+
+ ProcessBuilder processBuilder =
+ new ProcessBuilder(
+ CHECKER_EXECUTABLE,
+ "-q",
+ "--arch=" + checkerArch,
+ localCfgPath.getAbsolutePath(),
+ runTestDir.getAbsolutePath());
+
+ try {
+ Process process = processBuilder.start();
+ if (process.waitFor() != 0) {
+ String checkerOutput = new BufferedReader(
+ new InputStreamReader(process.getErrorStream())).lines().collect(
+ Collectors.joining("\n"));
+ listener.testFailed(testId, "Checker failed\n" + checkerOutput);
+ listener.testLog("graph.cfg", LogDataType.CFG,
+ new FileInputStreamSource(localCfgPath));
+ }
+ } catch (IOException | InterruptedException e) {
+ listener.testFailed(testId, "I/O error while starting Checker process");
+ }
}
} finally {
- HashMap<String, Metric> emptyTestMetrics = new HashMap();
+ HashMap<String, Metric> emptyTestMetrics = new HashMap<>();
listener.testEnded(testId, emptyTestMetrics);
- HashMap<String, Metric> emptyTestRunMetrics = new HashMap();
+ HashMap<String, Metric> emptyTestRunMetrics = new HashMap<>();
// TODO: Pass an actual value as `elapsedTimeMillis` argument.
listener.testRunEnded(/* elapsedTimeMillis*/ 0, emptyTestRunMetrics);
}
}
+ /**
+ * Check if current test should be skipped.
+ *
+ * @param description The test in progress.
+ * @return true if the test should be skipped.
+ */
+ private boolean shouldSkipCurrentTest(TestDescription description) {
+ // Force to skip any test not listed in include filters, or listed in exclude filters.
+ // exclude filters have highest priority.
+ String testName = description.getTestName();
+ String descString = description.toString();
+ if (mExcludeFilters.contains(testName) || mExcludeFilters.contains(descString)) {
+ return true;
+ }
+ if (!mIncludeFilters.isEmpty()) {
+ return !mIncludeFilters.contains(testName) && !mIncludeFilters.contains(descString);
+ }
+ return false;
+ }
+
/** Create an output receiver for the test command executed on the device. */
protected CollectingOutputReceiver createTestOutputReceiver() {
return new CollectingOutputReceiver();
}
- /** Search for a dependency/artifact file in the run-test's directory. */
- protected File getDependencyFileFromRunTestDir(TestInformation testInfo, String fileName)
- throws FileNotFoundException {
+ private File getRunTestDir(TestInformation testInfo) throws FileNotFoundException {
File testsDir = testInfo.executionFiles().get(FilesKey.TARGET_TESTS_DIRECTORY);
if (testsDir == null || !testsDir.exists()) {
throw new FileNotFoundException(
String.format(
"Could not find target tests directory for test %s.", mRunTestName));
}
- File runTestDir = new File(testsDir, mRunTestName);
- File file = FileUtil.findFile(runTestDir, fileName);
- if (file == null) {
- throw new FileNotFoundException(
- String.format(
- "Could not find an artifact file associated with %s in directory %s.",
- fileName, runTestDir));
- }
- return file;
+ return new File(testsDir, mRunTestName);
}
}
diff --git a/test_framework/com/android/tradefed/testtype/GTest.java b/test_framework/com/android/tradefed/testtype/GTest.java
index 70beba964..cd971f443 100644
--- a/test_framework/com/android/tradefed/testtype/GTest.java
+++ b/test_framework/com/android/tradefed/testtype/GTest.java
@@ -158,6 +158,10 @@ public class GTest extends GTestBase implements IDeviceTest {
return testPath.toString();
}
+ public void setNativeTestDevicePath(String path) {
+ mNativeTestDevicePath = path;
+ }
+
/**
* Executes all native tests in a folder as well as in all subfolders recursively.
*
diff --git a/test_framework/com/android/tradefed/testtype/GTestBase.java b/test_framework/com/android/tradefed/testtype/GTestBase.java
index 316d2f957..a7eaea79b 100644
--- a/test_framework/com/android/tradefed/testtype/GTestBase.java
+++ b/test_framework/com/android/tradefed/testtype/GTestBase.java
@@ -547,6 +547,14 @@ public abstract class GTestBase
}
/**
+ * Helper which allows derived classes to wrap the gtest command under some other tool (chroot,
+ * strace, gdb, and similar).
+ */
+ protected String getGTestCmdLineWrapper(String fullPath, String flags) {
+ return String.format("%s %s", fullPath, flags);
+ }
+
+ /**
* Helper method to build the gtest command to run.
*
* @param fullPath absolute file system path to gtest binary on device
@@ -568,7 +576,7 @@ public abstract class GTestBase
gTestCmdLine.append(String.format("su %s ", mRunTestAs));
}
- gTestCmdLine.append(String.format("%s %s", fullPath, flags));
+ gTestCmdLine.append(getGTestCmdLineWrapper(fullPath, flags));
return gTestCmdLine.toString();
}
diff --git a/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
index 373844a51..837ba81f7 100644
--- a/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
+++ b/test_framework/com/android/tradefed/testtype/GoogleBenchmarkTest.java
@@ -26,6 +26,7 @@ import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
+import com.android.tradefed.util.StringEscapeUtils;
import com.google.common.annotations.VisibleForTesting;
@@ -406,7 +407,7 @@ public class GoogleBenchmarkTest implements IDeviceTest, IRemoteTest, ITestFilte
if (iterator.hasNext()) {
filterFlag.append(String.format(" %s=%s", GBENCHMARK_FILTER_OPTION, iterator.next()));
while (iterator.hasNext()) {
- filterFlag.append(String.format("\\|%s", iterator.next()));
+ filterFlag.append(String.format("|%s", iterator.next()));
}
}
return filterFlag.toString();
@@ -421,7 +422,7 @@ public class GoogleBenchmarkTest implements IDeviceTest, IRemoteTest, ITestFilte
// Format benchmark as "^benchmark$" to avoid unintended regex partial matching.
filterFlag.append(String.format(" %s=^%s$", GBENCHMARK_FILTER_OPTION, iterator.next()));
while (iterator.hasNext()) {
- filterFlag.append(String.format("\\|^%s$", iterator.next()));
+ filterFlag.append(String.format("|^%s$", iterator.next()));
}
}
return filterFlag.toString();
@@ -441,14 +442,14 @@ public class GoogleBenchmarkTest implements IDeviceTest, IRemoteTest, ITestFilte
final String cmd,
final IShellOutputReceiver outputReceiver)
throws DeviceNotAvailableException {
+ String shellCmd = StringEscapeUtils.escapeShell(cmd);
// Ensure that command is not too long for adb
- if (cmd.length() < ADB_CMD_CHAR_LIMIT) {
+ if (shellCmd.length() < ADB_CMD_CHAR_LIMIT) {
if (outputReceiver == null) {
- return testDevice.executeShellCommand(cmd);
+ return testDevice.executeShellCommand(shellCmd);
}
-
testDevice.executeShellCommand(
- cmd,
+ shellCmd,
outputReceiver,
mMaxRunTime /* maxTimeToShellOutputResponse */,
TimeUnit.MILLISECONDS,
@@ -457,7 +458,7 @@ public class GoogleBenchmarkTest implements IDeviceTest, IRemoteTest, ITestFilte
}
// Wrap adb shell command in script if command is too long for direct execution
- return executeCommandByScript(testDevice, cmd, outputReceiver);
+ return executeCommandByScript(testDevice, shellCmd, outputReceiver);
}
/** Runs a command from a temporary script. */
diff --git a/test_framework/com/android/tradefed/testtype/HostGTest.java b/test_framework/com/android/tradefed/testtype/HostGTest.java
index 43d599e1e..3da63e465 100644
--- a/test_framework/com/android/tradefed/testtype/HostGTest.java
+++ b/test_framework/com/android/tradefed/testtype/HostGTest.java
@@ -41,21 +41,10 @@ import java.util.List;
/** A Test that runs a native test package. */
@OptionClass(alias = "hostgtest")
-public class HostGTest extends GTestBase implements IAbiReceiver, IBuildReceiver {
+public class HostGTest extends GTestBase implements IBuildReceiver {
private static final long DEFAULT_HOST_COMMAND_TIMEOUT_MS = 2 * 60 * 1000;
private IBuildInfo mBuildInfo = null;
- private IAbi mAbi = null;
-
- @Override
- public void setAbi(IAbi abi) {
- this.mAbi = abi;
- }
-
- @Override
- public IAbi getAbi() {
- return this.mAbi;
- }
@Override
public void setBuild(IBuildInfo buildInfo) {
@@ -216,7 +205,7 @@ public class HostGTest extends GTestBase implements IAbiReceiver, IBuildReceiver
String moduleName = getTestModule();
File gTestFile = null;
try {
- gTestFile = FileUtil.findFile(moduleName, mAbi, scanDirs.toArray(new File[] {}));
+ gTestFile = FileUtil.findFile(moduleName, getAbi(), scanDirs.toArray(new File[] {}));
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -226,7 +215,8 @@ public class HostGTest extends GTestBase implements IAbiReceiver, IBuildReceiver
// search for it with a potential suffix (which is allowed).
try {
File byBaseName =
- FileUtil.findFile(moduleName + ".*", mAbi, scanDirs.toArray(new File[] {}));
+ FileUtil.findFile(
+ moduleName + ".*", getAbi(), scanDirs.toArray(new File[] {}));
if (byBaseName != null && byBaseName.isFile()) {
gTestFile = byBaseName;
}
diff --git a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
index bba5fa39b..b1f7f1ad2 100644
--- a/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
+++ b/test_framework/com/android/tradefed/testtype/InstrumentationTest.java
@@ -47,6 +47,7 @@ import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.BugreportCollector;
import com.android.tradefed.result.CollectingTestListener;
import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.ITestLifeCycleReceiver;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
@@ -100,6 +101,8 @@ public class InstrumentationTest
/** default timeout for tests collection */
static final long TEST_COLLECTION_TIMEOUT_MS = 2 * 60 * 1000;
+ static final String RUN_TESTS_AS_USER_KEY = "RUN_TESTS_AS_USER";
+
/** test run name for merging coverage measurements */
static final String MERGE_COVERAGE_MEASUREMENTS_TEST_NAME = "mergeCoverageMeasurements";
@@ -886,7 +889,8 @@ public class InstrumentationTest
"Tests to run should not be set explicitly when --collect-tests-only is set.");
// Use the actual listener to collect the tests, and print a error if this fails
- Collection<TestDescription> collectedTests = collectTestsToRun(mRunner, listener);
+ Collection<TestDescription> collectedTests =
+ collectTestsToRun(testInfo, mRunner, listener);
if (collectedTests == null) {
CLog.e("Failed to collect tests for %s", mPackageName);
} else {
@@ -899,7 +903,7 @@ public class InstrumentationTest
Collection<TestDescription> testsToRun = mTestsToRun;
if (testsToRun == null) {
// Don't notify the listener since it's not a real run.
- testsToRun = collectTestsToRun(mRunner, null);
+ testsToRun = collectTestsToRun(testInfo, mRunner, null);
}
// Only set the debug flag after collecting tests.
@@ -962,7 +966,7 @@ public class InstrumentationTest
if (testsToRun == null) {
// Failed to collect the tests or collection is off. Just try to run them all.
- mDevice.runInstrumentationTests(mRunner, listener);
+ runInstrumentationTests(testInfo, mRunner, listener);
} else if (!testsToRun.isEmpty()) {
runWithRerun(testInfo, listener, testsToRun);
} else {
@@ -977,6 +981,20 @@ public class InstrumentationTest
}
}
+ private boolean runInstrumentationTests(
+ TestInformation testInfo,
+ IRemoteAndroidTestRunner runner,
+ ITestLifeCycleReceiver... receivers)
+ throws DeviceNotAvailableException {
+ if (testInfo != null && testInfo.properties().containsKey(RUN_TESTS_AS_USER_KEY)) {
+ return mDevice.runInstrumentationTestsAsUser(
+ runner,
+ Integer.parseInt(testInfo.properties().get(RUN_TESTS_AS_USER_KEY)),
+ receivers);
+ }
+ return mDevice.runInstrumentationTests(runner, receivers);
+ }
+
/**
* Returns a listener that will collect bugreports, or the original {@code listener} if this
* feature is disabled.
@@ -1071,7 +1089,7 @@ public class InstrumentationTest
getDevice().getProcessByName("system_server"));
}
instrumentationListener.setReportUnexecutedTests(mReportUnexecuted);
- mDevice.runInstrumentationTests(mRunner, instrumentationListener);
+ runInstrumentationTests(testInfo, mRunner, instrumentationListener);
TestRunResult testRun = testTracker.getCurrentRunResults();
if (testRun.isRunFailure() || !testRun.getCompletedTests().containsAll(expectedTests)) {
// Don't re-run any completed tests, unless this is a coverage run.
@@ -1172,7 +1190,9 @@ public class InstrumentationTest
* @throws DeviceNotAvailableException
*/
private Collection<TestDescription> collectTestsToRun(
- final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener)
+ final TestInformation testInfo,
+ final IRemoteAndroidTestRunner runner,
+ final ITestInvocationListener listener)
throws DeviceNotAvailableException {
if (isRerunMode()) {
Log.d(LOG_TAG, String.format("Collecting test info for %s on device %s",
@@ -1182,7 +1202,7 @@ public class InstrumentationTest
runner.setDebug(false);
// try to collect tests multiple times, in case device is temporarily not available
// on first attempt
- Collection<TestDescription> tests = collectTestsAndRetry(runner, listener);
+ Collection<TestDescription> tests = collectTestsAndRetry(testInfo, runner, listener);
// done with "logOnly" mode, restore proper test timeout before real test execution
addTimeoutsToRunner(runner);
runner.setTestCollection(false);
@@ -1202,7 +1222,9 @@ public class InstrumentationTest
*/
@VisibleForTesting
Collection<TestDescription> collectTestsAndRetry(
- final IRemoteAndroidTestRunner runner, final ITestInvocationListener listener)
+ final TestInformation testInfo,
+ final IRemoteAndroidTestRunner runner,
+ final ITestInvocationListener listener)
throws DeviceNotAvailableException {
boolean communicationFailure = false;
for (int i=0; i < COLLECT_TESTS_ATTEMPTS; i++) {
@@ -1211,9 +1233,9 @@ public class InstrumentationTest
// We allow to override the ddmlib default timeout for collection of tests.
runner.setMaxTimeToOutputResponse(mCollectTestTimeout, TimeUnit.MILLISECONDS);
if (listener == null) {
- instrResult = mDevice.runInstrumentationTests(runner, collector);
+ instrResult = runInstrumentationTests(testInfo, runner, collector);
} else {
- instrResult = mDevice.runInstrumentationTests(runner, collector, listener);
+ instrResult = runInstrumentationTests(testInfo, runner, collector, listener);
}
TestRunResult runResults = collector.getCurrentRunResults();
if (!instrResult || !runResults.isRunComplete()) {
diff --git a/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java b/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
index 67cd44cff..950f8dfe0 100644
--- a/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
+++ b/test_framework/com/android/tradefed/testtype/mobly/MoblyBinaryHostTest.java
@@ -24,10 +24,13 @@ import com.android.tradefed.config.OptionClass;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.FailureDescription;
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.InputStreamSource;
import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.targetprep.adb.AdbStopServerPreparer;
import com.android.tradefed.testtype.IBuildReceiver;
import com.android.tradefed.testtype.IDeviceTest;
@@ -37,6 +40,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.PythonVirtualenvHelper;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.StreamUtil;
@@ -48,6 +52,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
@@ -136,7 +141,11 @@ public class MoblyBinaryHostTest
@Override
public final void run(ITestInvocationListener listener) {
- List<File> parFilesList = findParFiles();
+ List<File> parFilesList = findParFiles(listener);
+ File venvDir = mBuildInfo.getFile("VIRTUAL_ENV");
+ if (venvDir != null) {
+ PythonVirtualenvHelper.activate(getRunUtil(), venvDir);
+ }
for (File parFile : parFilesList) {
// TODO(b/159365341): add a failure reporting for nonexistent binary.
if (!parFile.exists()) {
@@ -153,9 +162,13 @@ public class MoblyBinaryHostTest
reportLogs(getLogDir(), listener);
}
}
+ if (venvDir != null
+ && venvDir.getAbsolutePath().startsWith(System.getProperty("java.io.tmpdir"))) {
+ FileUtil.recursiveDelete(venvDir);
+ }
}
- private List<File> findParFiles() {
+ private List<File> findParFiles(ITestInvocationListener listener) {
File testsDir = null;
if (mBuildInfo instanceof IDeviceBuildInfo) {
testsDir = ((IDeviceBuildInfo) mBuildInfo).getTestsDir();
@@ -169,6 +182,8 @@ public class MoblyBinaryHostTest
res = FileUtil.findFile(testsDir, binaryName);
}
if (res == null) {
+ reportFailure(
+ listener, binaryName, "Couldn't find Mobly test binary " + binaryName);
throw new RuntimeException(
String.format("Couldn't find a par file %s", binaryName));
}
@@ -251,31 +266,43 @@ public class MoblyBinaryHostTest
InputStream inputStream = null;
try {
inputStream = new FileInputStream(yamlSummaryFile);
- processYamlTestResults(inputStream, parser);
+ processYamlTestResults(inputStream, parser, listener, runName);
} catch (FileNotFoundException ex) {
- // TODO(b/159367088): report a test failure.
- CLog.e("Fail processing test results: ", ex);
+ reportFailure(
+ listener,
+ runName,
+ "Fail processing test results, result file not found.\n" + ex);
} finally {
StreamUtil.close(inputStream);
}
}
+ /**
+ * Parses Mobly test results and does result reporting.
+ *
+ * @param inputStream An InputStream object reading in Mobly test result file.
+ * @param parser An MoblyYamlResultParser object that processes Mobly test results.
+ * @param listener An ITestInvocationListener instance that does various reporting.
+ * @param runName str, the name of the Mobly test binary run.
+ */
@VisibleForTesting
- protected void processYamlTestResults(InputStream inputStream, MoblyYamlResultParser parser) {
+ protected void processYamlTestResults(
+ InputStream inputStream,
+ MoblyYamlResultParser parser,
+ ITestInvocationListener listener,
+ String runName) {
try {
parser.parse(inputStream);
} catch (MoblyYamlResultHandlerFactory.InvalidResultTypeException
| IllegalAccessException
| InstantiationException ex) {
- // TODO(b/159367088): report a test failure.
- CLog.e("Failed to parse result file: %s", ex);
+ reportFailure(listener, runName, "Failed to parse the result file.\n" + ex);
}
}
private void updateConfigFile() {
InputStream inputStream = null;
FileWriter fileWriter = null;
- // TODO(b/159369745): clean up the tmp files created.
File localConfigFile = new File(getLogDir(), "local_config.yaml");
try {
inputStream = new FileInputStream(mConfigFile);
@@ -334,6 +361,15 @@ public class MoblyBinaryHostTest
return mLogDir;
}
+ private void reportFailure(
+ ITestInvocationListener listener, String runName, String errorMessage) {
+ listener.testRunStarted(runName, 0);
+ FailureDescription description =
+ FailureDescription.create(errorMessage, FailureStatus.TEST_FAILURE);
+ listener.testRunFailed(description);
+ listener.testRunEnded(0L, new HashMap<String, Metric>());
+ }
+
@VisibleForTesting
String getLogDirAbsolutePath() {
return getLogDir().getAbsolutePath();
@@ -348,6 +384,8 @@ public class MoblyBinaryHostTest
protected String[] buildCommandLineArray(String filePath) {
List<String> commandLine = new ArrayList<>();
commandLine.add(filePath);
+ // TODO(b/166468397): some test binaries are actually a wrapper of Mobly runner and need --
+ // to separate Python options.
commandLine.add("--");
if (getConfigPath() != null) {
commandLine.add("--config=" + getConfigPath());
diff --git a/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultHandlerFactory.java b/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultHandlerFactory.java
index 4d4a7aeda..e0938518b 100644
--- a/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultHandlerFactory.java
+++ b/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultHandlerFactory.java
@@ -45,6 +45,8 @@ public class MoblyYamlResultHandlerFactory {
return resultHandler;
}
+ // MoblyYamlResultHandlerFactory won't be serialized so suppress serial warning.
+ @SuppressWarnings("serial")
public class InvalidResultTypeException extends Exception {
public InvalidResultTypeException(String errorMsg) {
super(errorMsg);
@@ -59,9 +61,9 @@ public class MoblyYamlResultHandlerFactory {
SUMMARY("Summary", MoblyYamlResultSummaryHandler.class);
private String tag;
- private Class handlerClass;
+ private Class<?> handlerClass;
- Type(String tag, Class handlerClass) {
+ Type(String tag, Class<?> handlerClass) {
this.tag = tag;
this.handlerClass = handlerClass;
}
diff --git a/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultParser.java b/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultParser.java
index d3a692cfd..e3dda0055 100644
--- a/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultParser.java
+++ b/test_framework/com/android/tradefed/testtype/mobly/MoblyYamlResultParser.java
@@ -40,9 +40,9 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
public class MoblyYamlResultParser {
private static final String TYPE = "Type";
private ImmutableList.Builder<ITestInvocationListener> mListenersBuilder =
- new ImmutableList.Builder();
+ new ImmutableList.Builder<>();
private final String mRunName;
- private ImmutableList.Builder<ITestResult> mResultCacheBuilder = new ImmutableList.Builder();
+ private ImmutableList.Builder<ITestResult> mResultCacheBuilder = new ImmutableList.Builder<>();
private int mTestCount;
private long mRunStartTime;
private long mRunEndTime;
diff --git a/test_framework/com/android/tradefed/util/PythonVirtualenvHelper.java b/test_framework/com/android/tradefed/util/PythonVirtualenvHelper.java
new file mode 100644
index 000000000..35c419020
--- /dev/null
+++ b/test_framework/com/android/tradefed/util/PythonVirtualenvHelper.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2020 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 com.android.tradefed.log.LogUtil.CLog;
+
+import java.io.File;
+import java.util.stream.Stream;
+
+/** A helper class for activating Python 3 virtual environment. */
+public class PythonVirtualenvHelper {
+
+ private static final String PATH = "PATH";
+ private static final String PYTHONHOME = "PYTHONHOME";
+ private static final String PYTHONPATH = "PYTHONPATH";
+ public static final String VIRTUAL_ENV = "VIRTUAL_ENV";
+
+ /**
+ * Gets python bin directory path.
+ *
+ * <p>This method will check the directory existence.
+ *
+ * @return str, the path to the python bin directory in venv.
+ * @throws NullPointerException if arg virtualenvPath is null.
+ * @throws RuntimeException if /path/to/venv/bin does not exist.
+ */
+ public static String getPythonBinDir(String virtualenvPath) {
+ if (virtualenvPath == null) {
+ throw new NullPointerException(
+ "Path to the Python virtual environment should not be null");
+ }
+ File res = new File(virtualenvPath, "bin");
+ if (!res.exists()) {
+ throw new RuntimeException("Invalid python virtualenv path " + res.getAbsolutePath());
+ }
+ return res.getAbsolutePath();
+ }
+
+ /**
+ * Activate virtualenv for a RunUtil.
+ *
+ * @param runUtil an utility object for running virtualenv activation commands.
+ * @param virtualenvDir a File object representing the created virtualenv directory.
+ */
+ public static void activate(IRunUtil runUtil, File virtualenvDir) {
+ activate(runUtil, virtualenvDir.getAbsolutePath());
+ }
+
+ /**
+ * Activate virtualenv for a RunUtil.
+ *
+ * <p>This method will check for python bin directory existence
+ *
+ * @param runUtil an utility object for running virtualenv activation commands.
+ * @param virtualenvPath the path to the created virtualenv directory.
+ */
+ public static void activate(IRunUtil runUtil, String virtualenvPath) {
+ String pythonBinDir = getPythonBinDir(virtualenvPath);
+ String separater = ":";
+ String pythonPath =
+ getPackageInstallLocation(runUtil, virtualenvPath)
+ + separater
+ + System.getenv(PYTHONPATH);
+ runUtil.setEnvVariable(PATH, pythonBinDir + separater + System.getenv().get(PATH));
+ runUtil.setEnvVariable(VIRTUAL_ENV, virtualenvPath);
+ runUtil.setEnvVariable(PYTHONPATH, pythonPath);
+ runUtil.unsetEnvVariable(PYTHONHOME);
+ CLog.d("Activating virtual environment:");
+ CLog.d("%s: %s", PATH, pythonBinDir + separater + System.getenv().get(PATH));
+ CLog.d("%s: %s", VIRTUAL_ENV, virtualenvPath);
+ CLog.d("%s: %s", PYTHONPATH, pythonPath);
+ }
+
+ /**
+ * Gets the absolute path to the pip3 binary in the given venv directory.
+ *
+ * @param virtualenvPath the path to the venv directory.
+ * @return a string representing the absolute path to the pip3 binary.
+ */
+ private static String getPipPath(String virtualenvPath) {
+ File pipFile = new File(PythonVirtualenvHelper.getPythonBinDir(virtualenvPath), "pip3");
+ return pipFile.getAbsolutePath();
+ }
+
+ /**
+ * Gets python package install location.
+ *
+ * <p>This method will call /path/to/venv/bin/pip3 show pip and parse out package location from
+ * stdout output.
+ *
+ * @param runUtil an utility object for running for running commands.
+ * @param virtualenvPath the path to the created virtualenv directory.
+ * @return a string representing the absolute path to the location where Python packages are
+ * installed.
+ */
+ private static String getPackageInstallLocation(IRunUtil runUtil, String virtualenvPath) {
+ CommandResult result =
+ runUtil.runTimedCmd(60000, getPipPath(virtualenvPath), "show", "pip");
+ if (result.getStatus() != CommandStatus.SUCCESS) {
+ throw new RuntimeException(
+ String.format(
+ "Fail to run command: %s show pip.\nStatus:%s\nStdout:%s\nStderr:%s",
+ getPipPath(virtualenvPath),
+ result.getStatus(),
+ result.getStdout(),
+ result.getStderr()));
+ }
+ String stdout = result.getStdout();
+ String[] lines = stdout.split("\n");
+ String locationLine =
+ Stream.of(lines).filter(x -> x.startsWith("Location")).findFirst().orElse("");
+ return locationLine.split(" ")[1];
+ }
+}
diff --git a/tests/Android.bp b/tests/Android.bp
index 7d1334af0..caf95bbc3 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -12,6 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+java_library_host {
+ name: "tradefed-test-protos",
+ srcs: ["res/**/*.proto"],
+ libs: [
+ "libprotobuf-java-full",
+ ],
+ proto: {
+ include_dirs: ["external/protobuf/src"],
+ type: "full",
+ }
+}
+
tradefed_java_library_host {
name: "tradefed-tests",
defaults: ["tradefed_errorprone_defaults"],
@@ -30,6 +42,7 @@ tradefed_java_library_host {
"easymock",
"objenesis",
"mockito",
+ "tradefed-test-protos",
],
libs: [
"tradefed",
diff --git a/tests/res/proto/proto_util_test.proto b/tests/res/proto/proto_util_test.proto
new file mode 100644
index 000000000..a59332660
--- /dev/null
+++ b/tests/res/proto/proto_util_test.proto
@@ -0,0 +1,38 @@
+// Copyright (C) 2020 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.
+
+syntax = "proto3";
+
+option java_package = "com.android.tradefed.util.test";
+option java_outer_classname = "ProtoUtilTestProto";
+
+// A proto for com.android.tradefed.util.ProtoUtilTest.
+message TestMessage {
+ string string_field = 1;
+ int64 int_field = 2;
+ repeated string repeated_string_field = 3;
+
+ message SubMessage {
+ int64 int_field = 1;
+ repeated string repeated_string_field = 2;
+ }
+
+ SubMessage message_field = 4;
+ repeated SubMessage repeated_message_field = 5;
+
+ oneof oneof_field {
+ string oneof_string_field = 6;
+ SubMessage oneof_message_field = 7;
+ }
+}
diff --git a/tests/res/testconfigs/yaml/not-target-preparer.tf_yaml b/tests/res/testconfigs/yaml/not-target-preparer.tf_yaml
new file mode 100644
index 000000000..610b63795
--- /dev/null
+++ b/tests/res/testconfigs/yaml/not-target-preparer.tf_yaml
@@ -0,0 +1,16 @@
+description: "Human friendly description of the test"
+
+# pre_setup_action will run before the dependencies installation
+pre_setup_action:
+ - action:
+ name: "com.android.tradefed.testtype.AndroidJUnitTest"
+ options:
+ - package: "android.package"
+
+dependencies:
+
+tests:
+ - test:
+ name: "com.android.tradefed.testtype.AndroidJUnitTest"
+ options:
+ - package: "android.package"
diff --git a/tests/res/testconfigs/yaml/test-config.tf_yaml b/tests/res/testconfigs/yaml/test-config.tf_yaml
index eb89137b3..2df8a06c9 100644
--- a/tests/res/testconfigs/yaml/test-config.tf_yaml
+++ b/tests/res/testconfigs/yaml/test-config.tf_yaml
@@ -1,11 +1,29 @@
description: "Human friendly description of the test"
+# pre_setup_action will run before the dependencies installation
+pre_setup_action:
+ - action:
+ name: "com.android.tradefed.targetprep.RunCommandTargetPreparer"
+ options:
+ - run-command: "dumpsys value"
+ - action:
+ name: "com.android.tradefed.targetprep.RunCommandTargetPreparer"
+ options:
+ - run-command: "another one"
+
dependencies:
- apks: ["test.apk", "test2.apk"]
- apks: ["test1.apk"]
- files: ["file1.txt", "file2.txt"]
- device_files: {"tobepushed.txt": "/sdcard", "tobepushed2.txt": "/sdcard/"}
+# post_setup_action will run after the dependencies installation
+post_setup_action:
+ - action:
+ name: "com.android.tradefed.targetprep.RunCommandTargetPreparer"
+ options:
+ - run-command: "dumpsys value2"
+
tests:
- test:
name: "com.android.tradefed.testtype.AndroidJUnitTest"
diff --git a/tests/res/util/partial_zip.zip b/tests/res/util/partial_zip.zip
index 1f50fef78..4fb046ea7 100644
--- a/tests/res/util/partial_zip.zip
+++ b/tests/res/util/partial_zip.zip
Binary files differ
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 897aacef3..74717a58a 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -89,6 +89,7 @@ import com.android.tradefed.device.cloud.GceRemoteCmdFormatterTest;
import com.android.tradefed.device.cloud.GceSshTunnelMonitorTest;
import com.android.tradefed.device.cloud.ManagedRemoteDeviceTest;
import com.android.tradefed.device.cloud.NestedRemoteDeviceTest;
+import com.android.tradefed.device.cloud.RemoteAndroidVirtualDeviceTest;
import com.android.tradefed.device.cloud.RemoteFileUtilTest;
import com.android.tradefed.device.contentprovider.ContentProviderHandlerTest;
import com.android.tradefed.device.helper.TelephonyHelperTest;
@@ -96,33 +97,21 @@ import com.android.tradefed.device.metric.AtraceCollectorTest;
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.BuddyInfoMetricCollectorTest;
-import com.android.tradefed.device.metric.BugreportzMetricCollectorTest;
import com.android.tradefed.device.metric.BugreportzOnFailureCollectorTest;
import com.android.tradefed.device.metric.DebugHostLogOnFailureCollectorTest;
import com.android.tradefed.device.metric.DeviceMetricDataTest;
-import com.android.tradefed.device.metric.DumpHeapCollectorTest;
import com.android.tradefed.device.metric.FilePullerDeviceMetricCollectorTest;
import com.android.tradefed.device.metric.FilePullerLogCollectorTest;
-import com.android.tradefed.device.metric.GraphicsStatsMetricCollectorTest;
import com.android.tradefed.device.metric.GcovCodeCoverageCollectorTest;
import com.android.tradefed.device.metric.HostStatsdMetricCollectorTest;
import com.android.tradefed.device.metric.IncidentReportCollectorTest;
-import com.android.tradefed.device.metric.IonHeapInfoMetricCollectorTest;
import com.android.tradefed.device.metric.JavaCodeCoverageCollectorTest;
import com.android.tradefed.device.metric.LogcatOnFailureCollectorTest;
import com.android.tradefed.device.metric.LogcatTimingMetricCollectorTest;
-import com.android.tradefed.device.metric.MemInfoMetricCollectorTest;
-import com.android.tradefed.device.metric.PagetypeInfoMetricCollectorTest;
import com.android.tradefed.device.metric.PerfettoPullerMetricCollectorTest;
-import com.android.tradefed.device.metric.ProcessMaxMemoryCollectorTest;
import com.android.tradefed.device.metric.RebootReasonCollectorTest;
import com.android.tradefed.device.metric.RuntimeRestartCollectorTest;
-import com.android.tradefed.device.metric.ScheduleMultipleDeviceMetricCollectorTest;
-import com.android.tradefed.device.metric.ScheduledDeviceMetricCollectorTest;
import com.android.tradefed.device.metric.ScreenshotOnFailureCollectorTest;
-import com.android.tradefed.device.metric.TemperatureCollectorTest;
-import com.android.tradefed.device.metric.TraceMetricCollectorTest;
import com.android.tradefed.device.recovery.BatteryUnavailableDeviceRecoveryTest;
import com.android.tradefed.device.recovery.RunConfigDeviceRecoveryTest;
import com.android.tradefed.device.recovery.UsbResetMultiDeviceRecoveryTest;
@@ -157,6 +146,7 @@ import com.android.tradefed.log.HistoryLoggerTest;
import com.android.tradefed.log.LogRegistryTest;
import com.android.tradefed.log.SimpleFileLoggerTest;
import com.android.tradefed.log.TerribleFailureEmailHandlerTest;
+import com.android.tradefed.monitoring.LabResourceDeviceMonitorTest;
import com.android.tradefed.postprocessor.AggregatePostProcessorTest;
import com.android.tradefed.postprocessor.AveragePostProcessorTest;
import com.android.tradefed.postprocessor.BasePostProcessorTest;
@@ -245,6 +235,8 @@ import com.android.tradefed.targetprep.RebootTargetPreparerTest;
import com.android.tradefed.targetprep.RestartSystemServerTargetPreparerTest;
import com.android.tradefed.targetprep.RootTargetPreparerTest;
import com.android.tradefed.targetprep.RunCommandTargetPreparerTest;
+import com.android.tradefed.targetprep.RunOnSecondaryUserTargetPreparerTest;
+import com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparerTest;
import com.android.tradefed.targetprep.RunHostCommandTargetPreparerTest;
import com.android.tradefed.targetprep.RunHostScriptTargetPreparerTest;
import com.android.tradefed.targetprep.StopServicesSetupTest;
@@ -260,6 +252,7 @@ import com.android.tradefed.targetprep.multi.MergeMultiBuildTargetPreparerTest;
import com.android.tradefed.targetprep.multi.MixImageZipPreparerTest;
import com.android.tradefed.targetprep.suite.SuiteApkInstallerTest;
import com.android.tradefed.testtype.AndroidJUnitTestTest;
+import com.android.tradefed.testtype.ArtGTestTest;
import com.android.tradefed.testtype.ArtRunTestTest;
import com.android.tradefed.testtype.ClangCodeCoverageListenerTest;
import com.android.tradefed.testtype.DeviceBatteryLevelCheckerTest;
@@ -365,7 +358,9 @@ import com.android.tradefed.util.MultiMapTest;
import com.android.tradefed.util.NativeCodeCoverageFlusherTest;
import com.android.tradefed.util.PairTest;
import com.android.tradefed.util.PropertyChangerTest;
+import com.android.tradefed.util.ProtoUtilTest;
import com.android.tradefed.util.PsParserTest;
+import com.android.tradefed.util.PythonVirtualenvHelperTest;
import com.android.tradefed.util.QuotationAwareTokenizerTest;
import com.android.tradefed.util.RegexTrieTest;
import com.android.tradefed.util.RemoteZipTest;
@@ -379,6 +374,7 @@ import com.android.tradefed.util.SimplePerfUtilTest;
import com.android.tradefed.util.SimpleStatsTest;
import com.android.tradefed.util.SizeLimitedOutputStreamTest;
import com.android.tradefed.util.Sl4aBluetoothUtilTest;
+import com.android.tradefed.util.SparseImageUtilTest;
import com.android.tradefed.util.StreamUtilTest;
import com.android.tradefed.util.StringEscapeUtilsTest;
import com.android.tradefed.util.StringUtilTest;
@@ -519,7 +515,7 @@ import org.junit.runners.Suite.SuiteClasses;
GceSshTunnelMonitorTest.class,
ManagedRemoteDeviceTest.class,
NestedRemoteDeviceTest.class,
- RemoteAndroidDeviceTest.class,
+ RemoteAndroidVirtualDeviceTest.class,
RemoteFileUtilTest.class,
// device.contentprovider
@@ -533,33 +529,21 @@ import org.junit.runners.Suite.SuiteClasses;
AtraceRunMetricCollectorTest.class,
AutoLogCollectorTest.class,
BaseDeviceMetricCollectorTest.class,
- BuddyInfoMetricCollectorTest.class,
- BugreportzMetricCollectorTest.class,
BugreportzOnFailureCollectorTest.class,
DebugHostLogOnFailureCollectorTest.class,
DeviceMetricDataTest.class,
- DumpHeapCollectorTest.class,
FilePullerDeviceMetricCollectorTest.class,
FilePullerLogCollectorTest.class,
- GraphicsStatsMetricCollectorTest.class,
GcovCodeCoverageCollectorTest.class,
IncidentReportCollectorTest.class,
- IonHeapInfoMetricCollectorTest.class,
JavaCodeCoverageCollectorTest.class,
LogcatOnFailureCollectorTest.class,
LogcatTimingMetricCollectorTest.class,
- MemInfoMetricCollectorTest.class,
- PagetypeInfoMetricCollectorTest.class,
PerfettoPullerMetricCollectorTest.class,
- ProcessMaxMemoryCollectorTest.class,
RebootReasonCollectorTest.class,
RuntimeRestartCollectorTest.class,
- ScheduledDeviceMetricCollectorTest.class,
- ScheduleMultipleDeviceMetricCollectorTest.class,
ScreenshotOnFailureCollectorTest.class,
HostStatsdMetricCollectorTest.class,
- TemperatureCollectorTest.class,
- TraceMetricCollectorTest.class,
// device.recovery
BatteryUnavailableDeviceRecoveryTest.class,
@@ -583,7 +567,6 @@ import org.junit.runners.Suite.SuiteClasses;
InvocationContextTest.class,
InvocationExecutionTest.class,
RemoteInvocationExecutionTest.class,
- SandboxedInvocationExecutionTest.class,
ShardListenerTest.class,
ShardMainResultForwarderTest.class,
TestInvocationMultiTest.class,
@@ -607,7 +590,6 @@ import org.junit.runners.Suite.SuiteClasses;
// invoker.sandbox
ParentSandboxInvocationExecutionTest.class,
- SandboxedInvocationExecutionTest.class,
// lite
DryRunnerTest.class,
@@ -651,7 +633,6 @@ import org.junit.runners.Suite.SuiteClasses;
MultiFailureDescriptionTest.class,
SnapshotInputStreamSourceTest.class,
SubprocessResultsReporterTest.class,
- TestDescriptionTest.class,
TestFailureEmailResultReporterTest.class,
PassingTestFileReporterTest.class,
TestDescriptionTest.class,
@@ -711,6 +692,8 @@ import org.junit.runners.Suite.SuiteClasses;
RunCommandTargetPreparerTest.class,
RunHostCommandTargetPreparerTest.class,
RunHostScriptTargetPreparerTest.class,
+ RunOnSecondaryUserTargetPreparerTest.class,
+ RunOnWorkProfileTargetPreparerTest.class,
StopServicesSetupTest.class,
SystemUpdaterDeviceFlasherTest.class,
TargetSetupErrorTest.class,
@@ -754,6 +737,7 @@ import org.junit.runners.Suite.SuiteClasses;
// testtype
AndroidJUnitTestTest.class,
+ ArtGTestTest.class,
ArtRunTestTest.class,
ClangCodeCoverageListenerTest.class,
CoverageMeasurementForwarderTest.class,
@@ -879,7 +863,9 @@ import org.junit.runners.Suite.SuiteClasses;
MergedZipEntryCollectionTest.class,
NativeCodeCoverageFlusherTest.class,
PairTest.class,
+ ProtoUtilTest.class,
PsParserTest.class,
+ PythonVirtualenvHelperTest.class,
QuotationAwareTokenizerTest.class,
RegexTrieTest.class,
RemoteZipTest.class,
@@ -893,6 +879,7 @@ import org.junit.runners.Suite.SuiteClasses;
SimpleStatsTest.class,
SizeLimitedOutputStreamTest.class,
Sl4aBluetoothUtilTest.class,
+ SparseImageUtilTest.class,
StreamUtilTest.class,
StringEscapeUtilsTest.class,
StringUtilTest.class,
@@ -937,6 +924,9 @@ import org.junit.runners.Suite.SuiteClasses;
// util/testmapping
TestInfoTest.class,
TestMappingTest.class,
+
+ // monitoring
+ LabResourceDeviceMonitorTest.class,
})
public class UnitTests {
// empty of purpose
diff --git a/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java b/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java
index 3bf18e049..86248bd8b 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterCommandLauncherFuncTest.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.cluster;
+import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
@@ -35,11 +36,15 @@ import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.util.FileUtil;
+import org.hamcrest.CoreMatchers;
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.InOrder;
import org.mockito.Mock;
+import org.mockito.Mockito;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
@@ -52,8 +57,9 @@ import java.util.HashMap;
public class ClusterCommandLauncherFuncTest {
private static final String LEGACY_TRADEFED_JAR = "/testdata/tradefed-prebuilt-cts-8.0_r21.jar";
- private static final String LEGACY_TRADEFED_COMMAND =
- "host --null-device --class com.android.tradefed.device.DeviceDiagTest";
+ private static final String LEGACY_TRADEFED_COMMAND = "fake.xml --null-device --run testRun PF";
+ private static final String LEGACY_TRADEFED_COMMAND_FOR_INVOCATION_FAILURE =
+ "fake.xml --null-device --fail-invocation-with-cause cause";
private File mRootDir;
private IConfiguration mConfiguration;
@@ -82,22 +88,51 @@ public class ClusterCommandLauncherFuncTest {
FileUtil.recursiveDelete(mRootDir);
}
- // @Ignore
@Test
public void testRun_withLegacyTradefed()
throws IOException, ConfigurationException, DeviceNotAvailableException {
File tfJar = new File(mRootDir, "tradefed.jar");
FileUtil.writeToFile(getClass().getResourceAsStream(LEGACY_TRADEFED_JAR), tfJar);
+ FileUtil.writeToFile(
+ getClass().getResourceAsStream("/config/tf/fake.xml"),
+ new File(mRootDir, "fake.xml"));
mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", mRootDir.getAbsolutePath());
mOptionSetter.setOptionValue("cluster:use-subprocess-reporting", "true");
mOptionSetter.setOptionValue("cluster:command-line", LEGACY_TRADEFED_COMMAND);
mLauncher.run(mTestInformation, mListener);
+ InOrder inOrder = Mockito.inOrder(mListener);
HashMap<String, MetricMeasurement.Metric> emptyMap = new HashMap<>();
- verify(mListener).testRunStarted(anyString(), anyInt(), anyInt(), anyLong());
- verify(mListener).testStarted(any(TestDescription.class), anyLong());
- verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap));
- verify(mListener).testRunEnded(anyLong(), eq(emptyMap));
+ inOrder.verify(mListener).testRunStarted(eq("testRun"), anyInt(), anyInt(), anyLong());
+ inOrder.verify(mListener).testStarted(any(TestDescription.class), anyLong());
+ inOrder.verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap));
+ inOrder.verify(mListener).testStarted(any(TestDescription.class), anyLong());
+ inOrder.verify(mListener).testFailed(any(TestDescription.class), anyString());
+ inOrder.verify(mListener).testEnded(any(TestDescription.class), anyLong(), eq(emptyMap));
+ inOrder.verify(mListener).testRunEnded(anyLong(), eq(emptyMap));
+ }
+
+ @Test
+ public void testRun_withLegacyTradefed_invocationFailed()
+ throws IOException, ConfigurationException, DeviceNotAvailableException {
+ File tfJar = new File(mRootDir, "tradefed.jar");
+ FileUtil.writeToFile(getClass().getResourceAsStream(LEGACY_TRADEFED_JAR), tfJar);
+ FileUtil.writeToFile(
+ getClass().getResourceAsStream("/config/tf/fake.xml"),
+ new File(mRootDir, "fake.xml"));
+ mOptionSetter.setOptionValue("cluster:env-var", "TF_PATH", mRootDir.getAbsolutePath());
+ mOptionSetter.setOptionValue("cluster:use-subprocess-reporting", "true");
+ mOptionSetter.setOptionValue(
+ "cluster:command-line", LEGACY_TRADEFED_COMMAND_FOR_INVOCATION_FAILURE);
+
+ try {
+ mLauncher.run(mTestInformation, mListener);
+ fail("SubprocessCommandException should be thrown");
+ } catch (SubprocessCommandException e) {
+ Assert.assertThat(e.getCause().getMessage(), CoreMatchers.containsString("cause"));
+ }
+
+ verify(mListener).invocationFailed(any(Throwable.class));
}
}
diff --git a/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java b/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
index d4befb97e..adfc487ba 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
@@ -521,6 +521,23 @@ public class ClusterCommandSchedulerTest {
new String[] {CMD_LINE, "--serial", "deviceSerial"}, getExecCommandArgs());
}
+ /**
+ * If a unique device serial (one with a hostname prefix) is specified for a command task,
+ * convert it to a local device serial before appending it.
+ */
+ @Test
+ public void testExecCommandWithVirtualDeviceSerial() {
+ List<ClusterCommand> cmds = new ArrayList<>();
+ ClusterCommand cmd = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE);
+ cmd.setTargetDeviceSerials(
+ ArrayUtil.list(ClusterHostUtil.getHostName() + ":emulator-5554"));
+ cmds.add(cmd);
+ mScheduler.execCommands(cmds);
+ assertEquals(CMD_LINE, cmds.get(0).getCommandLine());
+ assertArrayEquals(
+ new String[] {CMD_LINE, "--serial", "emulator-5554"}, getExecCommandArgs());
+ }
+
/** Multiple serials specified for a command task. */
@Test
public void testExecCommandWithMultipleSerials() {
@@ -828,6 +845,70 @@ public class ClusterCommandSchedulerTest {
handler.invocationInitiated(context);
}
+ @Test
+ public void testInvocationEventHandler_withSubprocessCommandException() {
+ ClusterCommand mockCommand = new ClusterCommand(COMMAND_ID, TASK_ID, CMD_LINE);
+ IInvocationContext context = new InvocationContext();
+ ITestDevice mockTestDevice = EasyMock.createMock(ITestDevice.class);
+ EasyMock.expect(mockTestDevice.getSerialNumber()).andReturn(DEVICE_SERIAL);
+ EasyMock.expect(mockTestDevice.getIDevice()).andReturn(new StubDevice(DEVICE_SERIAL));
+ context.addAllocatedDevice("", mockTestDevice);
+ IBuildInfo mockBuildInfo = EasyMock.createMock(IBuildInfo.class);
+ context.addDeviceBuildInfo("", mockBuildInfo);
+ ClusterCommandScheduler.InvocationEventHandler handler =
+ mScheduler.new InvocationEventHandler(mockCommand);
+ mMockClusterOptions.setCollectEarlyTestSummary(true);
+
+ mMockEventUploader.postEvent(
+ checkClusterCommandEvent(ClusterCommandEvent.Type.InvocationInitiated));
+ mMockEventUploader.flush();
+ mMockEventUploader.postEvent(
+ checkClusterCommandEvent(ClusterCommandEvent.Type.InvocationStarted));
+ mMockEventUploader.flush();
+ mMockEventUploader.postEvent(
+ checkClusterCommandEvent(ClusterCommandEvent.Type.TestRunInProgress));
+ EasyMock.expectLastCall().anyTimes();
+ mMockEventUploader.postEvent(
+ checkClusterCommandEvent(ClusterCommandEvent.Type.InvocationEnded));
+ mMockEventUploader.flush();
+ Capture<ClusterCommandEvent> capture = new Capture<>();
+ mMockEventUploader.postEvent(EasyMock.capture(capture));
+ mMockEventUploader.flush();
+
+ EasyMock.replay(mMockEventUploader, mockBuildInfo, mockTestDevice);
+ handler.invocationInitiated(context);
+ List<TestSummary> summaries = new ArrayList<>();
+ summaries.add(
+ new TestSummary(new TestSummary.TypedString("http://uri", TestSummary.Type.URI)));
+ handler.putEarlySummary(summaries);
+ handler.putSummary(summaries);
+ handler.invocationStarted(context);
+ handler.invocationFailed(
+ new SubprocessCommandException(
+ "error_message", new Throwable("subprocess_command_error_message")));
+ handler.invocationEnded(100L);
+ context.addAllocatedDevice(DEVICE_SERIAL, mockTestDevice);
+ Map<ITestDevice, FreeDeviceState> releaseMap = new HashMap<>();
+ releaseMap.put(mockTestDevice, FreeDeviceState.AVAILABLE);
+ handler.invocationComplete(context, releaseMap);
+ EasyMock.verify(mMockEventUploader, mockBuildInfo, mockTestDevice);
+ ClusterCommandEvent capturedEvent = capture.getValue();
+ assertTrue(capturedEvent.getType().equals(ClusterCommandEvent.Type.InvocationCompleted));
+ assertTrue(
+ ((String) capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_ERROR))
+ .contains("SubprocessCommandException"));
+ assertEquals(
+ "subprocess_command_error_message",
+ capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_SUBPROCESS_COMMAND_ERROR));
+ assertEquals(
+ "0", capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_FAILED_TEST_COUNT));
+ assertEquals(
+ "0", capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_PASSED_TEST_COUNT));
+ assertEquals(
+ "URI: http://uri\n",
+ capturedEvent.getData().get(ClusterCommandEvent.DATA_KEY_SUMMARY));
+ }
+
/**
* Test that when dry-run is used we validate the config and no ConfigurationException gets
* thrown.
@@ -1058,7 +1139,8 @@ public class ClusterCommandSchedulerTest {
List<IDeviceConfiguration> deviceConfigs = config.getDeviceConfig();
assertEquals(cmd.getTargetDeviceSerials().size(), deviceConfigs.size());
for (int i = 0; i < cmd.getTargetDeviceSerials().size(); i++) {
- String serial = cmd.getTargetDeviceSerials().get(i);
+ String serial =
+ ClusterHostUtil.getLocalDeviceSerial(cmd.getTargetDeviceSerials().get(i));
Collection<String> serials =
deviceConfigs.get(i).getDeviceRequirements().getSerials(null);
assertTrue(serials.size() == 1 && serials.contains(serial));
@@ -1165,6 +1247,44 @@ public class ClusterCommandSchedulerTest {
}
}
+ /** Tests an execution of a managed cluster command. */
+ @Test
+ public void testExecManagedClusterCommand_virtualDeviceTest() throws Exception {
+ File workDir = null;
+ try {
+ ClusterCommand cmd = createMockManagedCommand(1);
+ cmd.setTargetDeviceSerials(
+ ArrayUtil.list(ClusterHostUtil.getHostName() + ":emulator-5554"));
+ workDir = new File(System.getProperty("java.io.tmpdir"), cmd.getAttemptId());
+ TestEnvironment testEnvironment = createMockTestEnvironment();
+ List<TestResource> testResources = createMockTestResources();
+ TestContext testContext = new TestContext();
+ mMockClusterClient = Mockito.spy(mMockClusterClient);
+ Mockito.doReturn(testEnvironment)
+ .when(mMockClusterClient)
+ .getTestEnvironment(REQUEST_ID);
+ Mockito.doReturn(testResources).when(mMockClusterClient).getTestResources(REQUEST_ID);
+ Mockito.doReturn(testContext)
+ .when(mMockClusterClient)
+ .getTestContext(REQUEST_ID, COMMAND_ID);
+ InvocationEventHandler invocationEventHandler =
+ mScheduler.new InvocationEventHandler(cmd);
+
+ mScheduler.execManagedClusterCommand(cmd, invocationEventHandler);
+
+ String[] args = getExecCommandArgs();
+ assertTrue(args.length > 0);
+ IConfiguration config =
+ ConfigurationFactory.getInstance().createConfigurationFromArgs(args);
+ verifyConfig(config, cmd, testEnvironment, testResources, workDir);
+ } finally {
+ if (workDir != null) {
+ // Clean up work directory
+ FileUtil.recursiveDelete(workDir);
+ }
+ }
+ }
+
/** Tests an execution of a managed cluster command for multiple devices. */
@Test
public void testExecManagedClusterCommand_multiDeviceTest() throws Exception {
diff --git a/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java b/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java
index fc98304c6..d0ba372b9 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterDeviceMonitorTest.java
@@ -100,6 +100,24 @@ public class ClusterDeviceMonitorTest {
Assert.assertEquals("cluster1", hostEvent.getClusterId());
Assert.assertEquals(Arrays.asList("cluster2", "cluster3"), hostEvent.getNextClusterIds());
Assert.assertEquals("lab1", hostEvent.getLabName());
+ Assert.assertEquals("", hostEvent.getData().get("label"));
+ }
+
+ @Test
+ public void testLabel() throws Exception {
+ mClusterOptionSetter.setOptionValue("cluster:label", "label1");
+ mClusterOptionSetter.setOptionValue("cluster:label", "label2");
+ Capture<ClusterHostEvent> capture = new Capture<>();
+ mHostEventUploader.postEvent(EasyMock.capture(capture));
+ mHostEventUploader.flush();
+ EasyMock.replay(mHostEventUploader);
+ mEventDispatcher.dispatch();
+ EasyMock.verify(mHostEventUploader);
+ ClusterHostEvent hostEvent = capture.getValue();
+ Assert.assertEquals("cluster1", hostEvent.getClusterId());
+ Assert.assertEquals(Arrays.asList("cluster2", "cluster3"), hostEvent.getNextClusterIds());
+ Assert.assertEquals("lab1", hostEvent.getLabName());
+ Assert.assertEquals("label1,label2", hostEvent.getData().get("label"));
}
void setOptions() throws Exception {
diff --git a/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java b/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java
index e2c10bdfb..a7ff29c68 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterHostUtilTest.java
@@ -25,6 +25,8 @@ import java.security.InvalidParameterException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import org.easymock.EasyMock;
import org.junit.Assert;
@@ -37,6 +39,7 @@ import org.junit.runners.JUnit4;
public class ClusterHostUtilTest {
private static final String DEVICE_SERIAL = "serial";
+ private static final String EMULATOR_SERIAL = "emulator-5554";
@Test
public void testIsIpPort() {
@@ -287,4 +290,71 @@ public class ClusterHostUtilTest {
"simOperator");
Assert.assertEquals("product", ClusterHostUtil.getRunTarget(device, format, null));
}
+
+ @Test
+ public void testGetRunTarget_withStubDevice() {
+ final String hostname = ClusterHostUtil.getHostName();
+ // with a stub device.
+ DeviceDescriptor device =
+ new DeviceDescriptor(
+ DEVICE_SERIAL,
+ true,
+ DeviceAllocationState.Available,
+ "product",
+ "productVariant",
+ "sdkVersion",
+ "buildId",
+ "batteryLevel");
+ Assert.assertEquals(
+ hostname + ":" + DEVICE_SERIAL,
+ ClusterHostUtil.getRunTarget(device, "{SERIAL}", null));
+ }
+
+ @Test
+ public void testGetRunTarget_withEmulator() {
+ final String hostname = ClusterHostUtil.getHostName();
+ // with a stub device.
+ DeviceDescriptor device =
+ new DeviceDescriptor(
+ EMULATOR_SERIAL,
+ false,
+ DeviceAllocationState.Available,
+ "product",
+ "productVariant",
+ "sdkVersion",
+ "buildId",
+ "batteryLevel");
+ Assert.assertEquals(
+ hostname + ":" + EMULATOR_SERIAL,
+ ClusterHostUtil.getRunTarget(device, "{SERIAL}", null));
+ }
+
+ @Test
+ public void testGetRunTarget_withEmptyDeviceSerial() {
+ final String hostname = ClusterHostUtil.getHostName();
+ // with a stub device.
+ DeviceDescriptor device =
+ new DeviceDescriptor(
+ "",
+ false,
+ DeviceAllocationState.Available,
+ "product",
+ "productVariant",
+ "sdkVersion",
+ "buildId",
+ "batteryLevel");
+ Assert.assertEquals(
+ hostname + ":" + ClusterHostUtil.NULL_DEVICE_SERIAL_PLACEHOLDER,
+ ClusterHostUtil.getRunTarget(device, "{SERIAL}", null));
+ }
+
+ @Test
+ public void testGetHostIpAddress() {
+ final String hostIp = ClusterHostUtil.getHostIpAddress();
+ Assert.assertNotEquals(hostIp, "127.0.0.1");
+ Pattern pattern =
+ Pattern.compile("[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}" + "|UNKNOWN");
+ Matcher matcher = pattern.matcher(hostIp);
+ Assert.assertTrue("host ip format not match: " + hostIp, matcher.matches());
+ }
}
diff --git a/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java b/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java
index f83ba2b03..67622f3b8 100644
--- a/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java
+++ b/tests/src/com/android/tradefed/cluster/SubprocessConfigBuilderTest.java
@@ -76,6 +76,23 @@ public class SubprocessConfigBuilderTest {
verifyWrapperXml(doc, reporterPort);
}
+ @Test
+ public void testCreateWrapperConfig_forCommandWithSlashes() throws Exception {
+ String oriConfigName = "util/timewaster";
+ String reporterPort = "1024";
+ mConfigBuilder
+ .setClasspath(mClasspath)
+ .setWorkingDir(mWorkDir)
+ .setOriginalConfig(oriConfigName)
+ .setPort(reporterPort);
+ File config = mConfigBuilder.build();
+ assertNotNull(config);
+ DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
+ Document doc = dBuilder.parse(config);
+ verifyWrapperXml(doc, reporterPort);
+ }
+
private void verifyWrapperXml(Document doc, String reporterPort) {
NodeList reporters = doc.getElementsByTagName("result_reporter");
assertTrue(0 < reporters.getLength());
diff --git a/tests/src/com/android/tradefed/command/CommandSchedulerTest.java b/tests/src/com/android/tradefed/command/CommandSchedulerTest.java
index 793ee11a4..7373e33bc 100644
--- a/tests/src/com/android/tradefed/command/CommandSchedulerTest.java
+++ b/tests/src/com/android/tradefed/command/CommandSchedulerTest.java
@@ -365,12 +365,14 @@ public class CommandSchedulerTest {
mScheduler =
new TestableCommandScheduler() {
@Override
- Map<String, ITestDevice> allocateDevices(
+ DeviceAllocationResult allocateDevices(
IConfiguration config, IDeviceManager manager) {
+ DeviceAllocationResult results = new DeviceAllocationResult();
Map<String, ITestDevice> allocated = new HashMap<>();
((MockDeviceManager) manager).addDevice(mockDevice);
allocated.put("device", ((MockDeviceManager) manager).allocateDevice());
- return allocated;
+ results.addAllocatedDevices(allocated);
+ return results;
}
};
replayMocks(mockDevice, mockListener);
@@ -1094,8 +1096,10 @@ public class CommandSchedulerTest {
mMockConfiguration.validateOptions();
replayMocks();
mScheduler.start();
- Map<String, ITestDevice> devices = mScheduler.allocateDevices(
- mMockConfiguration, mMockManager);
+ DeviceAllocationResult results =
+ mScheduler.allocateDevices(mMockConfiguration, mMockManager);
+ assertTrue(results.wasAllocationSuccessful());
+ Map<String, ITestDevice> devices = results.getAllocatedDevices();
assertEquals(1, devices.size());
mScheduler.shutdown();
}
@@ -1120,8 +1124,10 @@ public class CommandSchedulerTest {
mMockConfiguration.setDeviceConfigList(EasyMock.anyObject());
replayMocks();
mScheduler.start();
- Map<String, ITestDevice> devices =
+ DeviceAllocationResult results =
mScheduler.allocateDevices(mMockConfiguration, mMockManager);
+ assertTrue(results.wasAllocationSuccessful());
+ Map<String, ITestDevice> devices = results.getAllocatedDevices();
// With replicated setup, all devices get allocated.
assertEquals(3, devices.size());
mScheduler.shutdown();
@@ -1147,8 +1153,10 @@ public class CommandSchedulerTest {
mMockConfiguration.validateOptions();
replayMocks();
mScheduler.start();
- Map<String, ITestDevice> devices = mScheduler.allocateDevices(
- mMockConfiguration, mMockManager);
+ DeviceAllocationResult results =
+ mScheduler.allocateDevices(mMockConfiguration, mMockManager);
+ assertTrue(results.wasAllocationSuccessful());
+ Map<String, ITestDevice> devices = results.getAllocatedDevices();
assertEquals(2, devices.size());
assertEquals(0, mMockManager.getQueueOfAvailableDeviceSize());
mScheduler.shutdown();
@@ -1166,8 +1174,10 @@ public class CommandSchedulerTest {
mMockConfiguration.validateOptions();
replayMocks();
mScheduler.start();
- Map<String, ITestDevice> devices = mScheduler.allocateDevices(
- mMockConfiguration, mMockManager);
+ DeviceAllocationResult results =
+ mScheduler.allocateDevices(mMockConfiguration, mMockManager);
+ assertFalse(results.wasAllocationSuccessful());
+ Map<String, ITestDevice> devices = results.getAllocatedDevices();
assertEquals(0, devices.size());
assertEquals(2, mMockManager.getQueueOfAvailableDeviceSize());
mScheduler.shutdown();
@@ -1279,12 +1289,14 @@ public class CommandSchedulerTest {
mScheduler =
new TestableCommandScheduler() {
@Override
- Map<String, ITestDevice> allocateDevices(
+ DeviceAllocationResult allocateDevices(
IConfiguration config, IDeviceManager manager) {
+ DeviceAllocationResult results = new DeviceAllocationResult();
Map<String, ITestDevice> allocated = new HashMap<>();
((MockDeviceManager) manager).addDevice(mockDevice);
allocated.put("device", ((MockDeviceManager) manager).allocateDevice());
- return allocated;
+ results.addAllocatedDevices(allocated);
+ return results;
}
};
diff --git a/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java b/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java
index 87e60b755..c13780f04 100644
--- a/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java
+++ b/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java
@@ -1819,9 +1819,13 @@ public class ConfigurationFactoryTest extends TestCase {
IBuildProvider provider = config.getBuildProvider();
assertTrue(provider instanceof IDeviceBuildProvider);
IBuildInfo info = ((IDeviceBuildProvider) provider).getBuild(null);
- assertEquals("5", info.getBuildId());
- assertEquals("test", info.getBuildFlavor());
- assertEquals("main", info.getBuildBranch());
+ try {
+ assertEquals("5", info.getBuildId());
+ assertEquals("test", info.getBuildFlavor());
+ assertEquals("main", info.getBuildBranch());
+ } finally {
+ info.cleanUp();
+ }
}
private static String getClassName(String name) {
diff --git a/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java b/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java
index 69dd56d53..97730b4fe 100644
--- a/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java
+++ b/tests/src/com/android/tradefed/config/DynamicRemoteFileResolverTest.java
@@ -604,7 +604,7 @@ public class DynamicRemoteFileResolverTest {
IRemoteFileResolver actual = loader.load(NullFileResolver.PROTOCOL, ImmutableMap.of());
- assertThat(actual).isSameAs(expected);
+ assertThat(actual).isSameInstanceAs(expected);
}
@Test
@@ -616,7 +616,7 @@ public class DynamicRemoteFileResolverTest {
IRemoteFileResolver resolver1 = loader1.load(NullFileResolver.PROTOCOL, ImmutableMap.of());
IRemoteFileResolver resolver2 = loader2.load(NullFileResolver.PROTOCOL, ImmutableMap.of());
- assertThat(resolver1).isNotSameAs(resolver2);
+ assertThat(resolver1).isNotSameInstanceAs(resolver2);
}
@Test
@@ -968,15 +968,9 @@ public class DynamicRemoteFileResolverTest {
public static final class DuplicateNullFileResolver extends NullFileResolver {}
private static final Correspondence<File, File> FILE_PATH_EQUIVALENCE =
- new Correspondence<File, File>() {
- @Override
- public boolean compare(File actual, File expected) {
- return expected.getAbsolutePath().equals(actual.getAbsolutePath());
- }
-
- @Override
- public String toString() {
- return "is equivalent to";
- }
- };
+ Correspondence.from(
+ (File actual, File expected) -> {
+ return expected.getAbsolutePath().equals(actual.getAbsolutePath());
+ },
+ "is equivalent to");
}
diff --git a/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java b/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java
index 6a1c08187..478444379 100644
--- a/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java
+++ b/tests/src/com/android/tradefed/config/yaml/ConfigurationYamlParserTest.java
@@ -19,15 +19,18 @@ import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tradefed.build.DependenciesResolver;
import com.android.tradefed.build.StubBuildProvider;
import com.android.tradefed.config.ConfigurationDef;
+import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.suite.SuiteResultReporter;
import com.android.tradefed.targetprep.ITargetPreparer;
import com.android.tradefed.targetprep.PushFilePreparer;
+import com.android.tradefed.targetprep.RunCommandTargetPreparer;
import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
import com.android.tradefed.testtype.AndroidJUnitTest;
@@ -45,6 +48,8 @@ import java.util.List;
public class ConfigurationYamlParserTest {
private static final String YAML_TEST_CONFIG_1 = "/testconfigs/yaml/test-config.tf_yaml";
+ private static final String YAML_TEST_CONFIG_2 =
+ "/testconfigs/yaml/not-target-preparer.tf_yaml";
private ConfigurationYamlParser mParser;
private ConfigurationDef mConfigDef;
@@ -84,14 +89,19 @@ public class ConfigurationYamlParserTest {
// Dependencies
// apk dependencies
- assertEquals(2, config.getTargetPreparers().size());
- ITargetPreparer installApk = config.getTargetPreparers().get(0);
+ assertEquals(5, config.getTargetPreparers().size());
+ ITargetPreparer preCommandRunner = config.getTargetPreparers().get(0);
+ assertTrue(preCommandRunner instanceof RunCommandTargetPreparer);
+ ITargetPreparer preCommandRunner2 = config.getTargetPreparers().get(1);
+ assertTrue(preCommandRunner2 instanceof RunCommandTargetPreparer);
+
+ ITargetPreparer installApk = config.getTargetPreparers().get(2);
assertTrue(installApk instanceof SuiteApkInstaller);
assertThat(((SuiteApkInstaller) installApk).getTestsFileName())
.containsExactly(
new File("test.apk"), new File("test2.apk"), new File("test1.apk"));
// device file dependencies
- ITargetPreparer pushFile = config.getTargetPreparers().get(1);
+ ITargetPreparer pushFile = config.getTargetPreparers().get(3);
assertTrue(pushFile instanceof PushFilePreparer);
assertThat(((PushFilePreparer) pushFile).getPushSpecs(null))
.containsExactly(
@@ -99,6 +109,8 @@ public class ConfigurationYamlParserTest {
new File("tobepushed2.txt"),
"/sdcard",
new File("tobepushed.txt"));
+ ITargetPreparer postCommandRunner = config.getTargetPreparers().get(4);
+ assertTrue(postCommandRunner instanceof RunCommandTargetPreparer);
// Result reporters
List<ITestInvocationListener> listeners = config.getTestInvocationListeners();
assertTrue(listeners.get(0) instanceof SuiteResultReporter);
@@ -123,14 +135,14 @@ public class ConfigurationYamlParserTest {
// Dependencies
// apk dependencies
- assertEquals(2, config.getTargetPreparers().size());
- ITargetPreparer installApk = config.getTargetPreparers().get(0);
+ assertEquals(5, config.getTargetPreparers().size());
+ ITargetPreparer installApk = config.getTargetPreparers().get(2);
assertTrue(installApk instanceof SuiteApkInstaller);
assertThat(((SuiteApkInstaller) installApk).getTestsFileName())
.containsExactly(
new File("test.apk"), new File("test2.apk"), new File("test1.apk"));
// device file dependencies
- ITargetPreparer pushFile = config.getTargetPreparers().get(1);
+ ITargetPreparer pushFile = config.getTargetPreparers().get(3);
assertTrue(pushFile instanceof PushFilePreparer);
assertThat(((PushFilePreparer) pushFile).getPushSpecs(null))
.containsExactly(
@@ -145,6 +157,19 @@ public class ConfigurationYamlParserTest {
}
}
+ @Test
+ public void testParseConfig_notTargetPreparer() throws Exception {
+ try (InputStream res = readFromRes(YAML_TEST_CONFIG_2)) {
+ mParser.parse(mConfigDef, "source", res, false);
+ try {
+ mConfigDef.createConfiguration();
+ fail("Should have thrown an exception");
+ } catch (ConfigurationException expected) {
+
+ }
+ }
+ }
+
private InputStream readFromRes(String resourceFile) {
return getClass().getResourceAsStream(resourceFile);
}
diff --git a/tests/src/com/android/tradefed/device/TestDeviceTest.java b/tests/src/com/android/tradefed/device/TestDeviceTest.java
index e6007fd6f..f4e8aee3f 100644
--- a/tests/src/com/android/tradefed/device/TestDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/TestDeviceTest.java
@@ -4747,64 +4747,43 @@ public class TestDeviceTest extends TestCase {
}
/**
- * Test {@link TestDevice#doesFileExist(String)} when the file exists on an sdcard from another
- * user.
+ * Test {@link TestDevice#doesFileExist(String)} using content provider when the file is in
+ * external storage path.
*/
public void testDoesFileExists_sdcard() throws Exception {
- mTestDevice =
- new TestableTestDevice() {
- @Override
- public int getCurrentUser()
- throws DeviceNotAvailableException, DeviceRuntimeException {
- return 10;
- }
- };
- injectShellResponse("ls \"/storage/emulated/10/file\"", "file");
+ mTestDevice = createTestDevice();
+
+ TestableTestDevice spy = (TestableTestDevice) Mockito.spy(mTestDevice);
+ ContentProviderHandler cp = Mockito.mock(ContentProviderHandler.class);
+ doReturn(cp).when(spy).getContentProvider();
+
+ final String fakeFile = "/sdcard/file";
+ final String targetFilePath = "/storage/emulated/10/file";
+
+ doReturn("").when(spy).executeShellCommand(Mockito.contains("content query --user 10"));
+
EasyMock.replay(mMockIDevice);
- assertTrue(mTestDevice.doesFileExist("/sdcard/file"));
+ spy.doesFileExist(fakeFile);
EasyMock.verify(mMockIDevice);
+
+ verify(spy, times(1)).getContentProvider();
+ verify(cp, times(1)).doesFileExist(targetFilePath);
}
/** Push a file using the content provider. */
public void testPushFile_contentProvider() throws Exception {
- mTestDevice =
- new TestableTestDevice() {
- @Override
- public int getApiLevel() throws DeviceNotAvailableException {
- return 29;
- }
-
- @Override
- public int getCurrentUser()
- throws DeviceNotAvailableException, DeviceRuntimeException {
- return 10;
- }
-
- @Override
- public boolean isPackageInstalled(String packageName, String userId)
- throws DeviceNotAvailableException {
- return false;
- }
- };
+ mTestDevice = createTestDevice();
TestableTestDevice spy = (TestableTestDevice) Mockito.spy(mTestDevice);
+ setupContentProvider(spy);
+
final String fakeRemotePath = "/sdcard/";
File tmpFile = FileUtil.createTempFile("push", ".test");
- doReturn(null)
- .when(spy)
- .installPackage(Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean());
- CommandResult setLegacy = new CommandResult(CommandStatus.SUCCESS);
- doReturn(setLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops set"));
-
- CommandResult getLegacy = new CommandResult(CommandStatus.SUCCESS);
- getLegacy.setStdout("LEGACY_STORAGE: allow");
- doReturn(getLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops get"));
CommandResult writeContent = new CommandResult(CommandStatus.SUCCESS);
writeContent.setStdout("");
doReturn(writeContent)
.when(spy)
.executeShellV2Command(Mockito.contains("content write"), (File) Mockito.any());
- doReturn(null).when(spy).uninstallPackage(Mockito.eq("android.tradefed.contentprovider"));
EasyMock.replay(mMockIDevice);
try {
boolean res = spy.pushFile(tmpFile, fakeRemotePath);
@@ -4825,37 +4804,12 @@ public class TestDeviceTest extends TestCase {
/** Push a file using the content provider. */
public void testPushFile_contentProvider_notFound() throws Exception {
- mTestDevice =
- new TestableTestDevice() {
- @Override
- public int getApiLevel() throws DeviceNotAvailableException {
- return 29;
- }
-
- @Override
- public int getCurrentUser()
- throws DeviceNotAvailableException, DeviceRuntimeException {
- return 10;
- }
-
- @Override
- public boolean isPackageInstalled(String packageName, String userId)
- throws DeviceNotAvailableException {
- return false;
- }
- };
+ mTestDevice = createTestDevice();
TestableTestDevice spy = (TestableTestDevice) Mockito.spy(mTestDevice);
+ setupContentProvider(spy);
+
final String fakeRemotePath = "/sdcard/";
File tmpFile = FileUtil.createTempFile("push", ".test");
- doReturn(null)
- .when(spy)
- .installPackage(Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean());
- CommandResult setLegacy = new CommandResult(CommandStatus.SUCCESS);
- doReturn(setLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops set"));
-
- CommandResult getLegacy = new CommandResult(CommandStatus.SUCCESS);
- getLegacy.setStdout("LEGACY_STORAGE: allow");
- doReturn(getLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops get"));
CommandResult writeContent = new CommandResult(CommandStatus.SUCCESS);
writeContent.setStdout("");
@@ -4901,4 +4855,38 @@ public class TestDeviceTest extends TestCase {
EasyMock.eq(property)))
.andReturn(stubResult);
}
+
+ private void setupContentProvider(TestableTestDevice spy) throws Exception {
+ doReturn(null)
+ .when(spy)
+ .installPackage(Mockito.any(), Mockito.anyBoolean(), Mockito.anyBoolean());
+ CommandResult setLegacy = new CommandResult(CommandStatus.SUCCESS);
+ doReturn(setLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops set"));
+
+ CommandResult getLegacy = new CommandResult(CommandStatus.SUCCESS);
+ getLegacy.setStdout("LEGACY_STORAGE: allow");
+ doReturn(getLegacy).when(spy).executeShellV2Command(Mockito.contains("cmd appops get"));
+
+ doReturn(null).when(spy).uninstallPackage(Mockito.eq("android.tradefed.contentprovider"));
+ }
+
+ private TestableTestDevice createTestDevice() {
+ return new TestableTestDevice() {
+ @Override
+ public int getApiLevel() throws DeviceNotAvailableException {
+ return 29;
+ }
+
+ @Override
+ public int getCurrentUser() throws DeviceNotAvailableException, DeviceRuntimeException {
+ return 10;
+ }
+
+ @Override
+ public boolean isPackageInstalled(String packageName, String userId)
+ throws DeviceNotAvailableException {
+ return false;
+ }
+ };
+ }
}
diff --git a/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java b/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java
index e8d0219f6..18109d12a 100644
--- a/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDeviceTest.java
@@ -58,6 +58,7 @@ import org.mockito.Mockito;
import java.io.File;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@@ -323,6 +324,32 @@ public class RemoteAndroidVirtualDeviceTest {
/** Test {@link RemoteAndroidVirtualDevice#postInvocationTearDown(Throwable)}. */
@Test
public void testPostInvocationTearDown() throws Exception {
+ mTestDevice =
+ new TestableRemoteAndroidVirtualDevice() {
+ @Override
+ protected IRunUtil getRunUtil() {
+ return mMockRunUtil;
+ }
+
+ @Override
+ void createGceSshMonitor(
+ ITestDevice device,
+ IBuildInfo buildInfo,
+ HostAndPort hostAndPort,
+ TestDeviceOptions deviceOptions) {
+ // ignore
+ }
+
+ @Override
+ GceManager getGceHandler() {
+ return mGceHandler;
+ }
+
+ @Override
+ public DeviceDescriptor getDeviceDescriptor() {
+ return null;
+ }
+ };
mTestDevice.setTestLogger(mTestLogger);
EasyMock.expect(mMockStateMonitor.waitForDeviceNotAvailable(EasyMock.anyLong()))
.andReturn(true);
@@ -803,4 +830,83 @@ public class RemoteAndroidVirtualDeviceTest {
}
verifyMocks();
}
+
+ /**
+ * Run powerwash() but GceAvdInfo = null, RemoteAndroidVirtualDevice choose to throw exception.
+ */
+ @Test
+ public void testPowerwashNoAvdInfo() throws Exception {
+ final String expectedException = "Can not get GCE AVD Info. launch GCE first? [ : ]";
+ EasyMock.replay(mMockRunUtil, mMockIDevice);
+ try {
+ mTestDevice.powerwashGce();
+ fail("Should have thrown an exception");
+ } catch (TargetSetupError expected) {
+ assertEquals(expectedException, expected.getMessage());
+ }
+ EasyMock.verify(mMockRunUtil, mMockIDevice);
+ }
+
+ /** Test powerwash GCE command */
+ @Test
+ public void testPowerwashGce() throws Exception {
+ mTestDevice =
+ new TestableRemoteAndroidVirtualDevice() {
+ @Override
+ public IDevice getIDevice() {
+ return mMockIDevice;
+ }
+
+ @Override
+ GceManager getGceHandler() {
+ return mGceHandler;
+ }
+
+ @Override
+ void createGceSshMonitor(
+ ITestDevice device,
+ IBuildInfo buildInfo,
+ HostAndPort hostAndPort,
+ TestDeviceOptions deviceOptions) {
+ // ignore
+ }
+ };
+ String instanceUser = "user1";
+ IBuildInfo mMockBuildInfo = EasyMock.createMock(IBuildInfo.class);
+ OptionSetter setter = new OptionSetter(mTestDevice.getOptions());
+ setter.setOptionValue("instance-user", instanceUser);
+ String powerwashCommand = String.format("/home/%s/bin/powerwash_cvd", instanceUser);
+ String avdConnectHost = String.format("%s@127.0.0.1", instanceUser);
+ GceAvdInfo gceAvd =
+ new GceAvdInfo(
+ instanceUser, HostAndPort.fromHost("127.0.0.1"), null, GceStatus.SUCCESS);
+ doReturn(gceAvd).when(mGceHandler).startGce(null);
+ OutputStream stdout = null;
+ OutputStream stderr = null;
+ CommandResult powerwashCmdResult = new CommandResult(CommandStatus.SUCCESS);
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ EasyMock.eq(stdout),
+ EasyMock.eq(stderr),
+ EasyMock.eq("ssh"),
+ EasyMock.eq("-o"),
+ EasyMock.eq("UserKnownHostsFile=/dev/null"),
+ EasyMock.eq("-o"),
+ EasyMock.eq("StrictHostKeyChecking=no"),
+ EasyMock.eq("-o"),
+ EasyMock.eq("ServerAliveInterval=10"),
+ EasyMock.eq("-i"),
+ EasyMock.anyObject(),
+ EasyMock.eq(avdConnectHost),
+ EasyMock.eq(powerwashCommand)))
+ .andReturn(powerwashCmdResult);
+ EasyMock.expect(mMockStateMonitor.waitForDeviceAvailable(EasyMock.anyLong()))
+ .andReturn(mMockIDevice);
+ EasyMock.replay(mMockRunUtil, mMockIDevice);
+ // Launch GCE before powerwash.
+ mTestDevice.launchGce(mMockBuildInfo);
+ mTestDevice.powerwashGce();
+ EasyMock.verify(mMockRunUtil, mMockIDevice);
+ }
}
diff --git a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
index b0da7d41d..0570a5988 100644
--- a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
+++ b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
@@ -23,6 +23,7 @@ import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.util.CommandResult;
@@ -369,6 +370,36 @@ public class ContentProviderHandlerTest {
espacedUrl);
}
+ /** Test {@link ContentProviderHandler#doesFileExist(String)}. */
+ @Test
+ public void testDoesFileExist() throws Exception {
+ String devicePath = "path/somewhere/file.txt";
+
+ when(mMockDevice.getCurrentUser()).thenReturn(99);
+ when(mMockDevice.executeShellCommand(
+ "content query --user 99 --uri "
+ + ContentProviderHandler.createEscapedContentUri(devicePath)))
+ .thenReturn("");
+
+ assertTrue(mProvider.doesFileExist(devicePath));
+ }
+
+ /**
+ * Test {@link ContentProviderHandler#doesFileExist(String)} returns false when 'adb shell
+ * content query' returns no results.
+ */
+ @Test
+ public void testDoesFileExist_NotExists() throws Exception {
+ String devicePath = "path/somewhere/";
+
+ when(mMockDevice.getCurrentUser()).thenReturn(99);
+ when(mMockDevice.executeShellCommand(
+ "content query --user 99 --uri "
+ + ContentProviderHandler.createEscapedContentUri(devicePath)))
+ .thenReturn("No result found.\n");
+ assertFalse(mProvider.doesFileExist(devicePath));
+ }
+
@Test
public void testParseQueryResultRow() {
String row =
diff --git a/tests/src/com/android/tradefed/device/metric/BuddyInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/BuddyInfoMetricCollectorTest.java
deleted file mode 100644
index 27bf498a7..000000000
--- a/tests/src/com/android/tradefed/device/metric/BuddyInfoMetricCollectorTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2018 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.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link BuddyInfoMetricCollector}. */
-// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class BuddyInfoMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice device;
-
- @Spy BuddyInfoMetricCollector mBuddyInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mBuddyInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class));
-
- doReturn(new File("unusable-index-1"))
- .when(mBuddyInfoMetricCollector)
- .saveProcessOutput(any(ITestDevice.class), anyString(), anyString());
-
- doReturn(tempFolder.newFolder()).when(mBuddyInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- mBuddyInfoMetricCollector.collect(device, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("unusable-index-1"), eq(LogDataType.TEXT), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/BugreportzMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/BugreportzMetricCollectorTest.java
deleted file mode 100644
index 6b19eb491..000000000
--- a/tests/src/com/android/tradefed/device/metric/BugreportzMetricCollectorTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2018 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.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import java.util.Arrays;
-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.MockitoAnnotations;
-import org.mockito.Spy;
-
-/** Unit tests for {@link BugreportzMetricCollector}. */
-@RunWith(JUnit4.class)
-public class BugreportzMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestDevice mTestDevice;
-
- @Mock ITestInvocationListener mForwarder;
-
- @Spy BugreportzMetricCollector mBugreportzMetricCollector;
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- doReturn(Arrays.asList(mTestDevice)).when(mContext).getDevices();
-
- when(mTestDevice.logBugreport(anyString(), any(ITestInvocationListener.class)))
- .thenReturn(true);
-
- mBugreportzMetricCollector.init(mContext, mForwarder);
-
- when(mBugreportzMetricCollector.getFileSuffix()).thenReturn("1");
- }
-
- /** Tests successful collection of bugreport. */
- @Test
- public void testCollect_success() throws Exception {
- when(mTestDevice.logBugreport("bugreportz-1", mForwarder)).thenReturn(true);
-
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- mBugreportzMetricCollector.collect(mTestDevice, runData);
-
- verify(mTestDevice).logBugreport(eq("bugreport-1"), eq(mForwarder));
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/DumpHeapCollectorTest.java b/tests/src/com/android/tradefed/device/metric/DumpHeapCollectorTest.java
deleted file mode 100644
index 6a9cb5681..000000000
--- a/tests/src/com/android/tradefed/device/metric/DumpHeapCollectorTest.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2018 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.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-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.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-import com.android.tradefed.util.FileUtil;
-import com.google.common.truth.Truth;
-import java.io.File;
-import java.util.Arrays;
-import java.util.List;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-/** Unit tests for {@link DumpHeapCollector}. */
-@RunWith(JUnit4.class)
-public class DumpHeapCollectorTest {
-
- @Mock private IInvocationContext mContext;
-
- @Mock private ITestInvocationListener mListener;
-
- @Mock private ITestDevice mDevice;
-
- @Rule public TemporaryFolder folder = new TemporaryFolder();
-
- @Spy private DumpHeapCollector mDumpheapCollector;
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- when(mDevice.executeShellCommand("dumpsys meminfo -c | grep camera"))
- .thenReturn("proc,native,camera,21348,800,N/A,e\n");
-
- when(mDevice.executeShellCommand("dumpsys meminfo -c | grep maps"))
- .thenReturn("proc,native,maps,21349,900,N/A,e\n");
-
- when(mDevice.executeShellCommand("am dumpheap camera /data/local/tmp/camera_trigger.hprof"))
- .thenReturn("");
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), any(LogDataType.class), any(InputStreamSource.class));
- }
-
- @Test
- public void testTakeDumpheap_success() throws Exception {
- File mapsDumpheap1 = folder.newFile("maps1");
- File mapsDumpheap2 = folder.newFile("maps2");
-
- doReturn("1").when(mDumpheapCollector).getFileSuffix();
-
- when(mDevice.dumpHeap("maps", "/data/local/tmp/maps_trigger_1.hprof"))
- .thenReturn(mapsDumpheap1)
- .thenReturn(mapsDumpheap2);
-
- String fakeDumpheapOutput =
- "proc,native,maps,21349,900,N/A,e\nproc,native,camera,21350,800,N/A,e\n";
-
- List<File> files =
- mDumpheapCollector.takeDumpheap(mDevice, fakeDumpheapOutput, "maps", 850L);
-
- Truth.assertThat(files).containsExactly(mapsDumpheap1);
- }
-
- @Test
- public void testCollect_success() throws Exception {
- File tempFile1 = folder.newFile();
- File tempFile2 = folder.newFile();
- when(mDevice.dumpHeap(anyString(), anyString()))
- .thenReturn(tempFile1)
- .thenReturn(tempFile2);
-
- OptionSetter options = new OptionSetter(mDumpheapCollector);
-
- options.setOptionValue("dumpheap-thresholds", "camera", "700");
- options.setOptionValue("dumpheap-thresholds", "maps", "800");
-
- mDumpheapCollector.init(mContext, mListener);
-
- mDumpheapCollector.collect(mDevice, null);
-
- ArgumentCaptor<String> dataNameCaptor = ArgumentCaptor.forClass(String.class);
- ArgumentCaptor<LogDataType> dataTypeCaptor = ArgumentCaptor.forClass(LogDataType.class);
- ArgumentCaptor<InputStreamSource> inputCaptor =
- ArgumentCaptor.forClass(InputStreamSource.class);
-
- verify(mListener, times(2))
- .testLog(dataNameCaptor.capture(), dataTypeCaptor.capture(), inputCaptor.capture());
-
- // Assert that the correct filename was sent to testLog.
- Truth.assertThat(dataNameCaptor.getAllValues())
- .containsExactlyElementsIn(
- Arrays.asList(
- FileUtil.getBaseName(tempFile1.getName()),
- FileUtil.getBaseName(tempFile2.getName())));
-
- // Assert that the correct data type was sent to testLog.
- Truth.assertThat(dataTypeCaptor.getAllValues())
- .containsExactlyElementsIn(Arrays.asList(LogDataType.HPROF, LogDataType.HPROF));
- }
-
- @Test
- public void testCollectSuccess_thresholdTooHigh() throws Exception {
- File tempFile1 = folder.newFile();
- File tempFile2 = folder.newFile();
- when(mDevice.pullFile(anyString())).thenReturn(tempFile1).thenReturn(tempFile2);
-
- OptionSetter options = new OptionSetter(mDumpheapCollector);
-
- options.setOptionValue("dumpheap-thresholds", "camera", "7000");
- options.setOptionValue("dumpheap-thresholds", "maps", "8000");
-
- mDumpheapCollector.init(mContext, mListener);
-
- mDumpheapCollector.collect(mDevice, null);
-
- ArgumentCaptor<String> dataNameCaptor = ArgumentCaptor.forClass(String.class);
- ArgumentCaptor<LogDataType> dataTypeCaptor = ArgumentCaptor.forClass(LogDataType.class);
- ArgumentCaptor<InputStreamSource> inputCaptor =
- ArgumentCaptor.forClass(InputStreamSource.class);
-
- verify(mListener, times(0))
- .testLog(dataNameCaptor.capture(), dataTypeCaptor.capture(), inputCaptor.capture());
- }
-
- @Test
- public void testCollectNoError_processNotFound() throws Exception {
- // Make the meminfo dump not contain the heap info of fake_process.
- when(mDevice.executeShellCommand("dumpsys meminfo -c | grep fake_process")).thenReturn("");
-
- OptionSetter options = new OptionSetter(mDumpheapCollector);
- options.setOptionValue("dumpheap-thresholds", "fake_process", "7000");
-
- mDumpheapCollector.init(mContext, mListener);
-
- mDumpheapCollector.collect(mDevice, null);
-
- ArgumentCaptor<String> dataNameCaptor = ArgumentCaptor.forClass(String.class);
- ArgumentCaptor<LogDataType> dataTypeCaptor = ArgumentCaptor.forClass(LogDataType.class);
- ArgumentCaptor<InputStreamSource> inputCaptor =
- ArgumentCaptor.forClass(InputStreamSource.class);
-
- // Verify that no testLog calls were made.
- verify(mListener, times(0))
- .testLog(dataNameCaptor.capture(), dataTypeCaptor.capture(), inputCaptor.capture());
- }
-}
-
diff --git a/tests/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollectorTest.java
deleted file mode 100644
index cb051a600..000000000
--- a/tests/src/com/android/tradefed/device/metric/GraphicsStatsMetricCollectorTest.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Copyright (C) 2018 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.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link GraphicsStatsMetricCollector}. */
-//TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class GraphicsStatsMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice mDevice;
-
- @Spy GraphicsStatsMetricCollector mGfxInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mGfxInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), eq(LogDataType.GFX_INFO), any(InputStreamSource.class));
-
- doReturn(new File("graphics-1"))
- .when(mGfxInfoMetricCollector)
- .saveProcessOutput(any(ITestDevice.class), anyString(), anyString());
-
- doReturn(tempFolder.newFolder()).when(mGfxInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- mGfxInfoMetricCollector.collect(mDevice, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("graphics-1"), eq(LogDataType.GFX_INFO), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollectorTest.java
deleted file mode 100644
index 2547672ae..000000000
--- a/tests/src/com/android/tradefed/device/metric/IonHeapInfoMetricCollectorTest.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2018 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.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link IonHeapInfoMetricCollector}. */
-// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class IonHeapInfoMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice mDevice;
-
- @Spy IonHeapInfoMetricCollector mIonHeapInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mIonHeapInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class));
-
- doReturn(new File("ion-system-2"))
- .when(mIonHeapInfoMetricCollector)
- .saveProcessOutput(
- any(ITestDevice.class), eq("cat /d/ion/heaps/system"), anyString());
- doReturn(new File("ion-audio-1"))
- .when(mIonHeapInfoMetricCollector)
- .saveProcessOutput(
- any(ITestDevice.class), eq("cat /d/ion/heaps/audio"), anyString());
-
- doReturn(tempFolder.newFolder()).when(mIonHeapInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- mIonHeapInfoMetricCollector.collect(mDevice, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("ion-system-2"), eq(LogDataType.TEXT), any());
- verify(mListener).testLog(eq("ion-audio-1"), eq(LogDataType.TEXT), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java b/tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
index 164e536d3..db9f1e6db 100644
--- a/tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
@@ -347,6 +347,49 @@ public class JavaCodeCoverageCollectorTest {
mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
}
+ @Test
+ public void testRunningProcess_coverageFileNotDeleted() throws Exception {
+ enableJavaCoverage();
+
+ List<String> coverageFileList =
+ ImmutableList.of(
+ "/data/misc/trace/coverage1.ec",
+ "/data/misc/trace/coverage2.ec",
+ "/data/misc/trace/jacoco-123.mm.ec",
+ "/data/misc/trace/jacoco-456.mm.ec");
+ String psOutput =
+ "USER PID PPID VSZ RSS WCHAN PC S NAME\n"
+ + "bluetooth 123 1366 123 456 SyS_epoll+ 0 S com.android.bluetooth\n"
+ + "radio 890 1 7890 123 binder_io+ 0 S com.android.phone\n"
+ + "root 11 1234 567 890 binder_io+ 0 S not.a.java.package\n";
+
+ // Setup mocks.
+ mockCoverageFileOnDevice(DEVICE_PATH);
+
+ for (String additionalFile : coverageFileList) {
+ mockCoverageFileOnDevice(additionalFile);
+ }
+
+ doReturn("").when(mMockDevice).executeShellCommand("pm list packages -a");
+ doReturn(psOutput).when(mMockDevice).executeShellCommand("ps -e");
+ doReturn(String.join("\n", coverageFileList))
+ .when(mMockDevice)
+ .executeShellCommand("find /data/misc/trace -name '*.ec'");
+
+ // Simulate a test run.
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ Map<String, String> metric = new HashMap<>();
+ metric.put("coverageFilePath", DEVICE_PATH);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, TfMetricProtoUtil.upgradeConvert(metric));
+
+ // Verify the correct files were deleted and some files were not deleted.
+ verify(mMockDevice).deleteFile(coverageFileList.get(0));
+ verify(mMockDevice).deleteFile(coverageFileList.get(1));
+ verify(mMockDevice, never()).deleteFile(coverageFileList.get(2));
+ verify(mMockDevice).deleteFile(coverageFileList.get(3));
+ }
+
private void mockCoverageFileOnDevice(String devicePath)
throws IOException, DeviceNotAvailableException {
File coverageFile = folder.newFile(new File(devicePath).getName());
diff --git a/tests/src/com/android/tradefed/device/metric/MemInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/MemInfoMetricCollectorTest.java
deleted file mode 100644
index c56a081a8..000000000
--- a/tests/src/com/android/tradefed/device/metric/MemInfoMetricCollectorTest.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2018 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.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link MemInfoMetricCollector}. */
-// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class MemInfoMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice mDevice;
-
- @Spy MemInfoMetricCollector mMemInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mMemInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(
- anyString(), eq(LogDataType.COMPACT_MEMINFO), any(InputStreamSource.class));
-
- doReturn(new File("compact-meminfo-1"))
- .when(mMemInfoMetricCollector)
- .saveProcessOutput(any(ITestDevice.class), anyString(), anyString());
-
- doReturn(tempFolder.newFolder()).when(mMemInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
- when(mMemInfoMetricCollector.getFileSuffix()).thenReturn("1");
-
- mMemInfoMetricCollector.collect(mDevice, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("compact-meminfo-1"), eq(LogDataType.COMPACT_MEMINFO), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollectorTest.java
deleted file mode 100644
index 934874365..000000000
--- a/tests/src/com/android/tradefed/device/metric/PagetypeInfoMetricCollectorTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2018 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.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link PagetypeInfoMetricCollector}. */
-// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class PagetypeInfoMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice mDevice;
-
- @Spy PagetypeInfoMetricCollector mPagetypeInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mPagetypeInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class));
-
- doReturn(new File("pagetypeinfo-1"))
- .when(mPagetypeInfoMetricCollector)
- .saveProcessOutput(any(ITestDevice.class), anyString(), anyString());
-
- doReturn(tempFolder.newFolder()).when(mPagetypeInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
- when(mPagetypeInfoMetricCollector.getFileSuffix()).thenReturn("1");
-
- mPagetypeInfoMetricCollector.collect(mDevice, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("pagetypeinfo-1"), eq(LogDataType.TEXT), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
index a9807b0d3..9d131c5c1 100644
--- a/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/PerfettoPullerMetricCollectorTest.java
@@ -16,6 +16,7 @@
package com.android.tradefed.device.metric;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.times;
@@ -120,7 +121,45 @@ public class PerfettoPullerMetricCollectorTest {
assertTrue("Trace duration metrics not available but expected.",
currentMetrics.get("perfetto_trace_extractor_runtime").getMeasurements()
.getSingleDouble() >= 0);
- assertTrue("Trace file size metric is not available.",
+ assertNull("Trace duration metrics not available but expected.",
+ currentMetrics.get("perfetto_trace_file_size_bytes"));
+ }
+
+ @Test
+ public void testProcessingFlowWithFileSizeMetric() throws Exception {
+
+ OptionSetter setter = new OptionSetter(mPerfettoMetricCollector);
+ setter.setOptionValue("pull-pattern-keys", "perfettofile");
+ setter.setOptionValue("perfetto-binary-path", "trx");
+ setter.setOptionValue("convert-metric-file", "false");
+ setter.setOptionValue("collect-perfetto-file-size", "true");
+ HashMap<String, Metric> currentMetrics = new HashMap<>();
+ currentMetrics.put("perfettofile", TfMetricProtoUtil.stringToMetric("/data/trace.pb"));
+ Mockito.when(mMockDevice.pullFile(Mockito.eq("/data/trace.pb")))
+ .thenReturn(new File("trace"));
+
+ TestDescription testDesc = new TestDescription("xyz", "abc");
+ CommandResult cr = new CommandResult();
+ cr.setStatus(CommandStatus.SUCCESS);
+ cr.setStdout("abc:efg");
+
+ Mockito.doReturn(cr).when(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+ Mockito.any(), Mockito.any(), Mockito.any());
+
+ mPerfettoMetricCollector.testStarted(testDesc);
+ mPerfettoMetricCollector.testEnded(testDesc, currentMetrics);
+
+ Mockito.verify(mPerfettoMetricCollector).runHostCommand(Mockito.anyLong(),
+ Mockito.any(), Mockito.any(), Mockito.any());
+ Mockito.verify(mMockListener)
+ .testLog(Mockito.eq("trace"), Mockito.eq(LogDataType.PERFETTO), Mockito.any());
+ assertTrue("Expected two metrics that includes success status",
+ currentMetrics.get("perfetto_trace_extractor_status").getMeasurements()
+ .getSingleString().equals("1"));
+ assertTrue("Trace duration metrics not available but expected.",
+ currentMetrics.get("perfetto_trace_extractor_runtime").getMeasurements()
+ .getSingleDouble() >= 0);
+ assertTrue("Trace file size metric is not available in the final metrics.",
currentMetrics.get("perfetto_trace_file_size_bytes").getMeasurements()
.getSingleDouble() >= 0);
}
@@ -331,3 +370,4 @@ public class PerfettoPullerMetricCollectorTest {
}
}
+
diff --git a/tests/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollectorTest.java
deleted file mode 100644
index b66138f76..000000000
--- a/tests/src/com/android/tradefed/device/metric/ProcessMaxMemoryCollectorTest.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2018 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.junit.Assert.assertTrue;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-
-import java.util.Collections;
-import java.util.HashMap;
-
-/** Unit tests for {@link ProcessMaxMemoryCollector}. */
-@RunWith(JUnit4.class)
-public class ProcessMaxMemoryCollectorTest {
-
- private ProcessMaxMemoryCollector mCollector;
- private IInvocationContext mContext;
- private ITestDevice mDevice;
- private ITestInvocationListener mListener;
-
- private static final String TEST_INPUT =
- "time,28506638,177086152\n"
- + "4,938,system_server,11,22,N/A,44,0,0,N/A,0,0,0,N/A,0,27613,14013,176602,"
- + "218228,0,0,122860,122860,1512,1412,5740,8664,0,0,154924,154924,27568,"
- + "13972,11916,53456,0,0,123008,123008,0,0,0,0,0,0,0,0,Dalvik Other,3662,0,"
- + "104,0,3660,0,0,0,Stack,1576,0,8,0,1576,0,0,0,Cursor,0,0,0,0,0,0,0,0,"
- + "Ashmem,156,0,20,0,148,0,0,0,Gfx dev,100,0,48,0,76,0,0,0,Other dev,116,0,"
- + "164,0,0,96,0,0,.so mmap,7500,2680,3984,21864,904,2680,0,0,.jar mmap,0,0,0,"
- + "0,0,0,0,0,.apk mmap,72398,71448,0,11736,0,71448,0,0,.ttf mmap,0,0,0,0,0,0,"
- + "0,0,.dex mmap,76874,46000,0,83644,40,46000,0,0,.oat mmap,8127,2684,64,"
- + "26652,0,2684,0,0,.art mmap,1991,48,972,10004,1544,48,0,0,Other mmap,137,0,"
- + "44,1024,4,52,0,0,EGL mtrack,0,0,0,0,0,0,0,0,GL mtrack,111,222,333,444,555,"
- + "666,777,888,";
-
- @Before
- public void setup() throws Exception {
- mCollector = new ProcessMaxMemoryCollector();
- mContext = mock(IInvocationContext.class);
- mDevice = mock(ITestDevice.class);
- when(mContext.getDevices()).thenReturn(Collections.singletonList(mDevice));
- mListener = mock(ITestInvocationListener.class);
- mCollector.init(mContext, mListener);
- OptionSetter setter = new OptionSetter(mCollector);
- setter.setOptionValue("memory-usage-process-name", "system_server");
- }
-
- @Test
- public void testCollector() throws Exception {
- when(mDevice.executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server")))
- .thenReturn(TEST_INPUT);
-
- DeviceMetricData data = new DeviceMetricData(mContext);
- mCollector.onStart(data);
- mCollector.collect(mDevice, data);
- mCollector.onEnd(data);
-
- verify(mDevice).executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server"));
-
- HashMap<String, Metric> results = new HashMap<>();
- data.addToMetrics(results);
- assertEquals(218228, results.get("MAX_PSS#system_server").getMeasurements().getSingleInt());
- assertEquals(53456, results.get("MAX_USS#system_server").getMeasurements().getSingleInt());
- }
-
- @Test
- public void testCollectorNoProcess() throws Exception {
- when(mDevice.executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server")))
- .thenReturn("No process found for: system_server");
-
- DeviceMetricData data = new DeviceMetricData(mContext);
- mCollector.onStart(data);
- mCollector.collect(mDevice, data);
- mCollector.onEnd(data);
-
- verify(mDevice).executeShellCommand(Mockito.eq("dumpsys meminfo --checkin system_server"));
-
- HashMap<String, Metric> results = new HashMap<>();
- data.addToMetrics(results);
- assertTrue(results.isEmpty());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollectorTest.java
deleted file mode 100644
index 13605f388..000000000
--- a/tests/src/com/android/tradefed/device/metric/ScheduleMultipleDeviceMetricCollectorTest.java
+++ /dev/null
@@ -1,284 +0,0 @@
-/*
- * Copyright (C) 2017 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.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.util.RunUtil;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-/** Unit tests for {@link ScheduleMultipleDeviceMetricCollector}. */
-@RunWith(JUnit4.class)
-public class ScheduleMultipleDeviceMetricCollectorTest {
- @Rule public final TemporaryFolder folder = new TemporaryFolder();
- @Mock private ITestDevice mTestDevice;
- @Mock private ITestInvocationListener mMockListener;
- @Spy private ScheduleMultipleDeviceMetricCollector mMultipleMetricCollector;
-
- private IInvocationContext mContext;
-
- static class TestMeminfoCollector extends ScheduledDeviceMetricCollector {
- private int mInternalCounter = 0;
- private String key = "meminfo";
-
- TestMeminfoCollector() {
- setTag("meminfoInterval");
- }
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData)
- throws InterruptedException {
- mInternalCounter++;
- runData.addMetricForDevice(
- device,
- key + mInternalCounter,
- Metric.newBuilder()
- .setMeasurements(
- Measurements.newBuilder()
- .setSingleString("value" + mInternalCounter)));
- }
- }
-
- static class TestJankinfoCollector extends ScheduledDeviceMetricCollector {
- private int mInternalCounter = 0;
- private String key = "jankinfo";
-
- TestJankinfoCollector() {
- setTag("jankInterval");
- }
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData)
- throws InterruptedException {
- mInternalCounter++;
- runData.addMetricForDevice(
- device,
- key + mInternalCounter,
- Metric.newBuilder()
- .setMeasurements(
- Measurements.newBuilder()
- .setSingleString("value" + mInternalCounter)));
- }
- }
-
- static class TestFragmentationCollector extends ScheduledDeviceMetricCollector {
- private int mInternalCounter = 0;
- private String key = "fragmentation";
-
- TestFragmentationCollector() {
- setTag("fragmentationInterval");
- }
-
- @Override
- public void collect(ITestDevice device, DeviceMetricData runData)
- throws InterruptedException {
- mInternalCounter++;
- runData.addMetricForDevice(
- device,
- key + mInternalCounter,
- Metric.newBuilder()
- .setMeasurements(
- Measurements.newBuilder()
- .setSingleString("value" + mInternalCounter)));
- }
- }
-
- @Before
- public void setUp() throws Exception {
- MockitoAnnotations.initMocks(this);
- mContext = new InvocationContext();
- mContext.addAllocatedDevice("test device", mTestDevice);
- }
-
- @Test
- public void testMultipleMetricCollector_success() throws Exception {
- OptionSetter setter = new OptionSetter(mMultipleMetricCollector);
-
- // Set up the metric collection storage path.
- File metricStoragePath = folder.newFolder();
- setter.setOptionValue("metric-storage-path", metricStoragePath.toString());
-
- // Set up the intervals.
- Map<String, Long> intervals = new HashMap<>();
- intervals.put("meminfoInterval", 100L);
- intervals.put("fragmentationInterval", 100L);
- intervals.put("jankInterval", 100L);
- for (String key : intervals.keySet()) {
- setter.setOptionValue(
- "metric-collection-intervals", key, intervals.get(key).toString());
- }
-
- // Request the collectors.
- List<String> classnames = new ArrayList<>();
- classnames.add(TestMeminfoCollector.class.getName());
- classnames.add(TestJankinfoCollector.class.getName());
- classnames.add(TestFragmentationCollector.class.getName());
- for (String key : classnames) {
- setter.setOptionValue("metric-collector-command-classes", key);
- }
-
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- // Start the tests.
- HashMap<String, Metric> metrics = new HashMap<>();
- mMultipleMetricCollector.init(mContext, mMockListener);
- try {
- mMultipleMetricCollector.onTestRunStart(runData);
- RunUtil.getDefault().sleep(500);
- } finally {
- mMultipleMetricCollector.onTestRunEnd(runData, metrics);
- }
-
- // We give it 500msec to run and 100msec interval we should easily have at least run all the
- // metrics once.
- // assert that the metrics contains filenames of all the collected metrics.
- HashMap<String, Metric> metricsCollected = new HashMap<>();
- runData.addToMetrics(metricsCollected);
-
- assertTrue(metricsCollected.containsKey("jankinfo1"));
- assertTrue(metricsCollected.containsKey("meminfo1"));
- assertTrue(metricsCollected.containsKey("fragmentation1"));
- }
-
- @Test
- public void testMultipleMetricCollector_noFailureEvenIfNoCollectorRequested() throws Exception {
- HashMap<String, Metric> metrics = new HashMap<>();
- mMultipleMetricCollector.init(mContext, mMockListener);
-
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- try {
- mMultipleMetricCollector.onTestRunStart(runData);
- RunUtil.getDefault().sleep(500);
- } finally {
- mMultipleMetricCollector.onTestRunEnd(runData, metrics);
- }
-
- // No metrics should have been collected.
- HashMap<String, Metric> metricsCollected = new HashMap<>();
- runData.addToMetrics(metricsCollected);
-
- assertEquals(0, metricsCollected.size());
- }
-
- /** Test that if a specified collector does not exists, we ignore it and proceed. */
- @Test
- public void testMultipleMetricCollector_collectorNotFound() throws Exception {
- OptionSetter setter = new OptionSetter(mMultipleMetricCollector);
-
- // Set up the metric collection storage path.
- File metricStoragePath = folder.newFolder();
- setter.setOptionValue("metric-storage-path", metricStoragePath.toString());
-
- // Set up the intervals.
- Map<String, Long> intervals = new HashMap<>();
- intervals.put("meminfoInterval", 100L);
- for (String key : intervals.keySet()) {
- setter.setOptionValue(
- "metric-collection-intervals", key, intervals.get(key).toString());
- }
-
- // Request the collectors.
- List<String> classnames = new ArrayList<>();
- classnames.add(TestMeminfoCollector.class.getName());
- classnames.add("this.does.not.exists.collector");
- for (String key : classnames) {
- setter.setOptionValue("metric-collector-command-classes", key);
- }
-
- HashMap<String, Metric> metrics = new HashMap<>();
- mMultipleMetricCollector.init(mContext, mMockListener);
-
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- try {
- mMultipleMetricCollector.onTestRunStart(runData);
- RunUtil.getDefault().sleep(500);
- } finally {
- mMultipleMetricCollector.onTestRunEnd(runData, metrics);
- }
-
- // No metrics should have been collected.
- HashMap<String, Metric> metricsCollected = new HashMap<>();
- runData.addToMetrics(metricsCollected);
-
- assertTrue(metricsCollected.containsKey("meminfo1"));
- }
-
- @Test
- public void testMultipleMetricCollector_failsForNonNegativeInterval() throws Exception {
- String expectedStderr =
- "class com.android.tradefed.device.metric."
- + "ScheduleMultipleDeviceMetricCollectorTest$TestJankinfoCollector expects "
- + "a non negative interval.";
-
- OptionSetter setter = new OptionSetter(mMultipleMetricCollector);
-
- // Set up the metric collection storage path.
- setter.setOptionValue("metric-storage-path", folder.newFolder().toString());
-
- // Set up the interval.
- Map<String, Long> intervals = new HashMap<>();
- intervals.put("jankInterval", -100L);
- for (String key : intervals.keySet()) {
- setter.setOptionValue(
- "metric-collection-intervals", key, intervals.get(key).toString());
- }
-
- // Set up the classname.
- List<String> classnames = new ArrayList<>();
- classnames.add(TestJankinfoCollector.class.getName());
- for (String key : classnames) {
- setter.setOptionValue("metric-collector-command-classes", key);
- }
-
- DeviceMetricData runData = new DeviceMetricData(mContext);
-
- // Start the tests, which should fail with the expected error message.
- mMultipleMetricCollector.init(mContext, mMockListener);
-
- try {
- mMultipleMetricCollector.onTestRunStart(runData);
- fail("Should throw illegal argument exception in case of negative intervals.");
- } catch (IllegalArgumentException e) {
- assertEquals(expectedStderr, e.getMessage());
- }
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java
deleted file mode 100644
index 1f23a433e..000000000
--- a/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * Copyright (C) 2017 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.assertTrue;
-import static org.mockito.Mockito.mock;
-
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.util.RunUtil;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mockito;
-
-import java.util.HashMap;
-import java.util.Map;
-
-/** Unit tests for {@link ScheduledDeviceMetricCollector}. */
-@RunWith(JUnit4.class)
-public class ScheduledDeviceMetricCollectorTest {
- private Map<String, ITestDevice> mDevicesWithNames = new HashMap<>();
-
- public static class TestableAsyncTimer extends ScheduledDeviceMetricCollector {
- private int mInternalCounter = 0;
-
- @Override
- void collect(ITestDevice device, DeviceMetricData runData) throws InterruptedException {
- mInternalCounter++;
- runData.addMetricForDevice(
- device,
- "key" + mInternalCounter,
- Metric.newBuilder()
- .setMeasurements(
- Measurements.newBuilder()
- .setSingleString("value" + mInternalCounter)));
- }
- }
-
- private TestableAsyncTimer mBase;
- private IInvocationContext mContext;
- private ITestInvocationListener mMockListener;
-
- @Before
- public void setUp() {
- mBase = new TestableAsyncTimer();
- mContext = new InvocationContext();
- mMockListener = Mockito.mock(ITestInvocationListener.class);
- }
-
- /** Test the periodic run of the collector once testRunStarted has been called. */
- @Test
- public void testSetupAndPeriodicRunSingleDevice() throws Exception {
- // Setup the context with the devices.
- mDevicesWithNames.put("test device 1", mock(ITestDevice.class));
- mContext.addAllocatedDevice(mDevicesWithNames);
-
- OptionSetter setter = new OptionSetter(mBase);
- // 100 ms interval
- setter.setOptionValue("interval", "100");
- HashMap<String, Metric> metrics = new HashMap<>();
- mBase.init(mContext, mMockListener);
- try {
- mBase.testRunStarted("testRun", 1);
- RunUtil.getDefault().sleep(500);
- } finally {
- mBase.testRunEnded(0l, metrics);
- }
- // We give it 500msec to run and 100msec interval we should easily have at least three
- // iterations
- assertTrue(metrics.containsKey("key1"));
- assertTrue(metrics.containsKey("key2"));
- assertTrue(metrics.containsKey("key3"));
- }
-
- /**
- * Test the periodic run of the collector on multiple devices once testRunStarted has been
- * called.
- */
- @Test
- public void testSetupAndPeriodicRunMultipleDevices() throws Exception {
- // Setup the context with the devices.
- mDevicesWithNames.put("test device 1", mock(ITestDevice.class));
- mDevicesWithNames.put("test device 2", mock(ITestDevice.class));
- mContext.addAllocatedDevice(mDevicesWithNames);
-
- OptionSetter setter = new OptionSetter(mBase);
- // 100 ms interval
- setter.setOptionValue("interval", "100");
- HashMap<String, Metric> metrics = new HashMap<>();
- mBase.init(mContext, mMockListener);
- try {
- mBase.testRunStarted("testRun", 1);
- RunUtil.getDefault().sleep(500);
- } finally {
- mBase.testRunEnded(0l, metrics);
- }
- // We give it 500msec to run and 100msec interval we should easily have at least two
- // iterations one for each device. The order of execution is arbitrary so check for prefix
- // only.
- assertTrue(metrics.keySet().stream().anyMatch(key -> key.startsWith("{test device 1}")));
- assertTrue(metrics.keySet().stream().anyMatch(key -> key.startsWith("{test device 2}")));
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/TemperatureCollectorTest.java b/tests/src/com/android/tradefed/device/metric/TemperatureCollectorTest.java
deleted file mode 100644
index a4c855024..000000000
--- a/tests/src/com/android/tradefed/device/metric/TemperatureCollectorTest.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright (C) 2018 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.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
-import com.android.tradefed.result.ITestInvocationListener;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Collections;
-import java.util.HashMap;
-
-/** Unit tests for {@link TemperatureCollector}. */
-@RunWith(JUnit4.class)
-public class TemperatureCollectorTest {
-
- private TemperatureCollector mCollector;
- private IInvocationContext mContext;
- private ITestDevice mDevice;
- private ITestInvocationListener mListener;
-
- @Before
- public void setup() throws Exception {
- mCollector = new TemperatureCollector();
- mContext = mock(IInvocationContext.class);
- mDevice = mock(ITestDevice.class);
- when(mDevice.isAdbRoot()).thenReturn(true);
- when(mContext.getDevices()).thenReturn(Collections.singletonList(mDevice));
- mListener = mock(ITestInvocationListener.class);
- mCollector.init(mContext, mListener);
- OptionSetter setter = new OptionSetter(mCollector);
- setter.setOptionValue(
- "device-temperature-file-path", "/sys/class/hwmon/hwmon1/device/msm_therm");
- }
-
- @Test
- public void testCollector() throws Exception {
- when(mDevice.executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm")))
- .thenReturn("Result:32 Raw:7e51", "Result:22 Raw:7b51");
-
- DeviceMetricData data = new DeviceMetricData(mContext);
- mCollector.onStart(data);
- mCollector.collect(mDevice, data);
- mCollector.collect(mDevice, data);
- mCollector.onEnd(data);
-
- verify(mDevice, times(2))
- .executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm"));
-
- HashMap<String, Metric> results = new HashMap<>();
- data.addToMetrics(results);
- assertEquals(32D, results.get("max_temperature").getMeasurements().getSingleDouble(), 0);
- assertEquals(22D, results.get("min_temperature").getMeasurements().getSingleDouble(), 0);
- }
-
- @Test
- public void testCollectorNoData() throws Exception {
- when(mDevice.executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm")))
- .thenReturn(
- "cat: /sys/class/hwmon/hwmon1/device/msm_therm: No such file or directory");
-
- DeviceMetricData data = new DeviceMetricData(mContext);
- mCollector.onStart(data);
- mCollector.collect(mDevice, data);
- mCollector.onEnd(data);
-
- verify(mDevice).executeShellCommand(eq("cat /sys/class/hwmon/hwmon1/device/msm_therm"));
-
- HashMap<String, Metric> results = new HashMap<>();
- data.addToMetrics(results);
- assertTrue(results.isEmpty());
- }
-}
diff --git a/tests/src/com/android/tradefed/device/metric/TraceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/TraceMetricCollectorTest.java
deleted file mode 100644
index ffb3727c5..000000000
--- a/tests/src/com/android/tradefed/device/metric/TraceMetricCollectorTest.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * Copyright (C) 2018 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.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.result.ITestInvocationListener;
-import com.android.tradefed.result.InputStreamSource;
-import com.android.tradefed.result.LogDataType;
-
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-
-import java.io.File;
-
-/** Unit tests for {@link TraceMetricCollector}. */
-// TODO(b/71868090): Consolidate all the individual metric collector tests into one common tests.
-@RunWith(JUnit4.class)
-public class TraceMetricCollectorTest {
- @Mock IInvocationContext mContext;
-
- @Mock ITestInvocationListener mListener;
-
- @Mock ITestDevice mDevice;
-
- @Spy TraceMetricCollector mTraceInfoMetricCollector;
-
- @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
-
- @Before
- public void setup() throws Exception {
- MockitoAnnotations.initMocks(this);
-
- mTraceInfoMetricCollector.init(mContext, mListener);
-
- doNothing()
- .when(mListener)
- .testLog(anyString(), eq(LogDataType.TEXT), any(InputStreamSource.class));
-
- doReturn(new File("trace-1"))
- .when(mTraceInfoMetricCollector)
- .saveProcessOutput(any(ITestDevice.class), anyString(), anyString());
-
- doReturn(tempFolder.newFolder()).when(mTraceInfoMetricCollector).createTempDir();
- }
-
- @Test
- public void testCollect() throws Exception {
- DeviceMetricData runData = new DeviceMetricData(mContext);
- when(mTraceInfoMetricCollector.getFileSuffix()).thenReturn("1");
-
- mTraceInfoMetricCollector.collect(mDevice, runData);
-
- // Verify that we logged the metric file.
- verify(mListener).testLog(eq("trace-1"), eq(LogDataType.TEXT), any());
- }
-}
diff --git a/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java b/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
index dfb8b951c..65e7c0611 100644
--- a/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
+++ b/tests/src/com/android/tradefed/invoker/SandboxedInvocationExecutionTest.java
@@ -16,6 +16,8 @@
package com.android.tradefed.invoker;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -425,4 +427,26 @@ public class SandboxedInvocationExecutionTest {
FileUtil.deleteFile(buildFile);
}
}
+
+ @Test
+ public void testBuildInfo_testTag() throws Exception {
+ IBuildInfo info = new BuildInfo();
+ assertEquals("stub", info.getTestTag());
+ File testsDir = FileUtil.createTempDir("doesnt_matter_testsdir");
+ try {
+ info.setFile(BuildInfoFileKey.TESTDIR_IMAGE, testsDir, "tests");
+ mContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, info);
+ mConfig.getCommandOptions().setTestTag("test");
+ TestInformation testInfo =
+ TestInformation.newBuilder().setInvocationContext(mContext).build();
+ assertNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY));
+ mExecution.fetchBuild(testInfo, mConfig, null, null);
+ // Build test tag was updated
+ assertEquals("test", info.getTestTag());
+ // Execution file was back filled
+ assertNotNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY));
+ } finally {
+ FileUtil.recursiveDelete(testsDir);
+ }
+ }
}
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
index b66ba56b4..768ae2c5d 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationMultiTest.java
@@ -174,15 +174,17 @@ public class TestInvocationMultiTest {
EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger);
EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc);
mMockLogger.init();
+ EasyMock.expectLastCall().times(2);
EasyMock.expect(mMockLogger.getLog())
.andReturn(new ByteArrayInputStreamSource("fake".getBytes()));
mMockLogger.closeLog();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
mMockLogRegistry.registerLogger(mMockLogger);
+ EasyMock.expectLastCall().times(2);
mMockLogRegistry.dumpToGlobalLog(mMockLogger);
mMockLogRegistry.unregisterLogger();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty");
EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions());
@@ -270,15 +272,17 @@ public class TestInvocationMultiTest {
EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger);
EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc);
mMockLogger.init();
+ EasyMock.expectLastCall().times(2);
EasyMock.expect(mMockLogger.getLog())
.andReturn(new ByteArrayInputStreamSource("fake".getBytes()));
mMockLogger.closeLog();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
mMockLogRegistry.registerLogger(mMockLogger);
+ EasyMock.expectLastCall().times(2);
mMockLogRegistry.dumpToGlobalLog(mMockLogger);
mMockLogRegistry.unregisterLogger();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty");
EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions());
@@ -352,15 +356,17 @@ public class TestInvocationMultiTest {
EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger);
EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc);
mMockLogger.init();
+ EasyMock.expectLastCall().times(2);
EasyMock.expect(mMockLogger.getLog())
.andReturn(new ByteArrayInputStreamSource("fake".getBytes()));
mMockLogger.closeLog();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
mMockLogRegistry.registerLogger(mMockLogger);
+ EasyMock.expectLastCall().times(2);
mMockLogRegistry.dumpToGlobalLog(mMockLogger);
mMockLogRegistry.unregisterLogger();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty");
EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions());
@@ -442,15 +448,17 @@ public class TestInvocationMultiTest {
EasyMock.expect(mMockConfig.getLogOutput()).andStubReturn(mMockLogger);
EasyMock.expect(mMockConfig.getConfigurationDescription()).andReturn(mConfigDesc);
mMockLogger.init();
+ EasyMock.expectLastCall().times(2);
EasyMock.expect(mMockLogger.getLog())
.andReturn(new ByteArrayInputStreamSource("fake".getBytes()));
mMockLogger.closeLog();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
mMockLogRegistry.registerLogger(mMockLogger);
+ EasyMock.expectLastCall().times(2);
mMockLogRegistry.dumpToGlobalLog(mMockLogger);
mMockLogRegistry.unregisterLogger();
- EasyMock.expectLastCall().times(2);
+ EasyMock.expectLastCall().times(3);
EasyMock.expect(mMockConfig.getCommandLine()).andStubReturn("empty");
EasyMock.expect(mMockConfig.getCommandOptions()).andStubReturn(new CommandOptions());
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
index f8c4eb287..dc53f526b 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
@@ -412,11 +412,6 @@ public class TestInvocationTest {
setupMockFailureListeners(exception);
setupInvoke();
- EasyMock.reset(mMockLogger, mMockLogRegistry);
- mMockLogRegistry.registerLogger(mMockLogger);
- mMockLogger.init();
- mMockLogger.closeLog();
- mMockLogRegistry.unregisterLogger();
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
CommandOptions cmdOptions = new CommandOptions();
final String expectedTestTag = "TEST_TAG";
@@ -449,13 +444,6 @@ public class TestInvocationTest {
setupMockFailureListenersAny(
new BuildRetrievalError("fake", InfraErrorIdentifier.ARTIFACT_DOWNLOAD_ERROR),
true);
-
- EasyMock.reset(mMockLogger, mMockLogRegistry);
- mMockLogRegistry.registerLogger(mMockLogger);
- mMockLogger.init();
- mMockLogger.closeLog();
- mMockLogRegistry.unregisterLogger();
-
EasyMock.expect(mMockLogger.getLog()).andReturn(EMPTY_STREAM_SOURCE);
EasyMock.expect(mMockDevice.getLogcat()).andReturn(EMPTY_STREAM_SOURCE).times(2);
mMockDevice.clearLogcat();
@@ -484,12 +472,6 @@ public class TestInvocationTest {
"No build found to test.", InfraErrorIdentifier.ARTIFACT_NOT_FOUND),
true);
- EasyMock.reset(mMockLogger, mMockLogRegistry);
- mMockLogRegistry.registerLogger(mMockLogger);
- mMockLogger.init();
- mMockLogger.closeLog();
- mMockLogRegistry.unregisterLogger();
-
EasyMock.expect(mMockLogger.getLog()).andReturn(EMPTY_STREAM_SOURCE);
EasyMock.expect(mMockDevice.getLogcat()).andReturn(EMPTY_STREAM_SOURCE).times(2);
mMockDevice.clearLogcat();
@@ -520,13 +502,6 @@ public class TestInvocationTest {
"No build found to test.", InfraErrorIdentifier.ARTIFACT_NOT_FOUND),
true, /* don't expect host log */
false);
-
- EasyMock.reset(mMockLogger, mMockLogRegistry);
- mMockLogRegistry.registerLogger(mMockLogger);
- mMockLogger.init();
- mMockLogger.closeLog();
- EasyMock.expectLastCall().times(2);
-
IRemoteTest test = EasyMock.createMock(IRemoteTest.class);
mStubConfiguration.setTest(test);
// Host log fails to report
@@ -537,7 +512,7 @@ public class TestInvocationTest {
Capture<IBuildInfo> captured = new Capture<>();
mMockBuildProvider.cleanUp(EasyMock.capture(captured));
mMockLogRegistry.unregisterLogger();
- EasyMock.expectLastCall().times(2);
+ mMockLogger.closeLog();
mMockLogRegistry.dumpToGlobalLog(mMockLogger);
replayMocks(test, mockRescheduler);
mTestInvocation.invoke(mStubInvocationMetadata, mStubConfiguration, mockRescheduler);
@@ -1149,9 +1124,12 @@ public class TestInvocationTest {
mMockTestListener.invocationFailed(EasyMock.<FailureDescription>anyObject());
mMockSummaryListener.invocationFailed(EasyMock.<FailureDescription>anyObject());
} else {
+ FailureStatus failureStatus = FailureStatus.INFRA_FAILURE;
+ if (throwable instanceof BuildError) {
+ failureStatus = FailureStatus.DEPENDENCY_ISSUE;
+ }
FailureDescription failure =
- FailureDescription.create(
- throwable.getMessage(), FailureStatus.INFRA_FAILURE)
+ FailureDescription.create(throwable.getMessage(), failureStatus)
.setCause(throwable);
if (throwable instanceof BuildRetrievalError) {
failure.setActionInProgress(ActionInProgress.FETCHING_ARTIFACTS);
diff --git a/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java b/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java
index 7ce1e0de3..d7a9b683a 100644
--- a/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java
+++ b/tests/src/com/android/tradefed/invoker/logger/InvocationLocalTest.java
@@ -89,7 +89,7 @@ public final class InvocationLocalTest {
Object value0 = invocation(() -> local.get());
Object value1 = invocation(() -> local.get());
- assertThat(value0).isNotSameAs(value1);
+ assertThat(value0).isNotSameInstanceAs(value1);
}
/**
diff --git a/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java b/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java
index 91b8e7198..47a791f2b 100644
--- a/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java
+++ b/tests/src/com/android/tradefed/invoker/shard/StrictShardHelperTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
import com.android.tradefed.build.BuildInfo;
import com.android.tradefed.build.StubBuildProvider;
@@ -47,6 +48,7 @@ import com.android.tradefed.testtype.StubTest;
import com.android.tradefed.testtype.suite.ITestSuite;
import com.android.tradefed.util.FileUtil;
+import java.util.Arrays;
import org.easymock.EasyMock;
import org.junit.Assert;
import org.junit.Before;
@@ -256,11 +258,36 @@ public class StrictShardHelperTest {
}
}
+ public class FakeStrictShardHelper extends StrictShardHelper {
+ List<IRemoteTest> fakeModules = new ArrayList<>();
+
+ public FakeStrictShardHelper(List<IRemoteTest> modules) {
+ fakeModules.addAll(modules);
+ }
+
+ @Override
+ protected List<List<IRemoteTest>> splitTests(List<IRemoteTest> fullList, int shardCount) {
+ List<List<IRemoteTest>> shards = new ArrayList<>();
+ shards.add(new ArrayList<>(fakeModules));
+ shards.add(new ArrayList<>(fakeModules));
+ return shards;
+ }
+ }
+
private ITestSuite createFakeSuite(String name) throws Exception {
ITestSuite suite = new SplitITestSuite(name);
return suite;
}
+ private ITestSuite createFakeSuite(String name, boolean intraModuleSharding) throws Exception {
+ ITestSuite suite = new SplitITestSuite(name);
+ if (!intraModuleSharding) {
+ OptionSetter setter = new OptionSetter(suite);
+ setter.setOptionValue("intra-module-sharding", "false");
+ }
+ return suite;
+ }
+
private List<IRemoteTest> testShard(int shardIndex) throws Exception {
mContext.addAllocatedDevice("default", EasyMock.createMock(ITestDevice.class));
List<IRemoteTest> test = new ArrayList<>();
@@ -282,6 +309,26 @@ public class StrictShardHelperTest {
return mConfig.getTests();
}
+ private List<IRemoteTest> createITestSuiteList(List<String> modules) throws Exception {
+ List<IRemoteTest> tests = new ArrayList<>();
+ for (String name : modules) {
+ tests.add(createFakeSuite(name, false).split(2, mTestInfo).iterator().next());
+ }
+
+ CommandOptions options = new CommandOptions();
+ OptionSetter setter = new OptionSetter(options);
+ setter.setOptionValue("shard-count", "2");
+ setter.setOptionValue("shard-index", Integer.toString(1));
+ setter.setOptionValue("optimize-mainline-test", "true");
+ mConfig.setCommandOptions(options);
+ mConfig.setCommandLine(new String[] {"empty"});
+ mConfig.setTests(tests);
+
+ FakeStrictShardHelper fakeHelper = new FakeStrictShardHelper(tests);
+ fakeHelper.shardConfig(mConfig, mTestInfo, mRescheduler, null);
+ return mConfig.getTests();
+ }
+
/**
* Total for all the _shardX test should be 14 tests (2 per modules). 6 for module1: 3 module1
* shard * 2 4 for module2: 2 module2 shard * 2 4 for module3: 2 module3 shard * 2
@@ -304,6 +351,61 @@ public class StrictShardHelperTest {
assertEquals(1, ((ITestSuite) res.get(2)).getDirectModule().numTests());
}
+ /**
+ * Test that the unsorted test modules are re-ordered.
+ */
+ @Test
+ public void testReorderTestModules() throws Exception {
+ List<String> unSortedModules =
+ Arrays.asList(
+ "module1[com.android.mod1.apex]",
+ "module1[com.android.mod1.apex+com.android.mod2.apex]",
+ "module2[com.android.mod1.apex]",
+ "module1[com.android.mod3.apk]",
+ "module2[com.android.mod1.apex+com.android.mod2.apex]",
+ "module2[com.android.mod3.apk]",
+ "module3[com.android.mod1.apex+com.android.mod2.apex]",
+ "module3[com.android.mod3.apk]",
+ "module4[com.android.mod3.apk]",
+ "module5[com.android.mod3.apk]"
+ );
+ List<IRemoteTest> res = createITestSuiteList(unSortedModules);
+
+ List<String> sortedModules =
+ Arrays.asList(
+ "module1[com.android.mod1.apex]",
+ "module2[com.android.mod1.apex]",
+ "module1[com.android.mod1.apex+com.android.mod2.apex]",
+ "module2[com.android.mod1.apex+com.android.mod2.apex]",
+ "module3[com.android.mod1.apex+com.android.mod2.apex]",
+ "module1[com.android.mod3.apk]",
+ "module2[com.android.mod3.apk]",
+ "module3[com.android.mod3.apk]",
+ "module4[com.android.mod3.apk]",
+ "module5[com.android.mod3.apk]"
+ );
+ for (int i = 0 ; i < sortedModules.size() ; i++) {
+ assertEquals(sortedModules.get(i), ((ITestSuite)res.get(i)).getDirectModule().getId());
+ }
+ }
+
+ /**
+ * Test that the there exist a module with invalid parameterized modules defined.
+ */
+ @Test
+ public void testReorderTestModulesWithUnexpectedMainlineModules() throws Exception {
+ List<String> modules = Arrays.asList("module1[com.mod1.apex]", "module1[com.mod1]");
+ try {
+ List<IRemoteTest> res = createITestSuiteList(modules);
+ fail("Should have thrown an exception.");
+ } catch (RuntimeException expected) {
+ // expected
+ assertTrue(expected.getMessage().contains(
+ "Module: module1[com.mod1] doesn't match the pattern for mainline " +
+ "modules. The pattern should end with apk/apex/apks."));
+ }
+ }
+
@Test
public void testMergeSuite_shard1() throws Exception {
List<IRemoteTest> res = testShard(1);
diff --git a/tests/src/com/android/tradefed/monitoring/LabResourceDeviceMonitorTest.java b/tests/src/com/android/tradefed/monitoring/LabResourceDeviceMonitorTest.java
new file mode 100644
index 000000000..ee51dfdfe
--- /dev/null
+++ b/tests/src/com/android/tradefed/monitoring/LabResourceDeviceMonitorTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2020 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.monitoring;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class LabResourceDeviceMonitorTest {
+
+ private LabResourceDeviceMonitor mLabResourceDeviceMonitor;
+
+ @Before
+ public void setUp() {
+ mLabResourceDeviceMonitor = new LabResourceDeviceMonitor();
+ }
+
+ @Test
+ public void testServerStartAndShutdown() {
+ Assert.assertFalse(
+ "server should be empty before monitor run",
+ mLabResourceDeviceMonitor.getServer().isPresent());
+ mLabResourceDeviceMonitor.run();
+ Assert.assertTrue(
+ "server should present after monitor run",
+ mLabResourceDeviceMonitor.getServer().isPresent());
+ Assert.assertEquals(
+ LabResourceDeviceMonitor.DEFAULT_PORT,
+ mLabResourceDeviceMonitor.getServer().get().getPort());
+ mLabResourceDeviceMonitor.stop();
+ Assert.assertTrue(
+ "server should be shutdown after monitor stop",
+ mLabResourceDeviceMonitor.getServer().get().isShutdown());
+ }
+}
diff --git a/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java b/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
index d83c34319..82fc304bf 100644
--- a/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
+++ b/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
@@ -306,10 +306,11 @@ public class PerfettoGenericPostProcessorTest {
}
/**
- * Test metrics enabled with key prefixing.
+ * Test metrics enabled with key and string value prefixing.
*/
@Test
- public void testParsingWithKeyPrefixing() throws ConfigurationException, IOException {
+ public void testParsingWithKeyAndStringValuePrefixing()
+ throws ConfigurationException, IOException {
setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
mOptionSetter.setOptionValue(KEY_PREFIX_OPTION,
@@ -320,8 +321,8 @@ public class PerfettoGenericPostProcessorTest {
PREFIX_OPTION_VALUE,
new LogFile(
perfettoMetricProtoFile.getAbsolutePath(), "some.url", LogDataType.TEXTPB));
- Map<String, Metric.Builder> parsedMetrics =
- mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs);
+ Map<String, Metric.Builder> parsedMetrics = mProcessor
+ .processRunMetricsAndLogs(new HashMap<>(), testLogs);
assertMetricsContain(parsedMetrics,
"perfetto_android_hwui_metric-process_info-process_name-com.android.systemui-all_mem_min",
@@ -329,6 +330,28 @@ public class PerfettoGenericPostProcessorTest {
}
+ /**
+ * Test metrics enabled with key and integer value prefixing.
+ */
+ @Test
+ public void testParsingWithKeyAndIntegerValuePrefixing()
+ throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(KEY_PREFIX_OPTION,
+ "perfetto.protos.AndroidCpuMetric.CoreData.id");
+ mOptionSetter.setOptionValue(ALL_METRICS_OPTION, "true");
+ Map<String, LogFile> testLogs = new HashMap<>();
+ testLogs.put(
+ PREFIX_OPTION_VALUE,
+ new LogFile(
+ perfettoMetricProtoFile.getAbsolutePath(), "some.url", LogDataType.TEXTPB));
+ Map<String, Metric.Builder> parsedMetrics = mProcessor
+ .processRunMetricsAndLogs(new HashMap<>(), testLogs);
+ assertMetricsContain(parsedMetrics, "perfetto_android_cpu-process_info-name-com.google."
+ + "android.apps.messaging-threads-name-BG Thread #1-core-id-1-metrics-runtime_ns",
+ 14376405);
+ }
/** Test the post processor can parse binary perfetto metric proto format. */
@Test
@@ -515,6 +538,54 @@ public class PerfettoGenericPostProcessorTest {
" all_mem_min: 15120269\n" +
" all_mem_avg: 24468104.289592762\n" +
" }\n" +
+ "}"
+ + "android_cpu {\n" +
+ " process_info {\n" +
+ " name: \"com.google.android.apps.messaging\"\n" +
+ " metrics {\n" +
+ " mcycles: 139\n" +
+ " runtime_ns: 639064902\n" +
+ " min_freq_khz: 576000\n" +
+ " max_freq_khz: 2016000\n" +
+ " avg_freq_khz: 324000\n" +
+ " }\n" +
+ " threads {\n" +
+ " name: \"BG Thread #1\"\n" +
+ " core {\n" +
+ " id: 0\n" +
+ " metrics {\n" +
+ " runtime_ns: 8371202\n" +
+ " }\n" +
+ " }\n" +
+ " core {\n" +
+ " id: 1\n" +
+ " metrics {\n" +
+ " mcycles: 0\n" +
+ " runtime_ns: 14376405\n" +
+ " min_freq_khz: 1785600\n" +
+ " max_freq_khz: 1785600\n" +
+ " avg_freq_khz: 57977\n" +
+ " }\n" +
+ " }\n" +
+ " metrics {\n" +
+ " mcycles: 0\n" +
+ " runtime_ns: 22747607\n" +
+ " min_freq_khz: 1785600\n" +
+ " max_freq_khz: 1785600\n" +
+ " avg_freq_khz: 36000\n" +
+ " }\n" +
+ " core_type {\n" +
+ " type: \"little\"\n" +
+ " metrics {\n" +
+ " mcycles: 0\n" +
+ " runtime_ns: 22747607\n" +
+ " min_freq_khz: 1785600\n" +
+ " max_freq_khz: 1785600\n" +
+ " avg_freq_khz: 36000\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
+ " }\n" +
"}";
FileWriter fileWriter = null;
try {
@@ -593,3 +664,4 @@ public class PerfettoGenericPostProcessorTest {
.getSingleString())));
}
}
+
diff --git a/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java b/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java
index ff0692e9e..852a4df54 100644
--- a/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java
+++ b/tests/src/com/android/tradefed/presubmit/GeneralTestsConfigValidation.java
@@ -80,6 +80,8 @@ public class GeneralTestsConfigValidation implements IBuildReceiver {
"com.android.tradefed.testtype.rust.RustBinaryTest",
"com.android.tradefed.testtype.StubTest",
"com.android.tradefed.testtype.ArtRunTest",
+ "com.android.tradefed.testtype.ArtGTest",
+ "com.android.tradefed.testtype.mobly.MoblyBinaryHostTest",
// Others
"com.google.android.deviceconfig.RebootTest"));
diff --git a/tests/src/com/android/tradefed/result/JsonHttpTestResultReporterTest.java b/tests/src/com/android/tradefed/result/JsonHttpTestResultReporterTest.java
index d9b8eb6ba..26e020b31 100644
--- a/tests/src/com/android/tradefed/result/JsonHttpTestResultReporterTest.java
+++ b/tests/src/com/android/tradefed/result/JsonHttpTestResultReporterTest.java
@@ -25,6 +25,7 @@ import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.proto.TfMetricProtoUtil;
import org.json.JSONException;
@@ -65,14 +66,18 @@ public class JsonHttpTestResultReporterTest {
@Test
public void testSkipFailedRuns_notSet() throws JSONException {
mReporter.invocationStarted(mContext);
- injectTestRun(mReporter, "run1", "test", "metric1", 0, true);
- injectTestRun(mReporter, "run2", "test", "metric2", 1, false);
+ injectTestRun(mReporter, "run1", "test", "123", 0, true);
+ injectTestRun(mReporter, "run2", "test", "456", 1, false);
mReporter.invocationEnded(0);
ArgumentCaptor<JSONObject> jsonCaptor = ArgumentCaptor.forClass(JSONObject.class);
verify(mReporter).postResults(jsonCaptor.capture());
// Both runs should be in the posted metrics.
Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).has("run1"));
+ Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).getJSONObject("run1")
+ .has("run_metric"));
Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).has("run2"));
+ Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).getJSONObject("run2")
+ .has("run_metric"));
}
/** Test that failed runs are skipped when skip-failed-runs is set. */
@@ -81,16 +86,59 @@ public class JsonHttpTestResultReporterTest {
OptionSetter optionSetter = new OptionSetter(mReporter);
optionSetter.setOptionValue(SKIP_FAILED_RUNS_OPTION, String.valueOf(true));
mReporter.invocationStarted(mContext);
- injectTestRun(mReporter, "run1", "test", "metric1", 0, true);
- injectTestRun(mReporter, "run2", "test", "metric2", 1, false);
+ injectTestRun(mReporter, "run1", "test", "123", 0, true);
+ injectTestRun(mReporter, "run2", "test", "456", 1, false);
mReporter.invocationEnded(0);
ArgumentCaptor<JSONObject> jsonCaptor = ArgumentCaptor.forClass(JSONObject.class);
verify(mReporter).postResults(jsonCaptor.capture());
// Only the first run should be in the posted metrics.
Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).has("run1"));
+ Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).getJSONObject("run1")
+ .has("run_metric"));
Assert.assertFalse(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY).has("run2"));
}
+ /** Test non-numeric metrics are not posted in the final JSONObject. */
+ @Test
+ public void testInvalidMetricsNotSet() throws ConfigurationException, JSONException {
+ OptionSetter optionSetter = new OptionSetter(mReporter);
+ optionSetter.setOptionValue(SKIP_FAILED_RUNS_OPTION, String.valueOf(true));
+ mReporter.invocationStarted(mContext);
+ // Inject invalid metric "1.23invalid".
+ injectTestRun(mReporter, "run1", "test", "1.23invalid", 0, false);
+ mReporter.invocationEnded(0);
+ ArgumentCaptor<JSONObject> jsonCaptor = ArgumentCaptor.forClass(JSONObject.class);
+ verify(mReporter).postResults(jsonCaptor.capture());
+ // Only the first run should be in the posted metrics.
+ CLog.i(jsonCaptor.getValue().toString());
+ // Check the metric is not added in the JSONObject.
+ Assert.assertFalse(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY)
+ .getJSONObject("run1").has("run_metric"));
+ }
+
+ /** Test valid and invalid metrics in JSONObject. */
+ @Test
+ public void testInvalidAndInvalidMetricsNotSet() throws ConfigurationException, JSONException {
+ OptionSetter optionSetter = new OptionSetter(mReporter);
+ optionSetter.setOptionValue(SKIP_FAILED_RUNS_OPTION, String.valueOf(true));
+ mReporter.invocationStarted(mContext);
+ // Inject invalid metric "1.23invalid".
+ injectTestRun(mReporter, "run1", "test1", "1.23invalid", 0, false);
+ // Inject valid metric "5.99".
+ injectTestRun(mReporter, "run2", "test1", "5.99", 0, false);
+ mReporter.invocationEnded(0);
+ ArgumentCaptor<JSONObject> jsonCaptor = ArgumentCaptor.forClass(JSONObject.class);
+ verify(mReporter).postResults(jsonCaptor.capture());
+ CLog.i(jsonCaptor.getValue().toString());
+ // Check the invalid metric is not added in the JSONObject.
+ Assert.assertFalse(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY)
+ .getJSONObject("run1").has("run_metric"));
+ // Check the valid metric is added in the JSONObject.
+ Assert.assertTrue(jsonCaptor.getValue().getJSONObject(JSON_METRIC_KEY)
+ .getJSONObject("run2").has("run_metric"));
+ }
+
+
/** Test for parsing additional device details when collect device details is enabled. */
@Test
public void testIncludeAdditionalTestDetails() throws ConfigurationException {
@@ -132,4 +180,4 @@ public class JsonHttpTestResultReporterTest {
target.testRunEnded(0, TfMetricProtoUtil.upgradeConvert(runMetrics));
return test;
}
-}
+} \ No newline at end of file
diff --git a/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java b/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java
index 1bf8eff76..583a4c04d 100644
--- a/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java
+++ b/tests/src/com/android/tradefed/result/LogcatCrashResultForwarderTest.java
@@ -216,4 +216,56 @@ public class LogcatCrashResultForwarderTest {
+ "\tat class.method1(Class.java:1)\n"
+ "\tat class.method2(Class.java:2)\n"));
}
+
+ /** Test that test-timeout tests have failure status TIMED_OUT. */
+ @Test
+ @SuppressWarnings("MustBeClosedChecker")
+ public void testTestTimedOutTests() {
+ String trace =
+ "org.junit.runners.model.TestTimedOutException: "
+ + "test timed out after 1000 milliseconds";
+ mReporter = new LogcatCrashResultForwarder(mMockDevice, mMockListener);
+ TestDescription test = new TestDescription("com.class", "test");
+
+ mMockListener.testStarted(test, 0L);
+
+ Capture<FailureDescription> captured = new Capture<>();
+ mMockListener.testFailed(EasyMock.eq(test), EasyMock.capture(captured));
+ mMockListener.testEnded(test, 5L, new HashMap<String, Metric>());
+
+ EasyMock.replay(mMockListener, mMockDevice);
+ mReporter.testStarted(test, 0L);
+ mReporter.testFailed(test, trace);
+ mReporter.testEnded(test, 5L, new HashMap<String, Metric>());
+ EasyMock.verify(mMockListener, mMockDevice);
+ assertTrue(captured.getValue().getErrorMessage().contains(trace));
+ assertTrue(FailureStatus.TIMED_OUT.equals(captured.getValue().getFailureStatus()));
+ }
+
+ /** Test that shell-timeout tests have failure status TIMED_OUT. */
+ @Test
+ @SuppressWarnings("MustBeClosedChecker")
+ public void testShellTimedOutTests() {
+ String trace =
+ "Test failed to run to completion. "
+ + " Reason: 'Failed to receive adb shell test output within 3000 ms. "
+ + "Test may have timed out, or adb connection to device became "
+ + "unresponsive'. Check device logcat for details";
+ mReporter = new LogcatCrashResultForwarder(mMockDevice, mMockListener);
+ TestDescription test = new TestDescription("com.class", "test");
+
+ mMockListener.testStarted(test, 0L);
+
+ Capture<FailureDescription> captured = new Capture<>();
+ mMockListener.testFailed(EasyMock.eq(test), EasyMock.capture(captured));
+ mMockListener.testEnded(test, 5L, new HashMap<String, Metric>());
+
+ EasyMock.replay(mMockListener, mMockDevice);
+ mReporter.testStarted(test, 0L);
+ mReporter.testFailed(test, trace);
+ mReporter.testEnded(test, 5L, new HashMap<String, Metric>());
+ EasyMock.verify(mMockListener, mMockDevice);
+ assertTrue(captured.getValue().getErrorMessage().contains(trace));
+ assertTrue(FailureStatus.TIMED_OUT.equals(captured.getValue().getFailureStatus()));
+ }
}
diff --git a/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java b/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java
index 63556ab23..b7cc8eb80 100644
--- a/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java
+++ b/tests/src/com/android/tradefed/result/error/ErrorIdentifierTest.java
@@ -37,6 +37,7 @@ public class ErrorIdentifierTest {
List<ErrorIdentifier> errors = new ArrayList<>();
errors.addAll(Arrays.asList(InfraErrorIdentifier.values()));
errors.addAll(Arrays.asList(DeviceErrorIdentifier.values()));
+ errors.addAll(Arrays.asList(TestErrorIdentifier.values()));
List<String> names = errors.stream().map(e -> e.name()).collect(Collectors.toList());
Set<String> uniques = new HashSet<>();
diff --git a/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java b/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java
index 43aaa7a13..bd17c8777 100644
--- a/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java
+++ b/tests/src/com/android/tradefed/retry/ResultAggregatorTest.java
@@ -211,6 +211,135 @@ public class ResultAggregatorTest {
}
@Test
+ public void testForwarding_assumptionFailure() {
+ mDetailedListener = EasyMock.createStrictMock(ITestDetailedReceiver.class);
+ LogFile test1Log = new LogFile("test1", "url", LogDataType.TEXT);
+ LogFile test2LogBefore = new LogFile("test2-before", "url", LogDataType.TEXT);
+ LogFile test2LogAfter = new LogFile("test2-after", "url", LogDataType.TEXT);
+ LogFile testRun1LogBefore = new LogFile("test-run1-before", "url", LogDataType.TEXT);
+ LogFile testRun1LogAfter = new LogFile("test-run1-after", "url", LogDataType.TEXT);
+ LogFile beforeEnd = new LogFile("path", "url", LogDataType.TEXT);
+ LogFile betweenAttemptsLog = new LogFile("between-attempts", "url", LogDataType.TEXT);
+ LogFile moduleLog = new LogFile("module-log", "url", LogDataType.TEXT);
+ TestDescription test1 = new TestDescription("classname", "test1");
+ TestDescription test2 = new TestDescription("classname", "test2");
+ ILogSaver logger = EasyMock.createMock(ILogSaver.class);
+
+ EasyMock.expect(mDetailedListener.supportGranularResults()).andStubReturn(true);
+
+ // Invocation level
+ mAggListener.setLogSaver(logger);
+ mAggListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mAggListener.getSummary()).andStubReturn(null);
+ mDetailedListener.setLogSaver(logger);
+ mDetailedListener.invocationStarted(mInvocationContext);
+ EasyMock.expect(mDetailedListener.getSummary()).andStubReturn(null);
+
+ mAggListener.testModuleStarted(mModuleContext);
+ mDetailedListener.testModuleStarted(mModuleContext);
+
+ // Detailed receives the breakdown
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mDetailedListener.logAssociation("test1-log", test1Log);
+ mDetailedListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.logAssociation("test2-before-log", test2LogBefore);
+ mDetailedListener.testFailed(test2, FailureDescription.create("I failed. retry me."));
+ mDetailedListener.logAssociation("test2-after-log", test2LogAfter);
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.logAssociation("test-run1-before-log", testRun1LogBefore);
+ mDetailedListener.logAssociation("test-run1-after-log", testRun1LogAfter);
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(1), EasyMock.anyLong());
+ mDetailedListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mDetailedListener.testAssumptionFailure(
+ EasyMock.eq(test2), EasyMock.eq(FailureDescription.create("Assump failure")));
+ mDetailedListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mDetailedListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mDetailedListener.logAssociation("between-attempts", betweenAttemptsLog);
+ mDetailedListener.logAssociation("module-log", moduleLog);
+
+ // Aggregated listeners receives the aggregated results
+ mAggListener.testRunStarted(
+ EasyMock.eq("run1"), EasyMock.eq(2), EasyMock.eq(0), EasyMock.anyLong());
+ mAggListener.testStarted(EasyMock.eq(test1), EasyMock.anyLong());
+ mAggListener.logAssociation("test1-log", test1Log);
+ mAggListener.testEnded(
+ EasyMock.eq(test1),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.testStarted(EasyMock.eq(test2), EasyMock.anyLong());
+ mAggListener.testAssumptionFailure(
+ EasyMock.eq(test2), (FailureDescription) EasyMock.anyObject());
+ mAggListener.logAssociation("test2-before-log", test2LogBefore);
+ mAggListener.logAssociation("test2-after-log", test2LogAfter);
+ mAggListener.testEnded(
+ EasyMock.eq(test2),
+ EasyMock.anyLong(),
+ EasyMock.<HashMap<String, Metric>>anyObject());
+ mAggListener.logAssociation("test-run1-before-log", testRun1LogBefore);
+ mAggListener.logAssociation("test-run1-after-log", testRun1LogAfter);
+ mAggListener.testRunEnded(450L, new HashMap<String, Metric>());
+ mAggListener.logAssociation("between-attempts", betweenAttemptsLog);
+ mAggListener.logAssociation("module-log", moduleLog);
+ mAggListener.testModuleEnded();
+ mDetailedListener.testModuleEnded();
+ mAggListener.logAssociation("before-end", beforeEnd);
+ mAggListener.invocationEnded(500L);
+ mDetailedListener.logAssociation("before-end", beforeEnd);
+ mDetailedListener.invocationEnded(500L);
+
+ EasyMock.replay(mAggListener, mDetailedListener);
+ mAggregator =
+ new TestableResultAggregator(
+ Arrays.asList(mAggListener, mDetailedListener),
+ RetryStrategy.RETRY_ANY_FAILURE);
+ mAggregator.setLogSaver(logger);
+ mAggregator.invocationStarted(mInvocationContext);
+ mAggregator.testModuleStarted(mModuleContext);
+ // Attempt 1
+ mAggregator.testRunStarted("run1", 2, 0);
+ mAggregator.testStarted(test1);
+ mAggregator.logAssociation("test1-log", test1Log);
+ mAggregator.testEnded(test1, new HashMap<String, Metric>());
+ mAggregator.testStarted(test2);
+ mAggregator.logAssociation("test2-before-log", test2LogBefore);
+ mAggregator.testFailed(test2, FailureDescription.create("I failed. retry me."));
+ mAggregator.logAssociation("test2-after-log", test2LogAfter);
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.logAssociation("test-run1-before-log", testRun1LogBefore);
+ mAggregator.testRunFailed("run fail");
+ mAggregator.logAssociation("test-run1-after-log", testRun1LogAfter);
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+ mAggregator.logAssociation("between-attempts", betweenAttemptsLog);
+ // Attempt 2
+ mAggregator.testRunStarted("run1", 2, 1);
+ mAggregator.testStarted(test2);
+ mAggregator.testAssumptionFailure(test2, FailureDescription.create("Assump failure"));
+ mAggregator.testEnded(test2, new HashMap<String, Metric>());
+ mAggregator.testRunEnded(450L, new HashMap<String, Metric>());
+
+ mAggregator.logAssociation("module-log", moduleLog);
+ mAggregator.testModuleEnded();
+ mAggregator.logAssociation("before-end", beforeEnd);
+ mAggregator.invocationEnded(500L);
+ EasyMock.verify(mAggListener, mDetailedListener);
+ assertEquals("run fail", mAggregator.getInvocationMetricRunError());
+ }
+
+ @Test
public void testForwarding_runFailure() {
mDetailedListener = EasyMock.createStrictMock(ITestDetailedReceiver.class);
TestDescription test1 = new TestDescription("classname", "test1");
diff --git a/tests/src/com/android/tradefed/sandbox/SandboxedInvocationExecutionTest.java b/tests/src/com/android/tradefed/sandbox/SandboxedInvocationExecutionTest.java
deleted file mode 100644
index 7594ce45c..000000000
--- a/tests/src/com/android/tradefed/sandbox/SandboxedInvocationExecutionTest.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.tradefed.sandbox;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-
-import com.android.tradefed.build.BuildInfo;
-import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.config.Configuration;
-import com.android.tradefed.config.ConfigurationDef;
-import com.android.tradefed.config.IConfiguration;
-import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
-import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.invoker.sandbox.SandboxedInvocationExecution;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.io.File;
-
-/** Unit tests for {@link SandboxedInvocationExecution}. */
-@RunWith(JUnit4.class)
-public class SandboxedInvocationExecutionTest {
-
- private SandboxedInvocationExecution mExecution;
- private IInvocationContext mContext;
- private IConfiguration mConfig;
-
- @Before
- public void setUp() {
- mExecution = new SandboxedInvocationExecution();
- mContext = new InvocationContext();
- mConfig = new Configuration("name", "desc");
- mConfig.getConfigurationDescription().setSandboxed(true);
- }
-
- @Test
- public void testBuildInfo_testTag() throws Exception {
- IBuildInfo info = new BuildInfo();
- assertEquals("stub", info.getTestTag());
- info.setFile(BuildInfoFileKey.TESTDIR_IMAGE, new File("doesnt_matter_testsdir"), "tests");
- mContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, info);
- mConfig.getCommandOptions().setTestTag("test");
- TestInformation testInfo =
- TestInformation.newBuilder().setInvocationContext(mContext).build();
- assertNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY));
- mExecution.fetchBuild(testInfo, mConfig, null, null);
- // Build test tag was updated
- assertEquals("test", info.getTestTag());
- // Execution file was back filled
- assertNotNull(testInfo.executionFiles().get(FilesKey.TESTS_DIRECTORY));
- }
-}
diff --git a/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java b/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java
index a32f9b646..244934cf5 100644
--- a/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java
+++ b/tests/src/com/android/tradefed/targetprep/DeviceSetupTest.java
@@ -266,6 +266,22 @@ public class DeviceSetupTest extends TestCase {
EasyMock.verify(mMockDevice);
}
+ public void testSetup_wifi_network_empty() throws Exception {
+ doSetupExpectations();
+ doCheckExternalStoreSpaceExpectations();
+ doSettingExpectations("global", "wifi_on", "1");
+ doCommandsExpectations("svc wifi enable");
+ EasyMock.replay(mMockDevice);
+
+ mDeviceSetup.setWifiNetwork("");
+ mDeviceSetup.setWifiPsk("psk");
+
+ mDeviceSetup.setWifi(BinaryState.ON);
+ mDeviceSetup.setUp(mTestInfo);
+
+ EasyMock.verify(mMockDevice);
+ }
+
public void testSetup_wifi_multiple_network_names() throws Exception {
doSetupExpectations();
doCheckExternalStoreSpaceExpectations();
diff --git a/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java b/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java
index 9b38ce69b..e889441d2 100644
--- a/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java
@@ -28,10 +28,9 @@ import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.ZipUtil;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
+
import org.junit.After;
+import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,6 +39,10 @@ import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+
/** Unit tests for {@link DynamicSystemPreparer}. */
@RunWith(JUnit4.class)
public class DynamicSystemPreparerTest {
@@ -95,15 +98,12 @@ public class DynamicSystemPreparerTest {
}
}
- @Test
- public void testSetUp() throws TargetSetupError, BuildError, DeviceNotAvailableException {
- Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz")))
- .thenReturn(Boolean.TRUE);
+ private void mockGsiToolStatus(String status) throws DeviceNotAvailableException {
doAnswer(
new Answer<Object>() {
@Override
public Object answer(InvocationOnMock invocation) {
- byte[] outputBytes = "running".getBytes();
+ byte[] outputBytes = status.getBytes();
((CollectingOutputReceiver) invocation.getArguments()[1])
.addOutput(outputBytes, 0, outputBytes.length);
return null;
@@ -112,10 +112,62 @@ public class DynamicSystemPreparerTest {
.when(mMockDevice)
.executeShellCommand(
matches("gsi_tool status"), any(CollectingOutputReceiver.class));
+ }
+
+ @Test
+ public void testSetUp() throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz")))
+ .thenReturn(Boolean.TRUE);
+ Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(true);
+ mockGsiToolStatus("running");
CommandResult res = new CommandResult();
res.setStdout("");
res.setStatus(CommandStatus.SUCCESS);
Mockito.when(mMockDevice.executeShellV2Command("gsi_tool enable")).thenReturn(res);
mPreparer.setUp(mMockDevice, mBuildInfo);
}
+
+ @Test
+ public void testSetUp_installationFail() throws BuildError, DeviceNotAvailableException {
+ Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz")))
+ .thenReturn(Boolean.TRUE);
+ Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(false);
+ try {
+ mPreparer.setUp(mMockDevice, mBuildInfo);
+ Assert.fail("setUp() should have thrown.");
+ } catch (TargetSetupError e) {
+ Assert.assertEquals(
+ "Timed out waiting for DSU installation to complete and reboot",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetUp_rebootFail() throws BuildError, DeviceNotAvailableException {
+ Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz")))
+ .thenReturn(Boolean.TRUE);
+ Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(true);
+ Mockito.doThrow(new DeviceNotAvailableException()).when(mMockDevice).waitForDeviceOnline();
+ try {
+ mPreparer.setUp(mMockDevice, mBuildInfo);
+ Assert.fail("setUp() should have thrown.");
+ } catch (TargetSetupError e) {
+ Assert.assertEquals("Timed out booting into DSU", e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSetUp_noDsuRunningAfterRebootFail()
+ throws BuildError, DeviceNotAvailableException {
+ Mockito.when(mMockDevice.pushFile(Mockito.any(), Mockito.eq("/sdcard/system.raw.gz")))
+ .thenReturn(Boolean.TRUE);
+ Mockito.when(mMockDevice.waitForDeviceNotAvailable(Mockito.anyLong())).thenReturn(true);
+ mockGsiToolStatus("normal");
+ try {
+ mPreparer.setUp(mMockDevice, mBuildInfo);
+ Assert.fail("setUp() should have thrown.");
+ } catch (TargetSetupError e) {
+ Assert.assertEquals("Failed to boot into DSU", e.getMessage());
+ }
+ }
}
diff --git a/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java b/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java
index f91253e95..bccf68d0e 100644
--- a/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/GsiDeviceFlashPreparerTest.java
@@ -178,21 +178,36 @@ public class GsiDeviceFlashPreparerTest {
EasyMock.verify(mMockDevice, mMockRunUtil);
}
- /* Verifies that setUp will throw exception when there is no vbmeta.img in the zip file*/
+ /* Verifies that setUp can pass when there is no vbmeta.img is provided*/
@Test
- public void testSetUp_NoVbmetaImageInGsiZip() throws Exception {
+ public void testSetUp_Success_NoVbmetaImage() throws Exception {
File gsiDir = FileUtil.createTempDir("gsi_folder", mTmpDir);
File systemImg = new File(gsiDir, "system.img");
- File gsiZip = FileUtil.createTempFile("gsi_image", ".zip", mTmpDir);
- ZipUtil.createZip(List.of(systemImg), gsiZip);
- mBuildInfo.setFile("gsi_system.img", gsiZip, "0");
+ FileUtil.writeToFile("ddd", systemImg);
+ mBuildInfo.setFile("gsi_system.img", systemImg, "0");
+ mMockDevice.waitForDeviceOnline();
+ EasyMock.expect(mMockDevice.getApiLevel()).andReturn(29);
+ mMockDevice.rebootIntoBootloader();
+ mMockRunUtil.allowInterrupt(false);
+ mMockDevice.rebootIntoFastbootd();
+ doGetSlotExpectation();
+ EasyMock.expect(
+ mMockDevice.executeLongFastbootCommand(
+ "delete-logical-partition", "product_a"))
+ .andReturn(mSuccessResult);
+ EasyMock.expect(mMockDevice.executeLongFastbootCommand("erase", "system_a"))
+ .andReturn(mSuccessResult);
+ EasyMock.expect(
+ mMockDevice.executeLongFastbootCommand(
+ "flash",
+ "system",
+ mBuildInfo.getFile("gsi_system.img").getAbsolutePath()))
+ .andReturn(mSuccessResult);
+ EasyMock.expect(mMockDevice.executeLongFastbootCommand("-w")).andReturn(mSuccessResult);
+ mMockRunUtil.allowInterrupt(true);
+ doSetupExpectations();
EasyMock.replay(mMockDevice, mMockRunUtil);
- try {
- mPreparer.setUp(mTestInfo);
- fail("TargetSetupError is expected");
- } catch (TargetSetupError e) {
- // expected
- }
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice, mMockRunUtil);
}
diff --git a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
index 2541183c3..0652a2106 100644
--- a/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/InstallApexModuleTargetPreparerTest.java
@@ -24,6 +24,7 @@ import static org.mockito.Mockito.when;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.command.remote.DeviceDescriptor;
import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.device.ITestDevice.ApexInfo;
import com.android.tradefed.invoker.IInvocationContext;
@@ -35,6 +36,7 @@ import com.android.tradefed.util.FileUtil;
import com.google.common.collect.ImmutableSet;
+import java.util.Arrays;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
@@ -60,6 +62,8 @@ public class InstallApexModuleTargetPreparerTest {
private TestInformation mTestInfo;
private BundletoolUtil mMockBundletoolUtil;
private File mFakeApex;
+ private File mFakeApex2;
+ private File mFakeApex3;
private File mFakeApk;
private File mFakeApk2;
private File mFakePersistentApk;
@@ -68,6 +72,8 @@ public class InstallApexModuleTargetPreparerTest {
private File mBundletoolJar;
private OptionSetter mSetter;
private static final String APEX_PACKAGE_NAME = "com.android.FAKE_APEX_PACKAGE_NAME";
+ private static final String APEX2_PACKAGE_NAME = "com.android.FAKE_APEX2_PACKAGE_NAME";
+ private static final String APEX3_PACKAGE_NAME = "com.android.FAKE_APEX3_PACKAGE_NAME";
private static final String APK_PACKAGE_NAME = "com.android.FAKE_APK_PACKAGE_NAME";
private static final String APK2_PACKAGE_NAME = "com.android.FAKE_APK2_PACKAGE_NAME";
private static final String PERSISTENT_APK_PACKAGE_NAME = "com.android.PERSISTENT_PACKAGE_NAME";
@@ -78,6 +84,7 @@ public class InstallApexModuleTargetPreparerTest {
private static final String APEX_PACKAGE_KEYWORD = "FAKE_APEX_PACKAGE_NAME";
private static final long APEX_VERSION = 1;
private static final String APEX_NAME = "fakeApex.apex";
+ private static final String APEX2_NAME = "fakeApex_2.apex";
private static final String APK_NAME = "fakeApk.apk";
private static final String APK2_NAME = "fakeSecondApk.apk";
private static final String PERSISTENT_APK_NAME = "fakePersistentApk.apk";
@@ -92,6 +99,8 @@ public class InstallApexModuleTargetPreparerTest {
@Before
public void setUp() throws Exception {
mFakeApex = FileUtil.createTempFile("fakeApex", ".apex");
+ mFakeApex2 = FileUtil.createTempFile("fakeApex_2", ".apex");
+ mFakeApex3 = FileUtil.createTempFile("fakeApex_3", ".apex");
mFakeApk = FileUtil.createTempFile("fakeApk", ".apk");
mFakeApk2 = FileUtil.createTempFile("fakeSecondApk", ".apk");
mFakePersistentApk = FileUtil.createTempFile("fakePersistentApk", ".apk");
@@ -126,7 +135,13 @@ public class InstallApexModuleTargetPreparerTest {
@Override
protected File getLocalPathForFilename(
TestInformation testInfo, String appFileName) throws TargetSetupError {
- if (APEX_NAME.equals(appFileName)) {
+ if (appFileName.endsWith(".apex")) {
+ if (appFileName.contains("fakeApex_2")) {
+ return mFakeApex2;
+ }
+ else if (appFileName.contains("fakeApex_3")) {
+ return mFakeApex3;
+ }
return mFakeApex;
}
if (appFileName.endsWith(".apk")) {
@@ -138,13 +153,11 @@ public class InstallApexModuleTargetPreparerTest {
return mFakeApk;
}
}
- if (appFileName.endsWith(".apks")) {
- if (appFileName.contains("Apex")) {
- return mFakeApexApks;
- }
- if (appFileName.contains("Apk")) {
- return mFakeApkApks;
- }
+ if (SPLIT_APEX_APKS_NAME.equals(appFileName)) {
+ return mFakeApexApks;
+ }
+ if (SPLIT_APK__APKS_NAME.equals(appFileName)) {
+ return mFakeApkApks;
}
if (appFileName.endsWith(".jar")) {
return mBundletoolJar;
@@ -156,6 +169,12 @@ public class InstallApexModuleTargetPreparerTest {
protected String parsePackageName(
File testAppFile, DeviceDescriptor deviceDescriptor) {
if (testAppFile.getName().endsWith(".apex")) {
+ if (testAppFile.getName().contains("fakeApex_2")) {
+ return APEX2_PACKAGE_NAME;
+ }
+ else if (testAppFile.getName().contains("fakeApex_3")) {
+ return APEX3_PACKAGE_NAME;
+ }
return APEX_PACKAGE_NAME;
}
if (testAppFile.getName().endsWith(".apk")
@@ -185,6 +204,8 @@ public class InstallApexModuleTargetPreparerTest {
ApexInfo apexInfo;
if (apex.getName().contains("Split")) {
apexInfo = new ApexInfo(SPLIT_APEX_PACKAGE_NAME, APEX_VERSION);
+ } else if (apex.getName().contains("fakeApex_2")) {
+ apexInfo = new ApexInfo(APEX2_PACKAGE_NAME, APEX_VERSION);
} else {
apexInfo = new ApexInfo(APEX_PACKAGE_NAME, APEX_VERSION);
}
@@ -209,11 +230,343 @@ public class InstallApexModuleTargetPreparerTest {
@After
public void tearDown() throws Exception {
FileUtil.deleteFile(mFakeApex);
+ FileUtil.deleteFile(mFakeApex2);
+ FileUtil.deleteFile(mFakeApex3);
FileUtil.deleteFile(mFakeApk);
FileUtil.deleteFile(mFakeApk2);
FileUtil.deleteFile(mFakePersistentApk);
}
+ /**
+ * Test that it gets the correct apex files that are already installed on the /data directory.
+ */
+ @Test
+ public void testGetApexInData() throws Exception {
+ Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
+ Set<ApexInfo> expectedApex = new HashSet<ApexInfo>();
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexSystem =
+ new ApexInfo(
+ "com.android.FAKE_APEX3_PACKAGE_NAME",
+ 1,
+ "/system/apex/com.android.FAKE_APEX3_PACKAGE_NAME@1.apex");
+
+ activatedApex = new HashSet<>(Arrays.asList(fakeApexData, fakeApexData2, fakeApexSystem));
+ expectedApex = new HashSet<>(Arrays.asList(fakeApexData, fakeApexData2));
+ assertEquals(2, mInstallApexModuleTargetPreparer.getApexInData(activatedApex).size());
+ assertEquals(expectedApex, mInstallApexModuleTargetPreparer.getApexInData(activatedApex));
+
+ activatedApex = new HashSet<>(Arrays.asList(fakeApexSystem));
+ assertEquals(0, mInstallApexModuleTargetPreparer.getApexInData(activatedApex).size());
+ }
+
+ /**
+ * Test that it returns the correct files to be installed and uninstalled.
+ */
+ @Test
+ public void testGetModulesToUninstall_NoneUninstallAndInstallFiles() throws Exception {
+ Set<ApexInfo> apexInData = new HashSet<>();
+ List<File> testFiles = new ArrayList<>();
+ testFiles.add(mFakeApex);
+ testFiles.add(mFakeApex2);
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ apexInData.add(fakeApexData);
+ apexInData.add(fakeApexData2);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ Set<ApexInfo> results = mInstallApexModuleTargetPreparer.getModulesToUninstall(
+ apexInData, testFiles, mMockDevice);
+
+ assertEquals(0, testFiles.size());
+ assertEquals(0, results.size());
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ }
+
+ /**
+ * Test that it returns the correct files to be installed and uninstalled.
+ */
+ @Test
+ public void testGetModulesToUninstall_UninstallAndInstallFiles() throws Exception {
+ Set<ApexInfo> apexInData = new HashSet<>();
+ List<File> testFiles = new ArrayList<>();
+ testFiles.add(mFakeApex3);
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ apexInData.add(fakeApexData);
+ apexInData.add(fakeApexData2);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ Set<ApexInfo> results = mInstallApexModuleTargetPreparer.getModulesToUninstall(
+ apexInData, testFiles, mMockDevice);
+ assertEquals(1, testFiles.size());
+ assertEquals(mFakeApex3, testFiles.get(0));
+ assertEquals(2, results.size());
+ results.containsAll(apexInData);
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ }
+
+ /**
+ * Test that it returns the correct files to be installed and uninstalled.
+ */
+ @Test
+ public void testGetModulesToUninstall_UninstallAndInstallFiles2() throws Exception {
+ Set<ApexInfo> apexInData = new HashSet<>();
+ List<File> testFiles = new ArrayList<>();
+ testFiles.add(mFakeApex2);
+ testFiles.add(mFakeApex3);
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ apexInData.add(fakeApexData);
+ apexInData.add(fakeApexData2);
+
+ EasyMock.replay(mMockDevice);
+ Set<ApexInfo> results =
+ mInstallApexModuleTargetPreparer.getModulesToUninstall(
+ apexInData, testFiles, mMockDevice);
+ assertEquals(1, testFiles.size());
+ assertEquals(mFakeApex3, testFiles.get(0));
+ assertEquals(1, results.size());
+ results.contains(fakeApexData);
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test the method behaves the same process when the files to be installed contain apk or apks.
+ */
+ @Test
+ public void testSetupAndTearDown_Optimize_APEXANDAPK_Reboot() throws Exception {
+ mSetter.setOptionValue("skip-apex-teardown", "true");
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
+ mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
+
+ mMockDevice.deleteFile(APEX_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(1);
+ CommandResult res = new CommandResult();
+ res.setStdout("test.apex");
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(new HashSet<>()).times(2);
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(
+ new HashSet<>(Arrays.asList(fakeApexData))).atLeastOnce();
+ mockSuccessfulInstallMultiPackageAndReboot();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APK_PACKAGE_NAME);
+ installableModules.add(APEX_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+ EasyMock.replay(mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test the method will optimize the process and it will not reboot because the files to be
+ * installed are already installed on the device.
+ */
+ @Test
+ public void testSetupAndTearDown_Optimize_MultipleAPEX_NoReboot() throws Exception {
+ mSetter.setOptionValue("skip-apex-teardown", "true");
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME);
+
+ Set<ApexInfo> apexInData = new HashSet<>();
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ apexInData.add(fakeApexData);
+ apexInData.add(fakeApexData2);
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(apexInData).times(2);
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ installableModules.add(APEX2_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+ EasyMock.replay(mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test the method will uninstall the unused files and install the required files for the
+ * current test, and finally reboot the device.
+ */
+ @Test
+ public void testSetupAndTearDown_Optimize_MultipleAPEX_UninstallThenInstallAndReboot()
+ throws Exception {
+ mSetter.setOptionValue("skip-apex-teardown", "true");
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME);
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(
+ new HashSet<>(Arrays.asList(fakeApexData))).times(2);
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(
+ new HashSet<>(Arrays.asList(fakeApexData2))).atLeastOnce();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX2_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+ EasyMock.expect(mMockDevice.uninstallPackage(EasyMock.anyObject()))
+ .andReturn(null)
+ .once();
+ mockSuccessfulInstallPackageAndReboot(mFakeApex2);
+ EasyMock.replay(mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test the method will uninstall the unused files for the current test, and finally reboot the
+ * device.
+ */
+ @Test
+ public void testSetupAndTearDown_Optimize_MultipleAPEX_UninstallAndReboot() throws Exception {
+ mSetter.setOptionValue("skip-apex-teardown", "true");
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME);
+
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(
+ new HashSet<>(Arrays.asList(fakeApexData, fakeApexData2))).times(2);
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX2_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+ EasyMock.expect(mMockDevice.uninstallPackage(EasyMock.anyObject()))
+ .andReturn(null)
+ .once();
+ mMockDevice.reboot();
+ EasyMock.expectLastCall().once();
+ EasyMock.replay(mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ EasyMock.verify(mMockDevice);
+ }
+
+ /**
+ * Test the method will install the required files for the current test, and finally reboot the
+ * device.
+ */
+ @Test
+ public void testSetupAndTearDown_Optimize_MultipleAPEX_Reboot() throws Exception {
+ mSetter.setOptionValue("skip-apex-teardown", "true");
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
+ mInstallApexModuleTargetPreparer.addTestFileName(APEX2_NAME);
+
+ Set<ApexInfo> apexInData = new HashSet<>();
+ ApexInfo fakeApexData =
+ new ApexInfo(
+ APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex");
+
+ ApexInfo fakeApexData2 =
+ new ApexInfo(
+ APEX2_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX2_PACKAGE_NAME@1.apex");
+
+ apexInData.add(fakeApexData);
+ apexInData.add(fakeApexData2);
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(
+ new HashSet<>(Arrays.asList(fakeApexData))).times(2);
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(apexInData).atLeastOnce();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ installableModules.add(APEX2_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+ mockSuccessfulInstallPackageAndReboot(mFakeApex2);
+ EasyMock.replay(mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ EasyMock.verify(mMockDevice);
+ }
+
@Test
public void testSetupSuccess_removeExistingStagedApexSuccess() throws Exception {
mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
@@ -781,19 +1134,7 @@ public class InstallApexModuleTargetPreparerTest {
@Test
public void testSetupAndTearDown() throws Exception {
mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
- mMockDevice.deleteFile(APEX_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- CommandResult res = new CommandResult();
- res.setStdout("test.apex");
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
- mMockDevice.reboot();
- EasyMock.expectLastCall();
+ mockCleanInstalledApexPackagesAndReboot();
mockSuccessfulInstallPackageAndReboot(mFakeApex);
Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
activatedApex.add(
@@ -834,19 +1175,7 @@ public class InstallApexModuleTargetPreparerTest {
public void testSetupAndTearDown_MultiInstall() throws Exception {
mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
- mMockDevice.deleteFile(APEX_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- CommandResult res = new CommandResult();
- res.setStdout("test.apex");
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
- mMockDevice.reboot();
- EasyMock.expectLastCall();
+ mockCleanInstalledApexPackagesAndReboot();
mockSuccessfulInstallMultiPackageAndReboot();
Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
activatedApex.add(
@@ -891,22 +1220,7 @@ public class InstallApexModuleTargetPreparerTest {
mBundletoolJar = File.createTempFile("bundletool", ".jar");
File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks);
try {
- mMockDevice.deleteFile(APEX_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- CommandResult res = new CommandResult();
- res.setStdout("test.apex");
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR))
- .andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR))
- .andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR))
- .andReturn(res);
- mMockDevice.reboot();
- EasyMock.expectLastCall();
+ mockCleanInstalledApexPackagesAndReboot();
when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class)))
.thenReturn("serial.json");
@@ -998,6 +1312,340 @@ public class InstallApexModuleTargetPreparerTest {
}
@Test
+ public void testInstallUsingBundletool_AbsolutePath() throws Exception {
+ mInstallApexModuleTargetPreparer.addTestFileName(SPLIT_APEX_APKS_NAME);
+ mInstallApexModuleTargetPreparer.addTestFileName(SPLIT_APK__APKS_NAME);
+ mFakeApexApks = File.createTempFile("fakeApex", ".apks");
+ mFakeApkApks = File.createTempFile("fakeApk", ".apks");
+
+ File fakeSplitApexApks = File.createTempFile("ApexSplits", "");
+ fakeSplitApexApks.delete();
+ fakeSplitApexApks.mkdir();
+ File splitApex = File.createTempFile("fakeSplitApex", ".apex", fakeSplitApexApks);
+
+ File fakeSplitApkApks = File.createTempFile("ApkSplits", "");
+ fakeSplitApkApks.delete();
+ fakeSplitApkApks.mkdir();
+ File splitApk1 = File.createTempFile("fakeSplitApk1", ".apk", fakeSplitApkApks);
+ mBundletoolJar = File.createTempFile("/fake/absolute/path/bundletool", ".jar");
+ File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks);
+ try {
+ mockCleanInstalledApexPackagesAndReboot();
+ when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class)))
+ .thenReturn("serial.json");
+
+ assertTrue(fakeSplitApexApks != null);
+ assertTrue(fakeSplitApkApks != null);
+ assertTrue(mFakeApexApks != null);
+ assertTrue(mFakeApkApks != null);
+ assertEquals(1, fakeSplitApexApks.listFiles().length);
+ assertEquals(2, fakeSplitApkApks.listFiles().length);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApexApks);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApkApks);
+
+ mMockDevice.waitForDeviceAvailable();
+
+ List<String> trainInstallCmd = new ArrayList<>();
+ trainInstallCmd.add("install-multi-package");
+ trainInstallCmd.add(splitApex.getAbsolutePath());
+ String cmd = "";
+ for (File f : fakeSplitApkApks.listFiles()) {
+ if (!cmd.isEmpty()) {
+ cmd += ":" + f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ } else {
+ cmd += f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ }
+ }
+ trainInstallCmd.add(cmd);
+ EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+ .andReturn("Success")
+ .once();
+ mMockDevice.reboot();
+ Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
+ activatedApex.add(
+ new ApexInfo(
+ SPLIT_APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"));
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex).times(3);
+ EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME))
+ .andReturn(null)
+ .once();
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ installableModules.add(SPLIT_APK_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ 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
+ // installation.
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ } finally {
+ FileUtil.deleteFile(mFakeApexApks);
+ FileUtil.deleteFile(mFakeApkApks);
+ FileUtil.recursiveDelete(fakeSplitApexApks);
+ FileUtil.deleteFile(fakeSplitApexApks);
+ FileUtil.recursiveDelete(fakeSplitApkApks);
+ FileUtil.deleteFile(fakeSplitApkApks);
+ FileUtil.deleteFile(mBundletoolJar);
+ }
+ }
+
+ @Test
+ public void testInstallUsingBundletool_TrainFolder() throws Exception {
+ File trainFolder = File.createTempFile("tmpTrain", "");
+ trainFolder.delete();
+ trainFolder.mkdir();
+ mSetter.setOptionValue("train-path", trainFolder.getAbsolutePath());
+ mFakeApexApks = File.createTempFile("fakeApex", ".apks", trainFolder);
+ mFakeApkApks = File.createTempFile("fakeApk", ".apks", trainFolder);
+
+ File fakeSplitApexApks = File.createTempFile("ApexSplits", "");
+ fakeSplitApexApks.delete();
+ fakeSplitApexApks.mkdir();
+ File splitApex = File.createTempFile("fakeSplitApex", ".apex", fakeSplitApexApks);
+
+ File fakeSplitApkApks = File.createTempFile("ApkSplits", "");
+ fakeSplitApkApks.delete();
+ fakeSplitApkApks.mkdir();
+ File splitApk1 = File.createTempFile("fakeSplitApk1", ".apk", fakeSplitApkApks);
+ mBundletoolJar = File.createTempFile("bundletool", ".jar");
+ File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks);
+ try {
+ mockCleanInstalledApexPackagesAndReboot();
+ when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class)))
+ .thenReturn("serial.json");
+
+ assertTrue(fakeSplitApexApks != null);
+ assertTrue(fakeSplitApkApks != null);
+ assertTrue(mFakeApexApks != null);
+ assertTrue(mFakeApkApks != null);
+ assertEquals(1, fakeSplitApexApks.listFiles().length);
+ assertEquals(2, fakeSplitApkApks.listFiles().length);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApexApks);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApkApks);
+
+ mMockDevice.waitForDeviceAvailable();
+
+ List<String> trainInstallCmd = new ArrayList<>();
+ trainInstallCmd.add("install-multi-package");
+ trainInstallCmd.add(splitApex.getAbsolutePath());
+ String cmd = "";
+ for (File f : fakeSplitApkApks.listFiles()) {
+ if (!cmd.isEmpty()) {
+ cmd += ":" + f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ } else {
+ cmd += f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ }
+ }
+ trainInstallCmd.add(cmd);
+ EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+ .andReturn("Success")
+ .once();
+ mMockDevice.reboot();
+ Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
+ activatedApex.add(
+ new ApexInfo(
+ SPLIT_APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"));
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex).times(3);
+ EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME))
+ .andReturn(null)
+ .once();
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ installableModules.add(SPLIT_APK_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ 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
+ // installation.
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ } finally {
+ FileUtil.recursiveDelete(trainFolder);
+ FileUtil.deleteFile(trainFolder);
+ FileUtil.deleteFile(mFakeApexApks);
+ FileUtil.deleteFile(mFakeApkApks);
+ FileUtil.recursiveDelete(fakeSplitApexApks);
+ FileUtil.deleteFile(fakeSplitApexApks);
+ FileUtil.recursiveDelete(fakeSplitApkApks);
+ FileUtil.deleteFile(fakeSplitApkApks);
+ FileUtil.deleteFile(mBundletoolJar);
+ }
+ }
+
+ @Test
+ public void testInstallUsingBundletool_AllFilesHaveAbsolutePath() throws Exception {
+ mFakeApexApks = File.createTempFile("fakeApex", ".apks");
+ mFakeApkApks = File.createTempFile("fakeApk", ".apks");
+ mInstallApexModuleTargetPreparer.addTestFile(mFakeApexApks);
+ mInstallApexModuleTargetPreparer.addTestFile(mFakeApkApks);
+
+ File fakeSplitApexApks = File.createTempFile("ApexSplits", "");
+ fakeSplitApexApks.delete();
+ fakeSplitApexApks.mkdir();
+ File splitApex = File.createTempFile("fakeSplitApex", ".apex", fakeSplitApexApks);
+
+ File fakeSplitApkApks = File.createTempFile("ApkSplits", "");
+ fakeSplitApkApks.delete();
+ fakeSplitApkApks.mkdir();
+ File splitApk1 = File.createTempFile("fakeSplitApk1", ".apk", fakeSplitApkApks);
+ mBundletoolJar = File.createTempFile("/fake/absolute/path/bundletool", ".jar");
+ File splitApk2 = File.createTempFile("fakeSplitApk2", ".apk", fakeSplitApkApks);
+ try {
+ mockCleanInstalledApexPackagesAndReboot();
+ when(mMockBundletoolUtil.generateDeviceSpecFile(Mockito.any(ITestDevice.class)))
+ .thenReturn("serial.json");
+
+ assertTrue(fakeSplitApexApks != null);
+ assertTrue(fakeSplitApkApks != null);
+ assertTrue(mFakeApexApks != null);
+ assertTrue(mFakeApkApks != null);
+ assertEquals(1, fakeSplitApexApks.listFiles().length);
+ assertEquals(2, fakeSplitApkApks.listFiles().length);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApexApks);
+
+ when(mMockBundletoolUtil.extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class)))
+ .thenReturn(fakeSplitApkApks);
+
+ mMockDevice.waitForDeviceAvailable();
+
+ List<String> trainInstallCmd = new ArrayList<>();
+ trainInstallCmd.add("install-multi-package");
+ trainInstallCmd.add(splitApex.getAbsolutePath());
+ String cmd = "";
+ for (File f : fakeSplitApkApks.listFiles()) {
+ if (!cmd.isEmpty()) {
+ cmd += ":" + f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ } else {
+ cmd += f.getParentFile().getAbsolutePath() + "/" + f.getName();
+ }
+ }
+ trainInstallCmd.add(cmd);
+ EasyMock.expect(mMockDevice.executeAdbCommand(trainInstallCmd.toArray(new String[0])))
+ .andReturn("Success")
+ .once();
+ mMockDevice.reboot();
+ Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
+ activatedApex.add(
+ new ApexInfo(
+ SPLIT_APEX_PACKAGE_NAME,
+ 1,
+ "/data/apex/active/com.android.FAKE_APEX_PACKAGE_NAME@1.apex"));
+ EasyMock.expect(mMockDevice.getActiveApexes()).andReturn(activatedApex).times(3);
+ EasyMock.expect(mMockDevice.uninstallPackage(SPLIT_APK_PACKAGE_NAME))
+ .andReturn(null)
+ .once();
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ Set<String> installableModules = new HashSet<>();
+ installableModules.add(APEX_PACKAGE_NAME);
+ installableModules.add(SPLIT_APK_PACKAGE_NAME);
+ EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
+
+ EasyMock.replay(mMockBuildInfo, mMockDevice);
+ mInstallApexModuleTargetPreparer.setUp(mTestInfo);
+ mInstallApexModuleTargetPreparer.tearDown(mTestInfo, null);
+ 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
+ // installation.
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApexApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ Mockito.verify(mMockBundletoolUtil, times(2))
+ .extractSplitsFromApks(
+ Mockito.eq(mFakeApkApks),
+ Mockito.anyString(),
+ Mockito.any(ITestDevice.class),
+ Mockito.any(IBuildInfo.class));
+ EasyMock.verify(mMockBuildInfo, mMockDevice);
+ } finally {
+ FileUtil.deleteFile(mFakeApexApks);
+ FileUtil.deleteFile(mFakeApkApks);
+ FileUtil.recursiveDelete(fakeSplitApexApks);
+ FileUtil.deleteFile(fakeSplitApexApks);
+ FileUtil.recursiveDelete(fakeSplitApkApks);
+ FileUtil.deleteFile(fakeSplitApkApks);
+ FileUtil.deleteFile(mBundletoolJar);
+ }
+ }
+
+ @Test
public void testInstallUsingBundletool_skipModuleNotPreloaded() throws Exception {
mSetter.setOptionValue("ignore-if-module-not-preloaded", "true");
mInstallApexModuleTargetPreparer.addTestFileName(SPLIT_APEX_APKS_NAME);
@@ -1069,6 +1717,7 @@ public class InstallApexModuleTargetPreparerTest {
.once();
Set<String> installableModules = new HashSet<>();
installableModules.add(SPLIT_APK_PACKAGE_NAME);
+
EasyMock.expect(mMockDevice.getInstalledPackageNames()).andReturn(installableModules);
EasyMock.replay(mMockBuildInfo, mMockDevice);
@@ -1148,6 +1797,22 @@ public class InstallApexModuleTargetPreparerTest {
EasyMock.expectLastCall().once();
}
+ private void mockCleanInstalledApexPackagesAndReboot() throws DeviceNotAvailableException {
+ mMockDevice.deleteFile(APEX_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(2);
+ mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(2);
+ mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
+ EasyMock.expectLastCall().times(2);
+ CommandResult res = new CommandResult();
+ res.setStdout("test.apex");
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
+ EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
+ mMockDevice.reboot();
+ EasyMock.expectLastCall();
+ }
+
@Test
public void testSetupAndTearDown_noModulesPreloaded() throws Exception {
mSetter.setOptionValue("ignore-if-module-not-preloaded", "true");
@@ -1183,19 +1848,7 @@ public class InstallApexModuleTargetPreparerTest {
mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
// Module not preloaded.
mInstallApexModuleTargetPreparer.addTestFileName(APK2_NAME);
- mMockDevice.deleteFile(APEX_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- CommandResult res = new CommandResult();
- res.setStdout("test.apex");
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
- mMockDevice.reboot();
- EasyMock.expectLastCall();
+ mockCleanInstalledApexPackagesAndReboot();
mockSuccessfulInstallMultiPackageAndReboot();
Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
activatedApex.add(
@@ -1225,19 +1878,7 @@ public class InstallApexModuleTargetPreparerTest {
mInstallApexModuleTargetPreparer.addTestFileName(APEX_NAME);
mInstallApexModuleTargetPreparer.addTestFileName(APK_NAME);
mInstallApexModuleTargetPreparer.addTestFileName(APK2_NAME);
- mMockDevice.deleteFile(APEX_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(SESSION_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- mMockDevice.deleteFile(STAGING_DATA_DIR + "*");
- EasyMock.expectLastCall().times(2);
- CommandResult res = new CommandResult();
- res.setStdout("test.apex");
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + APEX_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + SESSION_DATA_DIR)).andReturn(res);
- EasyMock.expect(mMockDevice.executeShellV2Command("ls " + STAGING_DATA_DIR)).andReturn(res);
- mMockDevice.reboot();
- EasyMock.expectLastCall();
+ mockCleanInstalledApexPackagesAndReboot();
Set<ApexInfo> activatedApex = new HashSet<ApexInfo>();
activatedApex.add(
new ApexInfo(
diff --git a/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java b/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
index 16ec66f9c..853803c3e 100644
--- a/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
@@ -42,6 +42,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import java.io.IOException;
import java.io.File;
import java.util.Set;
@@ -645,6 +646,53 @@ public class PushFilePreparerTest {
}
}
+ /**
+ * Test that if multiple files exists after delayed partial download, push the one with matching
+ * ABI.
+ */
+ @Test
+ public void testPush_moduleName_files_abi_delayedDownload() throws Exception {
+ mOptionSetter.setOptionValue("push", "file->/data/local/tmp/file");
+ mPreparer.setAbi(new Abi("x86", "32"));
+
+ mPreparer.setInvocationContext(createModuleWithName("aaaaa"));
+ File tmpFolder = FileUtil.createTempDir("push-file-tests-dir");
+ IDeviceBuildInfo info =
+ new DeviceBuildInfo() {
+ @Override
+ public File stageRemoteFile(String fileName, File workingDir) {
+ try {
+ File file_64 =
+ new File(tmpFolder, "target/testcases/aaaaa/x86_64/file");
+ FileUtil.mkdirsRWX(file_64.getParentFile());
+ file_64.createNewFile();
+ File file_32 = new File(tmpFolder, "target/testcases/aaaaa/x86/file");
+ FileUtil.mkdirsRWX(file_32.getParentFile());
+ file_32.createNewFile();
+ // Return the file with mismatched ABI.
+ return file_64;
+ } catch (IOException e) {
+ return null;
+ }
+ }
+ };
+ try {
+ info.setFile(BuildInfoFileKey.TESTDIR_IMAGE, tmpFolder, "v1");
+ EasyMock.expect(
+ mMockDevice.pushFile(
+ EasyMock.eq(
+ new File(tmpFolder, "target/testcases/aaaaa/x86/file")),
+ EasyMock.eq("/data/local/tmp/file")))
+ .andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
+ EasyMock.replay(mMockDevice);
+ mPreparer.setUp(mTestInfo);
+ EasyMock.verify(mMockDevice);
+ } finally {
+ FileUtil.recursiveDelete(tmpFolder);
+ }
+ }
+
@Test
public void testPush_moduleName_ignored() throws Exception {
mOptionSetter.setOptionValue("push", "lib64->/data/local/tmp/lib");
diff --git a/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java b/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java
index 133bad598..20754fc89 100644
--- a/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/PythonVirtualenvPreparerTest.java
@@ -21,7 +21,10 @@ import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createMock;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.replay;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
import com.android.tradefed.build.BuildInfo;
import com.android.tradefed.build.IBuildInfo;
@@ -30,6 +33,8 @@ import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.IRunUtil;
+import com.google.common.base.Throwables;
+
import junit.framework.TestCase;
import java.io.File;
@@ -105,4 +110,44 @@ public class PythonVirtualenvPreparerTest extends TestCase {
mPreparer.installDeps(buildInfo, mMockDevice);
assertTrue(buildInfo.getFile("PYTHONPATH") == null);
}
+
+ public void testStartVirtualenv_throwTSE_whenVirtualenvNotFound() throws Exception {
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ result.setStdout("bash: virtualenv: command not found");
+ expect(mMockRunUtil.runTimedCmd(anyLong(), eq("virtualenv"), eq("--version")))
+ .andReturn(result);
+ replay(mMockRunUtil);
+
+ try {
+ mPreparer.startVirtualenv(new BuildInfo(), mMockDevice);
+ fail("startVirtualenv succeeded despite a failed command");
+ } catch (TargetSetupError e) {
+ assertThat(
+ String.format(
+ "An unexpected exception was thrown:\n%s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ containsString("virtualenv is not installed."));
+ }
+ }
+
+ public void testStartVirtualenv_throwTSE_whenVirtualenvIsTooOld() throws Exception {
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ result.setStdout("virtualenv 16.7.10 from /path/to/site-packages/virtualenv/__init__.py");
+ expect(mMockRunUtil.runTimedCmd(anyLong(), eq("virtualenv"), eq("--version")))
+ .andReturn(result);
+ replay(mMockRunUtil);
+
+ try {
+ mPreparer.startVirtualenv(new BuildInfo(), mMockDevice);
+ fail("startVirtualenv succeeded despite a failed command");
+ } catch (TargetSetupError e) {
+ assertEquals(
+ String.format(
+ "An unexpected exception was thrown:\n%s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ "virtualenv is too old. Required: >=20.0.1, yours: 16.7.10");
+ }
+ }
} \ No newline at end of file
diff --git a/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java
index 3f7ae6ddd..a8f95cf8d 100644
--- a/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/RunHostCommandTargetPreparerTest.java
@@ -20,24 +20,29 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.android.tradefed.config.OptionSetter;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.invoker.IInvocationContext;
-import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.device.IDeviceManager;
import com.android.tradefed.invoker.TestInformation;
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.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Answers;
+import org.mockito.InOrder;
import org.mockito.Mock;
-import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
import java.io.OutputStream;
import java.util.Arrays;
@@ -45,21 +50,24 @@ import java.util.Collections;
import java.util.List;
/** Unit test for {@link RunHostCommandTargetPreparer}. */
-@RunWith(MockitoJUnitRunner.class)
+@RunWith(JUnit4.class)
public final class RunHostCommandTargetPreparerTest {
private static final String DEVICE_SERIAL = "123456";
private static final String FULL_COMMAND = "command \t\t\t \t argument $SERIAL";
- @Mock private ITestDevice mDevice;
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private TestInformation mTestInfo;
+
@Mock private RunHostCommandTargetPreparer.BgCommandLog mBgCommandLog;
@Mock private IRunUtil mRunUtil;
+ @Mock private IDeviceManager mDeviceManager;
private RunHostCommandTargetPreparer mPreparer;
- private TestInformation mTestInfo;
@Before
public void setUp() {
- when(mDevice.getSerialNumber()).thenReturn(DEVICE_SERIAL);
mPreparer =
new RunHostCommandTargetPreparer() {
@Override
@@ -68,13 +76,16 @@ public final class RunHostCommandTargetPreparerTest {
}
@Override
+ IDeviceManager getDeviceManager() {
+ return mDeviceManager;
+ }
+
+ @Override
protected List<BgCommandLog> createBgCommandLogs() {
return Collections.singletonList(mBgCommandLog);
}
};
- IInvocationContext context = new InvocationContext();
- context.addAllocatedDevice("device", mDevice);
- mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build();
+ when(mTestInfo.getDevice().getSerialNumber()).thenReturn(DEVICE_SERIAL);
}
@Test
@@ -89,6 +100,10 @@ public final class RunHostCommandTargetPreparerTest {
// Verify timeout and command (split, removed whitespace, and device serial)
mPreparer.setUp(mTestInfo);
verify(mRunUtil).runTimedCmd(eq(10L), eq("command"), eq("argument"), eq(DEVICE_SERIAL));
+
+ // No flashing permit taken/returned by default
+ verify(mDeviceManager, never()).takeFlashingPermit();
+ verify(mDeviceManager, never()).returnFlashingPermit();
}
@Test
@@ -120,6 +135,24 @@ public final class RunHostCommandTargetPreparerTest {
}
@Test
+ public void testSetUp_flashingPermit() throws Exception {
+ OptionSetter optionSetter = new OptionSetter(mPreparer);
+ optionSetter.setOptionValue("host-setup-command", FULL_COMMAND);
+ optionSetter.setOptionValue("use-flashing-permit", "true");
+
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ when(mRunUtil.runTimedCmd(anyLong(), any())).thenReturn(result);
+
+ // Verify command ran with flashing permit
+ mPreparer.setUp(mTestInfo);
+ InOrder inOrder = inOrder(mRunUtil, mDeviceManager);
+ inOrder.verify(mDeviceManager).takeFlashingPermit();
+ inOrder.verify(mRunUtil)
+ .runTimedCmd(anyLong(), eq("command"), eq("argument"), eq(DEVICE_SERIAL));
+ inOrder.verify(mDeviceManager).returnFlashingPermit();
+ }
+
+ @Test
public void testTearDown() throws Exception {
OptionSetter optionSetter = new OptionSetter(mPreparer);
optionSetter.setOptionValue("host-teardown-command", FULL_COMMAND);
@@ -131,6 +164,10 @@ public final class RunHostCommandTargetPreparerTest {
// Verify timeout and command (split, removed whitespace, and device serial)
mPreparer.tearDown(mTestInfo, null);
verify(mRunUtil).runTimedCmd(eq(10L), eq("command"), eq("argument"), eq(DEVICE_SERIAL));
+
+ // No flashing permit taken/returned by default
+ verify(mDeviceManager, never()).takeFlashingPermit();
+ verify(mDeviceManager, never()).returnFlashingPermit();
}
@Test
@@ -146,6 +183,24 @@ public final class RunHostCommandTargetPreparerTest {
}
@Test
+ public void testTearDown_flashingPermit() throws Exception {
+ OptionSetter optionSetter = new OptionSetter(mPreparer);
+ optionSetter.setOptionValue("host-teardown-command", FULL_COMMAND);
+ optionSetter.setOptionValue("use-flashing-permit", "true");
+
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ when(mRunUtil.runTimedCmd(anyLong(), any())).thenReturn(result);
+
+ // Verify command ran with flashing permit
+ mPreparer.tearDown(mTestInfo, null);
+ InOrder inOrder = inOrder(mRunUtil, mDeviceManager);
+ inOrder.verify(mDeviceManager).takeFlashingPermit();
+ inOrder.verify(mRunUtil)
+ .runTimedCmd(anyLong(), eq("command"), eq("argument"), eq(DEVICE_SERIAL));
+ inOrder.verify(mDeviceManager).returnFlashingPermit();
+ }
+
+ @Test
public void testBgCommand() throws Exception {
OptionSetter optionSetter = new OptionSetter(mPreparer);
optionSetter.setOptionValue("host-background-command", FULL_COMMAND);
diff --git a/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java
index a623201f7..236498a11 100644
--- a/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/RunHostScriptTargetPreparerTest.java
@@ -21,6 +21,7 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -42,6 +43,7 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.mockito.Answers;
+import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -117,6 +119,9 @@ public final class RunHostScriptTargetPreparerTest {
verify(mRunUtil).runTimedCmd(10L, mScriptFile.getAbsolutePath());
// Verify that script is executable
assertTrue(mScriptFile.canExecute());
+ // No flashing permit taken/returned by default
+ verify(mDeviceManager, never()).takeFlashingPermit();
+ verify(mDeviceManager, never()).returnFlashingPermit();
}
@Test
@@ -169,4 +174,16 @@ public final class RunHostScriptTargetPreparerTest {
mPreparer.setUp(mTestInfo);
verify(mRunUtil).setEnvVariable("PATH", expectedPath);
}
+
+ @Test
+ public void testSetUp_flashingPermit() throws Exception {
+ mOptionSetter.setOptionValue("script-file", mScriptFile.getAbsolutePath());
+ mOptionSetter.setOptionValue("use-flashing-permit", "true");
+ // Verify script executed with flashing permit
+ mPreparer.setUp(mTestInfo);
+ InOrder inOrder = inOrder(mRunUtil, mDeviceManager);
+ inOrder.verify(mDeviceManager).takeFlashingPermit();
+ inOrder.verify(mRunUtil).runTimedCmd(anyLong(), eq(mScriptFile.getAbsolutePath()));
+ inOrder.verify(mDeviceManager).returnFlashingPermit();
+ }
}
diff --git a/tests/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java
new file mode 100644
index 000000000..14e324b94
--- /dev/null
+++ b/tests/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparerTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 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.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.UserInfo;
+import com.android.tradefed.invoker.TestInformation;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public class RunOnSecondaryUserTargetPreparerTest {
+
+ private static final String CREATED_USER_2_MESSAGE = "Created user id 2";
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private TestInformation mTestInfo;
+
+ private RunOnSecondaryUserTargetPreparer mPreparer;
+ private OptionSetter mOptionSetter;
+
+ @Before
+ public void setUp() throws Exception {
+ mPreparer = new RunOnSecondaryUserTargetPreparer();
+ mOptionSetter = new OptionSetter(mPreparer);
+ }
+
+ @Test
+ public void setUp_createsAndStartsSecondaryUser() throws Exception {
+ String expectedCreateUserCommand = "pm create-user secondary";
+ String expectedStartUserCommand = "am start-user -w 2";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_2_MESSAGE);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice()).executeShellCommand(expectedCreateUserCommand);
+ verify(mTestInfo.getDevice()).executeShellCommand(expectedStartUserCommand);
+ }
+
+ @Test
+ public void setUp_secondaryUserAlreadyExists_doesNotCreateSecondaryUser() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(2, new UserInfo(2, "secondary", /* flag= */ 0, /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice(), never()).executeShellCommand(any());
+ }
+
+ @Test
+ public void setUp_secondaryUserAlreadyExists_runsTestAsExistingUser() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(3, new UserInfo(3, "secondary", /* flag= */ 0, /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.properties())
+ .put(RunOnWorkProfileTargetPreparer.RUN_TESTS_AS_USER_KEY, "3");
+ }
+
+ @Test
+ public void setUp_setsRunTestsAsUser() throws Exception {
+ String expectedCreateUserCommand = "pm create-user secondary";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_2_MESSAGE);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.properties())
+ .put(RunOnSecondaryUserTargetPreparer.RUN_TESTS_AS_USER_KEY, "2");
+ }
+
+ @Test
+ public void setUp_secondaryUserAlreadyExists_installsPackagesInExistingUser() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(3, new UserInfo(3, "secondary", /* flag= */ 0, /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+ mOptionSetter.setOptionValue(
+ RunOnWorkProfileTargetPreparer.TEST_PACKAGE_NAME_OPTION, "com.android.testpackage");
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice())
+ .executeShellCommand("pm install-existing --user 3 com.android.testpackage");
+ }
+
+ @Test
+ public void setUp_installsPackagesInSecondaryUser() throws Exception {
+ String expectedCreateUserCommand = "pm create-user secondary";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_2_MESSAGE);
+ mOptionSetter.setOptionValue(
+ RunOnSecondaryUserTargetPreparer.TEST_PACKAGE_NAME_OPTION,
+ "com.android.testpackage");
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice())
+ .executeShellCommand("pm install-existing --user 2 com.android.testpackage");
+ }
+
+ @Test
+ public void setUp_workProfileAlreadyExists_disablesTearDown() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(3, new UserInfo(3, "secondary", /* flag= */ 0, /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+ mOptionSetter.setOptionValue("disable-tear-down", "false");
+
+ mPreparer.setUp(mTestInfo);
+
+ assertThat(mPreparer.isTearDownDisabled()).isTrue();
+ }
+
+ @Test
+ public void setUp_doesNotDisableTearDown() throws Exception {
+ String expectedCreateUserCommand = "pm create-user secondary";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_2_MESSAGE);
+ mOptionSetter.setOptionValue("disable-tear-down", "false");
+
+ mPreparer.setUp(mTestInfo);
+
+ assertThat(mPreparer.isTearDownDisabled()).isFalse();
+ }
+
+ @Test
+ public void tearDown_removesSecondaryUser() throws Exception {
+ when(mTestInfo.properties().get(RunOnSecondaryUserTargetPreparer.RUN_TESTS_AS_USER_KEY))
+ .thenReturn("2");
+
+ mPreparer.tearDown(mTestInfo, /* throwable= */ null);
+
+ verify(mTestInfo.getDevice()).removeUser(2);
+ }
+}
diff --git a/tests/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java b/tests/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java
new file mode 100644
index 000000000..5845cd275
--- /dev/null
+++ b/tests/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparerTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2020 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.RunOnWorkProfileTargetPreparer.RUN_TESTS_AS_USER_KEY;
+import static com.android.tradefed.targetprep.RunOnWorkProfileTargetPreparer.TEST_PACKAGE_NAME_OPTION;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.UserInfo;
+import com.android.tradefed.invoker.TestInformation;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Answers;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RunWith(JUnit4.class)
+public class RunOnWorkProfileTargetPreparerTest {
+ private static final String CREATED_USER_10_MESSAGE = "Created user id 10";
+
+ @Rule public final MockitoRule mockito = MockitoJUnit.rule();
+
+ @Mock(answer = Answers.RETURNS_DEEP_STUBS)
+ private TestInformation mTestInfo;
+
+ private RunOnWorkProfileTargetPreparer mPreparer;
+ private OptionSetter mOptionSetter;
+
+ @Before
+ public void setUp() throws Exception {
+ mPreparer = new RunOnWorkProfileTargetPreparer();
+ mOptionSetter = new OptionSetter(mPreparer);
+ }
+
+ @Test
+ public void setUp_createsAndStartsWorkProfile() throws Exception {
+ String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work";
+ String expectedStartUserCommand = "am start-user -w 10";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_10_MESSAGE);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice()).executeShellCommand(expectedCreateUserCommand);
+ verify(mTestInfo.getDevice()).executeShellCommand(expectedStartUserCommand);
+ }
+
+ @Test
+ public void setUp_workProfileAlreadyExists_doesNotCreateWorkProfile() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(
+ 10,
+ new UserInfo(
+ 10,
+ "work",
+ /* flag= */ UserInfo.FLAG_MANAGED_PROFILE,
+ /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice(), never()).executeShellCommand(any());
+ }
+
+ @Test
+ public void setUp_nonZeroCurrentUser_createsWorkProfileForCorrectUser() throws Exception {
+ when(mTestInfo.getDevice().getCurrentUser()).thenReturn(1);
+ String expectedCreateUserCommand = "pm create-user --profileOf 1 --managed work";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_10_MESSAGE);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice()).executeShellCommand(expectedCreateUserCommand);
+ }
+
+ @Test
+ public void setUp_workProfileAlreadyExists_runsTestAsExistingUser() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(
+ 11,
+ new UserInfo(
+ 11,
+ "work",
+ /* flag= */ UserInfo.FLAG_MANAGED_PROFILE,
+ /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.properties()).put(RUN_TESTS_AS_USER_KEY, "11");
+ }
+
+ @Test
+ public void setUp_setsRunTestsAsUser() throws Exception {
+ String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_10_MESSAGE);
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.properties()).put(RUN_TESTS_AS_USER_KEY, "10");
+ }
+
+ @Test
+ public void setUp_workProfileAlreadyExists_installsPackagesInExistingUser() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(
+ 11,
+ new UserInfo(
+ 11,
+ "work",
+ /* flag= */ UserInfo.FLAG_MANAGED_PROFILE,
+ /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+ mOptionSetter.setOptionValue(TEST_PACKAGE_NAME_OPTION, "com.android.testpackage");
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice())
+ .executeShellCommand("pm install-existing --user 11 com.android.testpackage");
+ }
+
+ @Test
+ public void setUp_installsPackagesInWorkUser() throws Exception {
+ String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_10_MESSAGE);
+ mOptionSetter.setOptionValue(TEST_PACKAGE_NAME_OPTION, "com.android.testpackage");
+
+ mPreparer.setUp(mTestInfo);
+
+ verify(mTestInfo.getDevice())
+ .executeShellCommand("pm install-existing --user 10 com.android.testpackage");
+ }
+
+ @Test
+ public void setUp_workProfileAlreadyExists_disablesTearDown() throws Exception {
+ Map<Integer, UserInfo> userInfos = new HashMap<>();
+ userInfos.put(
+ 11,
+ new UserInfo(
+ 11,
+ "work",
+ /* flag= */ UserInfo.FLAG_MANAGED_PROFILE,
+ /* isRunning= */ true));
+ when(mTestInfo.getDevice().getUserInfos()).thenReturn(userInfos);
+ mOptionSetter.setOptionValue("disable-tear-down", "false");
+
+ mPreparer.setUp(mTestInfo);
+
+ assertThat(mPreparer.isTearDownDisabled()).isTrue();
+ }
+
+ @Test
+ public void setUp_doesNotDisableTearDown() throws Exception {
+ String expectedCreateUserCommand = "pm create-user --profileOf 0 --managed work";
+ when(mTestInfo.getDevice().executeShellCommand(expectedCreateUserCommand))
+ .thenReturn(CREATED_USER_10_MESSAGE);
+ mOptionSetter.setOptionValue("disable-tear-down", "false");
+
+ mPreparer.setUp(mTestInfo);
+
+ assertThat(mPreparer.isTearDownDisabled()).isFalse();
+ }
+
+ @Test
+ public void tearDown_removesWorkUser() throws Exception {
+ when(mTestInfo.properties().get(RUN_TESTS_AS_USER_KEY)).thenReturn("10");
+
+ mPreparer.tearDown(mTestInfo, /* throwable= */ null);
+
+ verify(mTestInfo.getDevice()).removeUser(10);
+ }
+}
diff --git a/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java b/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java
index 97dbc2bbe..7fa4796df 100644
--- a/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java
+++ b/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java
@@ -16,6 +16,7 @@
package com.android.tradefed.targetprep;
+import static com.android.tradefed.targetprep.TestAppInstallSetup.CHECK_MIN_SDK_OPTION;
import static com.android.tradefed.targetprep.TestAppInstallSetup.TEST_FILE_NAME_OPTION;
import static com.android.tradefed.targetprep.TestAppInstallSetup.THROW_IF_NOT_FOUND_OPTION;
@@ -23,8 +24,13 @@ import static com.google.common.truth.Truth.assertThat;
import static org.easymock.EasyMock.anyBoolean;
import static org.easymock.EasyMock.anyObject;
+
+import static org.mockito.Mockito.doReturn;
+
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.mockito.Mockito.times;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.command.remote.DeviceDescriptor;
@@ -36,6 +42,7 @@ import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.testtype.Abi;
+import com.android.tradefed.util.AaptParser;
import com.android.tradefed.util.FileUtil;
import com.google.common.collect.ImmutableMap;
@@ -47,12 +54,14 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -68,6 +77,7 @@ public class TestAppInstallSetupTest {
private File fakeApk;
private File fakeApk2;
private File mFakeBuildApk;
+ private AaptParser mMockAaptParser;
private TestAppInstallSetup mPrep;
private TestInformation mTestInfo;
private IDeviceBuildInfo mMockBuildInfo;
@@ -125,6 +135,7 @@ public class TestAppInstallSetupTest {
mTestSplitApkFiles.add(fakeApk2);
mMockBuildInfo = EasyMock.createMock(IDeviceBuildInfo.class);
+ mMockAaptParser = Mockito.mock(AaptParser.class);
mMockTestDevice = EasyMock.createMock(ITestDevice.class);
EasyMock.expect(mMockTestDevice.getSerialNumber()).andStubReturn(SERIAL);
EasyMock.expect(mMockTestDevice.getDeviceDescriptor()).andStubReturn(null);
@@ -572,6 +583,108 @@ public class TestAppInstallSetupTest {
assertThat(installs).containsExactly(ImmutableSet.of(apkFile1, apkFile2));
}
+ /**
+ * Tests that we throw exception with check-min-sdk when file fails to parse
+ *
+ * @throws Exception the expected exception
+ */
+ @Test
+ public void testResolveApkFiles_checkMinSdk_failParsing() throws Exception {
+ mPrep =
+ new TestAppInstallSetup() {
+ @Override
+ AaptParser doAaptParse(File apkFile) {
+ return null;
+ }
+ };
+ List<File> files = new ArrayList<>();
+ files.add(fakeApk);
+ OptionSetter setter = new OptionSetter(mPrep);
+ setter.setOptionValue(CHECK_MIN_SDK_OPTION, "true");
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
+ try {
+ mPrep.resolveApkFiles(mTestInfo, files);
+ fail("Should have thrown an exception");
+ } catch (TargetSetupError expected) {
+ assertEquals(
+ String.format("Failed to extract info from `%s` using aapt", fakeApk.getName()),
+ expected.getMessage());
+ } finally {
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
+ }
+ }
+
+ /** Tests that we don't include the file if api level too low */
+ @Test
+ public void testResolveApkFiles_checkMinSdk_apiLow() throws Exception {
+ mPrep =
+ new TestAppInstallSetup() {
+ @Override
+ AaptParser doAaptParse(File apkFile) {
+ return mMockAaptParser;
+ }
+
+ @Override
+ protected String parsePackageName(
+ File testAppFile, DeviceDescriptor deviceDescriptor) {
+ return "fakePackageName";
+ }
+ };
+ List<File> files = new ArrayList<>();
+ files.add(fakeApk);
+ OptionSetter setter = new OptionSetter(mPrep);
+ setter.setOptionValue(CHECK_MIN_SDK_OPTION, "true");
+ EasyMock.expect(mMockTestDevice.getSerialNumber()).andStubReturn(SERIAL);
+ EasyMock.expect(mMockTestDevice.getApiLevel()).andReturn(21).times(2);
+ doReturn(22).when(mMockAaptParser).getSdkVersion();
+
+ Map<File, String> expected = new LinkedHashMap<>();
+
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
+
+ Map<File, String> result = mPrep.resolveApkFiles(mTestInfo, files);
+
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
+ Mockito.verify(mMockAaptParser, times(2)).getSdkVersion();
+ assertEquals(expected, result);
+ }
+
+ /** Tests that we include the file if level is appropriate */
+ @Test
+ public void testResolveApkFiles_checkMinSdk_apiOk() throws Exception {
+ mPrep =
+ new TestAppInstallSetup() {
+ @Override
+ AaptParser doAaptParse(File apkFile) {
+ return mMockAaptParser;
+ }
+
+ @Override
+ protected String parsePackageName(
+ File testAppFile, DeviceDescriptor deviceDescriptor) {
+ return "fakePackageName";
+ }
+ };
+ List<File> files = new ArrayList<>();
+ files.add(fakeApk);
+ OptionSetter setter = new OptionSetter(mPrep);
+ setter.setOptionValue(CHECK_MIN_SDK_OPTION, "true");
+ EasyMock.expect(mMockTestDevice.getSerialNumber()).andStubReturn(SERIAL);
+ EasyMock.expect(mMockTestDevice.getApiLevel()).andReturn(23);
+ doReturn(22).when(mMockAaptParser).getSdkVersion();
+
+ Map<File, String> expected = new LinkedHashMap<>();
+ expected.put(fakeApk, "fakePackageName");
+
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
+
+ Map<File, String> result = mPrep.resolveApkFiles(mTestInfo, files);
+
+ Mockito.verify(mMockAaptParser).getSdkVersion();
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
+ assertEquals(result, expected);
+ }
+
private static Path createSubDirectory(Path parent, String name) throws IOException {
return Files.createDirectory(parent.resolve(name)).toAbsolutePath();
}
diff --git a/tests/src/com/android/tradefed/testtype/ArtGTestTest.java b/tests/src/com/android/tradefed/testtype/ArtGTestTest.java
new file mode 100644
index 000000000..a30343ef7
--- /dev/null
+++ b/tests/src/com/android/tradefed/testtype/ArtGTestTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2020 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;
+
+import com.android.ddmlib.IShellOutputReceiver;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.MockFileUtil;
+import com.android.tradefed.targetprep.ArtChrootPreparer;
+import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.result.ITestInvocationListener;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link ArtGTest}. */
+@RunWith(JUnit4.class)
+public class ArtGTestTest {
+ private ITestInvocationListener mMockInvocationListener = null;
+ private IShellOutputReceiver mMockReceiver = null;
+ private ITestDevice mMockITestDevice = null;
+ private GTest mGTest;
+ private TestInformation mTestInfo;
+
+ @Before
+ public void setUp() throws Exception {
+ mMockInvocationListener = EasyMock.createMock(ITestInvocationListener.class);
+ mMockReceiver = EasyMock.createMock(IShellOutputReceiver.class);
+ mMockITestDevice = EasyMock.createMock(ITestDevice.class);
+ mGTest =
+ new ArtGTest() {
+ @Override
+ IShellOutputReceiver createResultParser(
+ String runName, ITestInvocationListener listener) {
+ return mMockReceiver;
+ }
+ };
+ mGTest.setDevice(mMockITestDevice);
+ mGTest.setNativeTestDevicePath(ArtChrootPreparer.CHROOT_PATH);
+ mTestInfo = TestInformation.newBuilder().build();
+
+ EasyMock.expect(mMockITestDevice.getSerialNumber()).andStubReturn("serial");
+ }
+
+ private void replayMocks() {
+ EasyMock.replay(mMockInvocationListener, mMockITestDevice);
+ }
+
+ private void verifyMocks() {
+ EasyMock.verify(mMockInvocationListener, mMockITestDevice);
+ }
+
+ @Test
+ public void testChroot_testRun() throws DeviceNotAvailableException {
+ final String nativeTestPath = ArtChrootPreparer.CHROOT_PATH;
+ final String test1 = "test1";
+ final String testPath1 = String.format("%s/%s", nativeTestPath, test1);
+
+ MockFileUtil.setMockDirContents(mMockITestDevice, nativeTestPath, test1);
+ EasyMock.expect(mMockITestDevice.doesFileExist(nativeTestPath)).andReturn(true);
+ EasyMock.expect(mMockITestDevice.isDirectory(nativeTestPath)).andReturn(true);
+ EasyMock.expect(mMockITestDevice.isDirectory(testPath1)).andReturn(false);
+ EasyMock.expect(mMockITestDevice.isExecutable(testPath1)).andReturn(true);
+
+ String[] files = new String[] {"test1"};
+ EasyMock.expect(mMockITestDevice.getChildren(nativeTestPath)).andReturn(files);
+ mMockITestDevice.executeShellCommand(
+ EasyMock.startsWith("chroot /data/local/tmp/art-test-chroot /test1"),
+ EasyMock.anyObject(),
+ EasyMock.anyLong(),
+ EasyMock.anyObject(),
+ EasyMock.anyInt());
+
+ replayMocks();
+ mGTest.run(mTestInfo, mMockInvocationListener);
+ verifyMocks();
+ }
+}
diff --git a/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java b/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java
index 23d8b7590..e7c435442 100644
--- a/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/ArtRunTestTest.java
@@ -25,7 +25,6 @@ import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.TestInformation;
-import com.android.tradefed.invoker.ExecutionFiles.FilesKey;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
@@ -49,9 +48,6 @@ import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class ArtRunTestTest {
- // Default run-test name.
- private static final String RUN_TEST_NAME = "run-test";
-
private ITestInvocationListener mMockInvocationListener;
private IAbi mMockAbi;
private ITestDevice mMockITestDevice;
@@ -60,9 +56,9 @@ public class ArtRunTestTest {
private ArtRunTest mArtRunTest;
private OptionSetter mSetter;
private TestInformation mTestInfo;
- // Target tests directory.
- private File mTmpTargetTestsDir;
- // Expected output file (under the target tests directory).
+ // Test dependencies directory on host.
+ private File mTmpDepsDir;
+ // Expected output file (within the dependencies directory).
private File mTmpExpectedFile;
@Before
@@ -82,23 +78,22 @@ public class ArtRunTestTest {
mArtRunTest.setDevice(mMockITestDevice);
mSetter = new OptionSetter(mArtRunTest);
- // Set up target tests directory and expected output file.
- mTmpTargetTestsDir = FileUtil.createTempDir("target_testcases");
- File runTestDir = new File(mTmpTargetTestsDir, RUN_TEST_NAME);
- runTestDir.mkdir();
- mTmpExpectedFile = new File(runTestDir, "expected.txt");
- FileWriter fw = new FileWriter(mTmpExpectedFile);
- fw.write("output\n");
- fw.close();
-
- // Set the target tests directory in test information object.
- mTestInfo = TestInformation.newBuilder().build();
- mTestInfo.executionFiles().put(FilesKey.TARGET_TESTS_DIRECTORY, mTmpTargetTestsDir);
+ // Temporary test directory (e.g. for the expected-output file).
+ mTmpDepsDir = FileUtil.createTempDir("art-run-test-deps");
+ mTestInfo = TestInformation.newBuilder().setDependenciesFolder(mTmpDepsDir).build();
}
@After
public void tearDown() {
- FileUtil.recursiveDelete(mTmpTargetTestsDir);
+ FileUtil.recursiveDelete(mTmpDepsDir);
+ }
+
+ /** Helper creating an expected-output file within the (temporary) test directory. */
+ private void createExpectedOutputFile(String runTestName) throws IOException {
+ mTmpExpectedFile = new File(mTmpDepsDir, runTestName + "-expected.txt");
+ FileWriter fw = new FileWriter(mTmpExpectedFile);
+ fw.write("output\n");
+ fw.close();
}
/** Helper mocking writing the output of a test command. */
@@ -151,7 +146,9 @@ public class ArtRunTestTest {
@Test
public void testRunSingleTest_unsetClasspathOption()
throws ConfigurationException, DeviceNotAvailableException, IOException {
- mSetter.setOptionValue("run-test-name", RUN_TEST_NAME);
+ final String runTestName = "test";
+ mSetter.setOptionValue("run-test-name", runTestName);
+ createExpectedOutputFile(runTestName);
replayMocks();
try {
@@ -163,21 +160,20 @@ public class ArtRunTestTest {
verifyMocks();
}
- /** Test the run method for a (single) test. */
- @Test
- public void testRunSingleTest()
+ /** Helper containing testing logic for a (single) test expected to run (and succeed). */
+ private void doTestRunSingleTest(final String runTestName, final String classpath)
throws ConfigurationException, DeviceNotAvailableException, IOException {
- mSetter.setOptionValue("run-test-name", RUN_TEST_NAME);
- final String classpath = "/data/local/tmp/test/test.jar";
+ mSetter.setOptionValue("run-test-name", runTestName);
+ createExpectedOutputFile(runTestName);
mSetter.setOptionValue("classpath", classpath);
// Pre-test checks.
- EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn("");
EasyMock.expect(mMockAbi.getName()).andReturn("abi");
+ EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn("");
String runName = "ArtRunTest_abi";
// Beginning of test.
mMockInvocationListener.testRunStarted(runName, 1);
- TestDescription testId = new TestDescription(runName, RUN_TEST_NAME);
+ TestDescription testId = new TestDescription(runName, runTestName);
mMockInvocationListener.testStarted(testId);
String cmd = String.format("dalvikvm64 -classpath %s Main", classpath);
// Test execution.
@@ -198,6 +194,31 @@ public class ArtRunTestTest {
verifyMocks();
}
+ /** Helper containing testing logic for a (single) test expected not to run. */
+ private void doTestDoNotRunSingleTest(final String runTestName, final String classpath)
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ mSetter.setOptionValue("run-test-name", runTestName);
+ createExpectedOutputFile(runTestName);
+ mSetter.setOptionValue("classpath", classpath);
+
+ EasyMock.expect(mMockAbi.getName()).andReturn("abi");
+ replayMocks();
+
+ mArtRunTest.run(mTestInfo, mMockInvocationListener);
+
+ verifyMocks();
+ }
+
+ /** Test the run method for a (single) test. */
+ @Test
+ public void testRunSingleTest()
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ final String runTestName = "test";
+ final String classpath = "/data/local/tmp/test/test.jar";
+
+ doTestRunSingleTest(runTestName, classpath);
+ }
+
/**
* Test the behavior of the run method when the output produced by the shell command on device
* differs from the expected output.
@@ -205,17 +226,19 @@ public class ArtRunTestTest {
@Test
public void testRunSingleTest_unexpectedOutput()
throws ConfigurationException, DeviceNotAvailableException, IOException {
- mSetter.setOptionValue("run-test-name", RUN_TEST_NAME);
+ final String runTestName = "test";
+ mSetter.setOptionValue("run-test-name", runTestName);
+ createExpectedOutputFile(runTestName);
final String classpath = "/data/local/tmp/test/test.jar";
mSetter.setOptionValue("classpath", classpath);
// Pre-test checks.
- EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn("");
EasyMock.expect(mMockAbi.getName()).andReturn("abi");
+ EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn("");
String runName = "ArtRunTest_abi";
// Beginning of test.
mMockInvocationListener.testRunStarted(runName, 1);
- TestDescription testId = new TestDescription(runName, RUN_TEST_NAME);
+ TestDescription testId = new TestDescription(runName, runTestName);
mMockInvocationListener.testStarted(testId);
String cmd = String.format("dalvikvm64 -classpath %s Main", classpath);
// Test execution.
@@ -224,7 +247,14 @@ public class ArtRunTestTest {
mockTestOutputWrite("unexpected\n");
EasyMock.expect(mMockITestDevice.getSerialNumber()).andReturn("");
// End of test.
- mMockInvocationListener.testFailed(testId, "'unexpected\n' instead of 'output\n'");
+ String errorMessage =
+ "The test's standard output does not match the expected output:\n"
+ + "--- expected.txt\n"
+ + "+++ stdout\n"
+ + "@@ -1,1 +1,1 @@\n"
+ + "-output\n"
+ + "+unexpected\n";
+ mMockInvocationListener.testFailed(testId, errorMessage);
mMockInvocationListener.testEnded(
EasyMock.eq(testId), (HashMap<String, Metric>) EasyMock.anyObject());
mMockInvocationListener.testRunEnded(
@@ -236,4 +266,44 @@ public class ArtRunTestTest {
verifyMocks();
}
+
+ /** Test the run method for a (single) test contained in an include filter. */
+ @Test
+ public void testIncludeFilter()
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ final String runTestName = "test";
+ final String classpath = "/data/local/tmp/test/test.jar";
+ // Add an include filter containing the test's name.
+ mArtRunTest.addIncludeFilter(runTestName);
+
+ doTestRunSingleTest(runTestName, classpath);
+ }
+
+ /** Test the run method for a (single) test contained in an exclude filter. */
+ @Test
+ public void testExcludeFilter()
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ final String runTestName = "test";
+ final String classpath = "/data/local/tmp/test/test.jar";
+ // Add an exclude filter containing the test's name.
+ mArtRunTest.addExcludeFilter(runTestName);
+
+ doTestDoNotRunSingleTest(runTestName, classpath);
+ }
+
+ /**
+ * Test the run method for a (single) test contained both in an include and an exclude filter.
+ */
+ @Test
+ public void testIncludeAndExcludeFilter()
+ throws ConfigurationException, DeviceNotAvailableException, IOException {
+ final String runTestName = "test";
+ final String classpath = "/data/local/tmp/test/test.jar";
+ // Add an include filter containing the test's name.
+ mArtRunTest.addIncludeFilter(runTestName);
+ // Add an exclude filter containing the test's name.
+ mArtRunTest.addExcludeFilter(runTestName);
+
+ doTestDoNotRunSingleTest(runTestName, classpath);
+ }
}
diff --git a/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java b/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
index 51877ced1..8ce4d0a62 100644
--- a/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/GoogleBenchmarkTestTest.java
@@ -23,6 +23,7 @@ import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.util.StringEscapeUtils;
import junit.framework.TestCase;
@@ -348,8 +349,7 @@ public class GoogleBenchmarkTestTest extends TestCase {
String filterFlag = mGoogleBenchmarkTest.getFilterFlagForFilters(filters);
assertEquals(
String.format(
- " %s=%s",
- GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "filter1\\|filter2"),
+ " %s=%s", GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "filter1|filter2"),
filterFlag);
}
@@ -366,8 +366,7 @@ public class GoogleBenchmarkTestTest extends TestCase {
String filterFlag = mGoogleBenchmarkTest.getFilterFlagForTests(tests);
assertEquals(
String.format(
- " %s=%s",
- GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "^test1$\\|^test2$"),
+ " %s=%s", GoogleBenchmarkTest.GBENCHMARK_FILTER_OPTION, "^test1$|^test2$"),
filterFlag);
}
@@ -407,7 +406,10 @@ public class GoogleBenchmarkTestTest extends TestCase {
String incFilterFlag =
mGoogleBenchmarkTest.getFilterFlagForFilters(
mGoogleBenchmarkTest.getIncludeFilters());
- EasyMock.expect(mMockITestDevice.executeShellCommand(EasyMock.contains(incFilterFlag)))
+ EasyMock.expect(
+ mMockITestDevice.executeShellCommand(
+ EasyMock.contains(
+ StringEscapeUtils.escapeShell(incFilterFlag))))
.andReturn(incTests);
} else {
EasyMock.expect(
@@ -422,14 +424,17 @@ public class GoogleBenchmarkTestTest extends TestCase {
String excFilterFlag =
mGoogleBenchmarkTest.getFilterFlagForFilters(
mGoogleBenchmarkTest.getExcludeFilters());
- EasyMock.expect(mMockITestDevice.executeShellCommand(EasyMock.contains(excFilterFlag)))
+ EasyMock.expect(
+ mMockITestDevice.executeShellCommand(
+ EasyMock.contains(
+ StringEscapeUtils.escapeShell(excFilterFlag))))
.andReturn(excTests);
}
if (filteredTests != null && filteredTests.size() > 0) {
// Runningt filtered tests
String testFilterFlag = mGoogleBenchmarkTest.getFilterFlagForTests(filteredTests);
mMockITestDevice.executeShellCommand(
- EasyMock.contains(testFilterFlag),
+ EasyMock.contains(StringEscapeUtils.escapeShell(testFilterFlag)),
EasyMock.same(mMockReceiver),
EasyMock.anyLong(),
(TimeUnit) EasyMock.anyObject(),
diff --git a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
index d9e1e3e81..02359c967 100644
--- a/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/InstrumentationTestTest.java
@@ -16,6 +16,8 @@
package com.android.tradefed.testtype;
+import static com.android.tradefed.testtype.InstrumentationTest.RUN_TESTS_AS_USER_KEY;
+
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertEquals;
@@ -28,6 +30,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.atLeastOnce;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -205,6 +208,27 @@ public class InstrumentationTestTest {
}
@Test
+ public void testRun_nullTestInfo() throws Exception {
+ mInstrumentationTest.run(/* testInfo= */ null, mMockListener);
+
+ verify(mMockTestDevice, atLeastOnce())
+ .runInstrumentationTests(
+ any(IRemoteAndroidTestRunner.class), any(ITestInvocationListener.class));
+ }
+
+ @Test
+ public void testRun_runTestsAsUser() throws DeviceNotAvailableException {
+ mTestInfo.properties().put(RUN_TESTS_AS_USER_KEY, "10");
+ mInstrumentationTest.run(mTestInfo, mMockListener);
+
+ verify(mMockTestDevice, atLeastOnce())
+ .runInstrumentationTestsAsUser(
+ any(IRemoteAndroidTestRunner.class),
+ eq(10),
+ any(ITestInvocationListener.class));
+ }
+
+ @Test
public void testRun_bothAbi() throws DeviceNotAvailableException {
mInstrumentationTest.setAbi(mock(IAbi.class));
mInstrumentationTest.setForceAbi("test");
diff --git a/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java b/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
index de69a0519..27e793bf7 100644
--- a/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
+++ b/tests/src/com/android/tradefed/testtype/mobly/MoblyBinaryHostTestTest.java
@@ -28,6 +28,9 @@ import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.contains;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@@ -78,6 +81,7 @@ public class MoblyBinaryHostTestTest {
private File mMoblyTestDir;
private File mMoblyBinary; // used by python-binaries option
private File mMoblyBinary2; // used by par-file-name option
+ private File mVenvDir;
private DeviceBuildInfo mMockBuildInfo;
@Before
@@ -87,6 +91,10 @@ public class MoblyBinaryHostTestTest {
mMockRunUtil = Mockito.mock(IRunUtil.class);
mMockBuildInfo = Mockito.mock(DeviceBuildInfo.class);
mSpyTest.setDevice(mMockDevice);
+
+ mVenvDir = FileUtil.createTempDir("venv");
+ new File(mVenvDir, "bin").mkdir();
+
Mockito.doReturn(mMockRunUtil).when(mSpyTest).getRunUtil();
Mockito.doReturn(DEFAULT_TIME_OUT).when(mSpyTest).getTestTimeout();
Mockito.doReturn("not_adb").when(mSpyTest).getAdbPath();
@@ -99,6 +107,7 @@ public class MoblyBinaryHostTestTest {
@After
public void tearDown() throws Exception {
FileUtil.recursiveDelete(mMoblyTestDir);
+ FileUtil.recursiveDelete(mVenvDir);
}
@Test
@@ -216,6 +225,71 @@ public class MoblyBinaryHostTestTest {
}
@Test
+ public void testRun_shouldActivateVenvAndCleanUp_whenVenvIsSet() throws Exception {
+ Mockito.when(mMockBuildInfo.getFile(eq("VIRTUAL_ENV"))).thenReturn(mVenvDir);
+ OptionSetter setter = new OptionSetter(mSpyTest);
+ setter.setOptionValue("python-binaries", mMoblyBinary.getAbsolutePath());
+ File testResult = new File(mSpyTest.getLogDirAbsolutePath(), TEST_RESULT_FILE_NAME);
+ Mockito.when(
+ mMockRunUtil.runTimedCmd(
+ anyLong(),
+ anyString(),
+ eq("--"),
+ contains("--device_serial="),
+ contains("--log_path=")))
+ .thenAnswer(
+ new Answer<CommandResult>() {
+ @Override
+ public CommandResult answer(InvocationOnMock invocation)
+ throws Throwable {
+ FileUtils.createFile(testResult, "");
+ FileUtils.createFile(
+ new File(mSpyTest.getLogDirAbsolutePath(), "log"),
+ "log content");
+ return new CommandResult(CommandStatus.SUCCESS);
+ }
+ });
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ result.setStdout(
+ "Name: pip\nLocation: "
+ + new File(mVenvDir.getAbsolutePath(), "lib/python3.8/site-packages"));
+ Mockito.when(mMockRunUtil.runTimedCmd(anyLong(), anyString(), eq("show"), eq("pip")))
+ .thenReturn(result);
+
+ mSpyTest.run(Mockito.mock(ITestInvocationListener.class));
+
+ verify(mSpyTest.getRunUtil(), times(1))
+ .setEnvVariable(eq("VIRTUAL_ENV"), eq(mVenvDir.getAbsolutePath()));
+ assertFalse(mVenvDir.exists());
+ }
+
+ @Test
+ public void testRun_shouldNotActivateVenv_whenVenvIsNotSet() throws Exception {
+ FileUtil.recursiveDelete(mVenvDir);
+ OptionSetter setter = new OptionSetter(mSpyTest);
+ setter.setOptionValue("python-binaries", mMoblyBinary.getAbsolutePath());
+ File testResult = new File(mSpyTest.getLogDirAbsolutePath(), TEST_RESULT_FILE_NAME);
+ Mockito.when(mMockRunUtil.runTimedCmd(anyLong(), any()))
+ .thenAnswer(
+ new Answer<CommandResult>() {
+ @Override
+ public CommandResult answer(InvocationOnMock invocation)
+ throws Throwable {
+ FileUtils.createFile(testResult, "");
+ FileUtils.createFile(
+ new File(mSpyTest.getLogDirAbsolutePath(), "log"),
+ "log content");
+ return new CommandResult(CommandStatus.SUCCESS);
+ }
+ });
+
+ mSpyTest.run(Mockito.mock(ITestInvocationListener.class));
+
+ verify(mSpyTest.getRunUtil(), never())
+ .setEnvVariable(eq("VIRTUAL_ENV"), eq(mVenvDir.getAbsolutePath()));
+ }
+
+ @Test
public void testBuildCommandLineArrayWithOutConfig() throws Exception {
Mockito.doNothing().when(mSpyTest).reportLogs(any(), any());
Mockito.doReturn(DEVICE_SERIAL).when(mMockDevice).getSerialNumber();
@@ -262,7 +336,11 @@ public class MoblyBinaryHostTestTest {
Mockito.doNothing().when(mSpyTest).reportLogs(any(), any());
mMockSummaryInputStream = Mockito.mock(InputStream.class);
mMockParser = Mockito.mock(MoblyYamlResultParser.class);
- mSpyTest.processYamlTestResults(mMockSummaryInputStream, mMockParser);
+ mSpyTest.processYamlTestResults(
+ mMockSummaryInputStream,
+ mMockParser,
+ Mockito.mock(ITestInvocationListener.class),
+ "runName");
verify(mMockParser, times(1)).parse(mMockSummaryInputStream);
}
diff --git a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
index 3cee111d1..105046093 100644
--- a/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapperTest.java
@@ -26,6 +26,7 @@ import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.ITestDevice;
@@ -263,13 +264,22 @@ public class GranularRetriableTestWrapperTest {
private GranularRetriableTestWrapper createGranularTestWrapper(
IRemoteTest test, int maxRunCount) throws Exception {
- return createGranularTestWrapper(test, maxRunCount, new ArrayList<>());
+ return createGranularTestWrapper(test, maxRunCount, new ArrayList<>(), null);
}
private GranularRetriableTestWrapper createGranularTestWrapper(
IRemoteTest test, int maxRunCount, List<IMetricCollector> collectors) throws Exception {
+ return createGranularTestWrapper(test, maxRunCount, collectors, null);
+ }
+
+ private GranularRetriableTestWrapper createGranularTestWrapper(
+ IRemoteTest test,
+ int maxRunCount,
+ List<IMetricCollector> collectors,
+ ModuleDefinition module)
+ throws Exception {
GranularRetriableTestWrapper granularTestWrapper =
- new GranularRetriableTestWrapper(test, null, null, null, maxRunCount);
+ new GranularRetriableTestWrapper(test, module, null, null, null, maxRunCount);
granularTestWrapper.setModuleId("test module");
granularTestWrapper.setMarkTestsSkipped(false);
granularTestWrapper.setMetricCollectors(collectors);
@@ -904,6 +914,101 @@ public class GranularRetriableTestWrapperTest {
EasyMock.verify(mMockDevice, mMockDevice2);
}
+ /** Test to reset multi-devices at the last intra-module retry. */
+ @Test
+ public void testIntraModuleRun_resetMultiDevicesAtLastIntraModuleRetry() throws Exception {
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("reset-at-last-retry", "true");
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModuleInvocationContext);
+ FakeTest test = new FakeTest();
+ test.setRunFailure("I failed!");
+ ITestDevice noneAVDDevice = EasyMock.createMock(ITestDevice.class);
+
+ RemoteAndroidVirtualDevice avdDevice = Mockito.mock(RemoteAndroidVirtualDevice.class);
+ Mockito.when(avdDevice.powerwashGce()).thenReturn(true);
+
+ ModuleDefinition module = Mockito.mock(ModuleDefinition.class);
+ // Should call suite level preparers.
+ Mockito.when(module.runPreparation(true)).thenReturn(null);
+
+ mModuleInvocationContext.addAllocatedDevice("default-device1", noneAVDDevice);
+ mModuleInvocationContext.addAllocatedDevice("default-device2", avdDevice);
+ GranularRetriableTestWrapper granularTestWrapper =
+ createGranularTestWrapper(test, 3, new ArrayList<>(), module);
+ granularTestWrapper.setRetryDecision(decision);
+ EasyMock.expect(noneAVDDevice.getIDevice())
+ .andStubReturn(EasyMock.createMock(IDevice.class));
+ EasyMock.expect(noneAVDDevice.getSerialNumber()).andStubReturn("device-1");
+
+ EasyMock.replay(noneAVDDevice);
+ granularTestWrapper.run(mModuleInfo, new CollectingTestListener());
+ EasyMock.verify(noneAVDDevice);
+ }
+
+ /** Test to reset device at the last intra-module retry failed due to preparer failure. */
+ @Test
+ public void testIntraModuleRun_resetFailed_preparerFailure() throws Exception {
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("reset-at-last-retry", "true");
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModuleInvocationContext);
+ FakeTest test = new FakeTest();
+ test.setRunFailure("I failed!");
+
+ RemoteAndroidVirtualDevice avdDevice = Mockito.mock(RemoteAndroidVirtualDevice.class);
+ Mockito.when(avdDevice.powerwashGce()).thenReturn(true);
+
+ ModuleDefinition module = Mockito.mock(ModuleDefinition.class);
+ // Suite level preparers failed.
+ Mockito.when(module.runPreparation(true)).thenReturn(new RuntimeException());
+
+ mModuleInvocationContext.addAllocatedDevice("default-device2", avdDevice);
+ GranularRetriableTestWrapper granularTestWrapper =
+ createGranularTestWrapper(test, 3, new ArrayList<>(), module);
+ granularTestWrapper.setRetryDecision(decision);
+
+ try {
+ granularTestWrapper.run(mModuleInfo, new CollectingTestListener());
+ fail("Exception should be raised when reset is failed.");
+ } catch (DeviceNotAvailableException e) {
+ assertTrue(e.getMessage().startsWith("Failed to reset devices before retry: "));
+ }
+ }
+
+ /** Test to reset device at the last intra-module retry failed due to reset failure. */
+ @Test
+ public void testIntraModuleRun_resetFailed_powerwashFailure() throws Exception {
+ IRetryDecision decision = new BaseRetryDecision();
+ OptionSetter setter = new OptionSetter(decision);
+ setter.setOptionValue("reset-at-last-retry", "true");
+ setter.setOptionValue("retry-strategy", "RETRY_ANY_FAILURE");
+ setter.setOptionValue("max-testcase-run-count", Integer.toString(3));
+ decision.setInvocationContext(mModuleInvocationContext);
+ FakeTest test = new FakeTest();
+ test.setRunFailure("I failed!");
+
+ RemoteAndroidVirtualDevice device = Mockito.mock(RemoteAndroidVirtualDevice.class);
+ Mockito.when(device.powerwashGce()).thenReturn(false);
+ Mockito.when(device.getSerialNumber()).thenReturn("device1");
+
+ test.setDevice(device);
+ mModuleInvocationContext.addAllocatedDevice("default-device1", device);
+ GranularRetriableTestWrapper granularTestWrapper = createGranularTestWrapper(test, 3);
+ granularTestWrapper.setRetryDecision(decision);
+
+ try {
+ granularTestWrapper.run(mModuleInfo, new CollectingTestListener());
+ fail("Exception should be raised when reset is failed.");
+ } catch (DeviceNotAvailableException e) {
+ assertEquals("Failed to powerwash device: device1", e.getMessage());
+ }
+ }
+
/** Collector that track if it was called or not */
public static class CalledMetricCollector extends BaseDeviceMetricCollector {
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java
index 2b3db6b6f..5d43bb2b1 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleSplitterTest.java
@@ -37,8 +37,11 @@ import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
+import java.util.Map;
/** Unit tests for {@link ModuleSplitter}. */
@RunWith(JUnit4.class)
@@ -46,6 +49,8 @@ public class ModuleSplitterTest {
private static final String DEFAULT_DEVICE = ConfigurationDef.DEFAULT_DEVICE_NAME;
private TestInformation mTestInfo = TestInformation.newBuilder().build();
+ private Map<String, List<ITargetPreparer>> mSuitePreparersPerDevice =
+ new HashMap<String, List<ITargetPreparer>>();
/**
* Tests that {@link ModuleSplitter#splitConfiguration(TestInformation, LinkedHashMap, int,
@@ -69,7 +74,8 @@ public class ModuleSplitterTest {
setter.setOptionValue("not-shardable", "true");
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true);
// matching 1 for 1, config to ModuleDefinition since not shardable
assertEquals(1, res.size());
// The original target preparer is changed since we split multiple <test> tags.
@@ -105,7 +111,8 @@ public class ModuleSplitterTest {
setter.setOptionValue("not-strict-shardable", "true");
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true);
// We are sharding since even if we are not-strict-shardable, we are in dynamic context
assertEquals(10, res.size());
// The original target preparer is changed since we split multiple <test> tags.
@@ -140,7 +147,8 @@ public class ModuleSplitterTest {
setter.setOptionValue("not-strict-shardable", "true");
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, false, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, false, true);
// matching 1 for 1, config to ModuleDefinition since not shardable
assertEquals(1, res.size());
// The original target preparer is changed since we split multiple <test> tags.
@@ -169,7 +177,8 @@ public class ModuleSplitterTest {
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, false);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, false);
// matching 1 for 1, config to ModuleDefinition since no intra-module sharding
assertEquals(1, res.size());
// The original target preparer is changed since we split multiple <test> tags.
@@ -206,7 +215,8 @@ public class ModuleSplitterTest {
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true);
// matching 1 for 1, config to ModuleDefinition since not shardable
assertEquals(1, res.size());
// The original target preparer is not there, it has been copied
@@ -239,7 +249,8 @@ public class ModuleSplitterTest {
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, true, true);
// matching 1 for 1, config to ModuleDefinition since did not shard
assertEquals(1, res.size());
// The original target preparer is not there, it has been copied
@@ -271,14 +282,25 @@ public class ModuleSplitterTest {
setter.setOptionValue("num-shards", "6");
config.setTest(test);
+ Map<String, List<ITargetPreparer>> suitePreparers =
+ new HashMap<String, List<ITargetPreparer>>();
+ ITargetPreparer preparer1 = new StubTargetPreparer();
+ ITargetPreparer preparer2 = new StubTargetPreparer();
+ List<ITargetPreparer> preparers = Arrays.asList(preparer1, preparer2);
+ suitePreparers.put(DEFAULT_DEVICE, preparers);
+
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, true, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, suitePreparers, 5, true, true);
// matching 1 for 10 since tests sharding in 5 units times 2.
assertEquals(10, res.size());
// The original IRemoteTest does not exists anymore, new IRemoteTests have been created.
for (ModuleDefinition m : res) {
assertNotSame(test, m.getTests().get(0));
+ assertEquals(2, m.getSuitePreparerForDevice(DEFAULT_DEVICE).size());
+ assertNotSame(preparer1, m.getSuitePreparerForDevice(DEFAULT_DEVICE).get(0));
+ assertNotSame(preparer1, m.getSuitePreparerForDevice(DEFAULT_DEVICE).get(1));
}
assertTrue(config.getTests().isEmpty());
}
@@ -302,7 +324,8 @@ public class ModuleSplitterTest {
runConfig.put("module1", config);
List<ModuleDefinition> res =
- ModuleSplitter.splitConfiguration(mTestInfo, runConfig, 5, false, true);
+ ModuleSplitter.splitConfiguration(
+ mTestInfo, runConfig, mSuitePreparersPerDevice, 5, false, true);
// matching 1 for 6 since tests sharding in 6 tests.
assertEquals(6, res.size());
// The original IRemoteTest does not exists anymore, new IRemoteTests have been created.
diff --git a/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java b/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
index 07e080718..81fa44f9e 100644
--- a/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertTrue;
import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey;
import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.config.Configuration;
import com.android.tradefed.config.ConfigurationDef;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.ConfigurationFactory;
@@ -87,6 +88,7 @@ public class TestMappingSuiteRunnerTest {
private IDeviceBuildInfo mBuildInfo;
private ITestDevice mMockDevice;
private TestInformation mTestInfo;
+ private IConfiguration mStubMainConfiguration;
private static final String TEST_MAINLINE_CONFIG =
"<configuration description=\"Runs a stub tests part of some suite\">\n"
@@ -115,6 +117,8 @@ public class TestMappingSuiteRunnerTest {
mMainlineRunner = new FakeMainlineTMSR();
mMainlineRunner.setBuild(mBuildInfo);
mMainlineRunner.setDevice(mMockDevice);
+ mStubMainConfiguration = new Configuration("stub", "stub");
+ mMainlineRunner.setConfiguration(mStubMainConfiguration);
mMainlineOptionSetter = new OptionSetter(mMainlineRunner);
IInvocationContext context = new InvocationContext();
diff --git a/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java b/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java
index 11dee185d..4a5d70763 100644
--- a/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/params/MainlineModuleHandlerTest.java
@@ -62,7 +62,7 @@ public final class MainlineModuleHandlerTest {
public void testApplySetup() {
EasyMock.expect(mMockBuildInfo.getBuildBranch()).andStubReturn("branch");
EasyMock.replay(mMockBuildInfo);
- mHandler = new MainlineModuleHandler("mod1.apk", mAbi, mContext);
+ mHandler = new MainlineModuleHandler("mod1.apk", mAbi, mContext, false);
mHandler.applySetup(mConfig);
assertTrue(mConfig.getTargetPreparers().get(0) instanceof InstallApexModuleTargetPreparer);
InstallApexModuleTargetPreparer preparer =
@@ -77,7 +77,7 @@ public final class MainlineModuleHandlerTest {
public void testApplySetup_MultipleMainlineModules() {
EasyMock.expect(mMockBuildInfo.getBuildBranch()).andStubReturn("branch");
EasyMock.replay(mMockBuildInfo);
- mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext);
+ mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext, false);
mHandler.applySetup(mConfig);
assertTrue(mConfig.getTargetPreparers().get(0) instanceof InstallApexModuleTargetPreparer);
InstallApexModuleTargetPreparer preparer =
@@ -97,7 +97,7 @@ public final class MainlineModuleHandlerTest {
try {
EasyMock.expect(mMockBuildInfo.getBuildBranch()).andStubReturn(null);
EasyMock.replay(mMockBuildInfo);
- mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext);
+ mHandler = new MainlineModuleHandler("mod1.apk+mod2.apex", mAbi, mContext, false);
fail("Should have thrown an exception.");
} catch (IllegalArgumentException expected) {
// expected
diff --git a/tests/src/com/android/tradefed/util/AaptParserTest.java b/tests/src/com/android/tradefed/util/AaptParserTest.java
index 65bf70dd5..93caecff8 100644
--- a/tests/src/com/android/tradefed/util/AaptParserTest.java
+++ b/tests/src/com/android/tradefed/util/AaptParserTest.java
@@ -168,6 +168,34 @@ public class AaptParserTest extends TestCase {
assertTrue(p.isRequestingLegacyStorage());
}
+ public void testParseXmlTreeForAapt2_withRequestLegacyFlagTrue() {
+ AaptParser p = new AaptParser();
+ p.parseXmlTree(
+ "N: android=http://schemas.android.com/apk/res/android\n"
+ + " E: manifest (line=2)\n"
+ + " A: http://schemas.android.com/apk/res/android:versionCode(0x0101021b)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/android:versionName(0x0101021c)=\"R\" (Raw: \"R\")\n"
+ + " A: http://schemas.android.com/apk/res/android:compileSdkVersion(0x01010572)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/android:compileSdkVersionCodename(0x01010573)=\"R\" (Raw: "
+ + "\"R\")\n"
+ + " A: package=\"com.android.foo\" (Raw: \"com.android.foo\")\n"
+ + " A: platformBuildVersionCode=(type 0x10)0x1d\n"
+ + " A: platformBuildVersionName=\"R\" (Raw: \"R\")\n"
+ + " E: uses-sdk (line=5)\n"
+ + " A: http://schemas.android.com/apk/res/android:minSdkVersion(0x0101020c)=(type 0x10)0x1c\n"
+ + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=\"R\" (Raw: \"R\")\n"
+ + " E: application (line=12)\n"
+ + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=(type 0x10)0x1e\n"
+ + " A: http://schemas.android.com/apk/res/android:supportsRtl(0x010103af)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/android:extractNativeLibs(0x010104ea)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/android:appComponentFactory(0x0101057a)=\"androidx.core.app"
+ + ".CoreComponentFactory\" (Raw: \"androidx.core.app"
+ + ".CoreComponentFactory\")\n"
+ + " A: http://schemas.android.com/apk/res/android:requestLegacyExternalStorage(0x01010603)=(type 0x12)"
+ + "0xffffffff\n");
+ assertTrue(p.isRequestingLegacyStorage());
+ }
+
public void testParseXmlTree_withRequestLegacyFlagFalse() {
AaptParser p = new AaptParser();
p.parseXmlTree(
@@ -196,6 +224,34 @@ public class AaptParserTest extends TestCase {
assertFalse(p.isRequestingLegacyStorage());
}
+ public void testParseXmlTreeForAapt2_withRequestLegacyFlagFalse() {
+ AaptParser p = new AaptParser();
+ p.parseXmlTree(
+ "N: android=http://schemas.android.com/apk/res/android\n"
+ + " E: manifest (line=2)\n"
+ + " A: http://schemas.android.com/apk/res/android:versionCode(0x0101021b)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/android:versionName(0x0101021c)=\"R\" (Raw: \"R\")\n"
+ + " A: http://schemas.android.com/apk/res/android:compileSdkVersion(0x01010572)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/android:compileSdkVersionCodename(0x01010573)=\"R\" (Raw: "
+ + "\"R\")\n"
+ + " A: package=\"com.android.foo\" (Raw: \"com.android.foo\")\n"
+ + " A: platformBuildVersionCode=(type 0x10)0x1d\n"
+ + " A: platformBuildVersionName=\"R\" (Raw: \"R\")\n"
+ + " E: uses-sdk (line=5)\n"
+ + " A: http://schemas.android.com/apk/res/android:minSdkVersion(0x0101020c)=(type 0x10)0x1c\n"
+ + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=\"R\" (Raw: \"R\")\n"
+ + " E: application (line=12)\n"
+ + " A: http://schemas.android.com/apk/res/android:targetSdkVersion(0x01010270)=(type 0x10)0x1e\n"
+ + " A: http://schemas.android.com/apk/res/android:supportsRtl(0x010103af)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/android:extractNativeLibs(0x010104ea)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/android:appComponentFactory(0x0101057a)=\"androidx.core.app"
+ + ".CoreComponentFactory\" (Raw: \"androidx.core.app"
+ + ".CoreComponentFactory\")\n"
+ + " A: http://schemas.android.com/apk/res/android:requestLegacyExternalStorage(0x01010603)=(type 0x12)"
+ + "0x0\n");
+ assertFalse(p.isRequestingLegacyStorage());
+ }
+
public void testParseXmlTree_withoutRequestLegacyFlag() {
AaptParser p = new AaptParser();
p.parseXmlTree(
@@ -221,7 +277,32 @@ public class AaptParserTest extends TestCase {
assertFalse(p.isRequestingLegacyStorage());
}
- public void testParseXmlTree_withUsesPermissionManageExternalStorage() {
+ public void testParseXmlTreeForAapt2_withoutRequestLegacyFlag() {
+ AaptParser p = new AaptParser();
+ p.parseXmlTree(
+ "N: android=http://schemas.android.com/apk/res/android\n"
+ + " E: manifest (line=2)\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:versionCode(0x0101021b)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:versionName(0x0101021c)=\"R\" (Raw: \"R\")\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:compileSdkVersion(0x01010572)=(type 0x10)0x1d\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:compileSdkVersionCodename(0x01010573)=\"R\" (Raw: "
+ + "\"R\")\n"
+ + " A: package=\"com.android.foo\" (Raw: \"com.android.foo\")\n"
+ + " A: platformBuildVersionCode=(type 0x10)0x1d\n"
+ + " A: platformBuildVersionName=\"R\" (Raw: \"R\")\n"
+ + " E: uses-sdk (line=5)\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:minSdkVersion(0x0101020c)=(type 0x10)0x1c\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:targetSdkVersion(0x01010270)=\"R\" (Raw: \"R\")\n"
+ + " E: application (line=12)\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:targetSdkVersion(0x01010270)=(type 0x10)0x1e\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:supportsRtl(0x010103af)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:extractNativeLibs(0x010104ea)=(type 0x12)0xffffffff\n"
+ + " A: http://schemas.android.com/apk/res/androidandroid:appComponentFactory(0x0101057a)=\"androidx.core.app"
+ + ".CoreComponentFactory\" (Raw: \"androidx.core.app");
+ assertFalse(p.isRequestingLegacyStorage());
+ }
+
+ public void testParse_withUsesPermissionManageExternalStorage() {
AaptParser p = new AaptParser();
p.parse(
"package: name='com.android.foo' versionCode='217173' versionName='1.7173' "
@@ -234,7 +315,7 @@ public class AaptParserTest extends TestCase {
assertTrue(p.isUsingPermissionManageExternalStorage());
}
- public void testParseXmlTree_withoutUsesPermissionManageExternalStorage() {
+ public void testParse_withoutUsesPermissionManageExternalStorage() {
AaptParser p = new AaptParser();
p.parse(
"package: name='com.android.foo' versionCode='217173' versionName='1.7173' "
diff --git a/tests/src/com/android/tradefed/util/ProtoUtilTest.java b/tests/src/com/android/tradefed/util/ProtoUtilTest.java
new file mode 100644
index 000000000..2e42eee55
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/ProtoUtilTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 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.junit.Assert.assertArrayEquals;
+
+import com.android.tradefed.util.test.ProtoUtilTestProto.TestMessage;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/** Unit tests for {@link ProtoUtil} */
+@RunWith(Parameterized.class)
+public class ProtoUtilTest {
+ @Parameter(0)
+ public String mTestName; // Unused, for identifying tests only.
+
+ @Parameter(1)
+ public TestMessage mMessage;
+
+ @Parameter(2)
+ public List<String> mReferences;
+
+ @Parameter(3)
+ public List<String> mExpectedResults;
+
+ @Parameters(name = "{0}#{index}")
+ public static Iterable<Object[]> data() {
+ List<Object[]> parameters = new ArrayList<>();
+ parameters.add(
+ new Object[] {
+ "returnsMessageAsStringForEmptyReference",
+ TestMessage.newBuilder().setIntField(7).build(),
+ new ArrayList<String>(),
+ Arrays.asList(TestMessage.newBuilder().setIntField(7).build().toString())
+ });
+ parameters.add(
+ new Object[] {
+ "singleLevel",
+ TestMessage.newBuilder().setIntField(7).build(),
+ Arrays.asList("int_field"),
+ Arrays.asList("7")
+ });
+ parameters.add(
+ new Object[] {
+ "singleLevel",
+ TestMessage.newBuilder().setStringField("string").build(),
+ Arrays.asList("string_field"),
+ Arrays.asList("string")
+ });
+ parameters.add(
+ new Object[] {
+ "singleLevel",
+ TestMessage.newBuilder()
+ .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7))
+ .build(),
+ Arrays.asList("message_field"),
+ Arrays.asList(TestMessage.SubMessage.newBuilder().setIntField(7).toString())
+ });
+ parameters.add(
+ new Object[] {
+ "singleLevelRepeated",
+ TestMessage.newBuilder()
+ .addAllRepeatedStringField(Arrays.asList("string1", "string2"))
+ .build(),
+ Arrays.asList("repeated_string_field"),
+ Arrays.asList("string1", "string2")
+ });
+ parameters.add(
+ new Object[] {
+ "multiLevel",
+ TestMessage.newBuilder()
+ .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7))
+ .build(),
+ Arrays.asList("message_field", "int_field"),
+ Arrays.asList("7")
+ });
+ parameters.add(
+ new Object[] {
+ "multiLevelRepeated",
+ TestMessage.newBuilder()
+ .setMessageField(
+ TestMessage.SubMessage.newBuilder()
+ .addAllRepeatedStringField(
+ Arrays.asList("string1", "string2")))
+ .build(),
+ Arrays.asList("message_field", "repeated_string_field"),
+ Arrays.asList("string1", "string2")
+ });
+ parameters.add(
+ new Object[] {
+ "multiLevelRepeated",
+ TestMessage.newBuilder()
+ .addAllRepeatedMessageField(
+ Arrays.asList(
+ TestMessage.SubMessage.newBuilder()
+ .addAllRepeatedStringField(
+ Arrays.asList("string1", "string2"))
+ .build(),
+ TestMessage.SubMessage.newBuilder()
+ .addAllRepeatedStringField(
+ Arrays.asList("string3", "string4"))
+ .build()))
+ .build(),
+ Arrays.asList("repeated_message_field", "repeated_string_field"),
+ Arrays.asList("string1", "string2", "string3", "string4")
+ });
+ parameters.add(
+ new Object[] {
+ "oneofSingleLevel",
+ TestMessage.newBuilder().setOneofStringField("string").build(),
+ Arrays.asList("oneof_string_field"),
+ Arrays.asList("string")
+ });
+ parameters.add(
+ new Object[] {
+ "oneofMultiLevel",
+ TestMessage.newBuilder()
+ .setOneofMessageField(
+ TestMessage.SubMessage.newBuilder()
+ .addAllRepeatedStringField(
+ Arrays.asList("string1", "string2")))
+ .build(),
+ Arrays.asList("oneof_message_field", "repeated_string_field"),
+ Arrays.asList("string1", "string2")
+ });
+ parameters.add(
+ new Object[] {
+ "returnsEmptyForNonExistentFieldReference",
+ TestMessage.newBuilder().setStringField("string").build(),
+ Arrays.asList("not_a_field"),
+ new ArrayList<String>()
+ });
+ parameters.add(
+ new Object[] {
+ "returnsEmptyForNonExistentFieldReference",
+ TestMessage.newBuilder()
+ .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7))
+ .build(),
+ Arrays.asList("message_field", "not_a_field"),
+ new ArrayList<String>()
+ });
+ parameters.add(
+ new Object[] {
+ "returnsEmptyForNonExistentFieldReference",
+ TestMessage.newBuilder()
+ .setMessageField(TestMessage.SubMessage.newBuilder().setIntField(7))
+ .build(),
+ Arrays.asList("message_field", "int_field", "not_a_field"),
+ new ArrayList<String>()
+ });
+ return parameters;
+ }
+
+ @Test
+ public void testParsing() {
+ assertArrayEquals(
+ mExpectedResults.toArray(),
+ ProtoUtil.getNestedFieldFromMessageAsStrings(mMessage, mReferences).toArray());
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/PythonVirtualenvHelperTest.java b/tests/src/com/android/tradefed/util/PythonVirtualenvHelperTest.java
new file mode 100644
index 000000000..aeb1c2466
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/PythonVirtualenvHelperTest.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2020 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.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.*;
+
+import com.google.common.base.Throwables;
+
+import org.junit.After;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.file.Paths;
+
+public class PythonVirtualenvHelperTest {
+
+ private File mVenvDir;
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtil.recursiveDelete(mVenvDir);
+ }
+
+ @Test
+ public void testActivate_shouldThrowNPE_whenVirtualenvPathIsNull() throws Exception {
+ String nullVirtualenvPath = null;
+ IRunUtil runUtil = mock(RunUtil.class);
+
+ try {
+ PythonVirtualenvHelper.activate(runUtil, nullVirtualenvPath);
+ fail("Should have thrown an exception");
+ } catch (NullPointerException e) {
+ assertThat(
+ String.format(
+ "An unexpected exception was thrown, full stack trace: %s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ containsString("Path to the Python virtual environment should not be null"));
+ }
+ }
+
+ @Test
+ public void testActivate_whenVirtualenvPathIsInvalid() throws Exception {
+ mVenvDir = FileUtil.createTempDir("venv");
+ mVenvDir.delete();
+ IRunUtil runUtil = mock(RunUtil.class);
+
+ try {
+ PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath());
+ fail("Should have thrown an exception");
+ } catch (RuntimeException e) {
+ assertThat(
+ String.format(
+ "An unexpected exception was thrown, full stack trace: %s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ containsString("Invalid python virtualenv path"));
+ }
+ }
+
+ @Test
+ public void testActivate_whenPythonBinNotFound() throws Exception {
+ mVenvDir = FileUtil.createTempDir("venv");
+ IRunUtil runUtil = mock(RunUtil.class);
+
+ try {
+ PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath());
+ fail("Should have thrown an exception");
+ } catch (RuntimeException e) {
+ assertThat(
+ String.format(
+ "An unexpected exception was thrown, full stack trace: %s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ containsString("Invalid python virtualenv path"));
+ }
+ }
+
+ @Test
+ public void testActivate_success() throws Exception {
+ mVenvDir = FileUtil.createTempDir("venv");
+ File pythonBin = new File(mVenvDir, "bin");
+ pythonBin.mkdir();
+ IRunUtil runUtil = mock(RunUtil.class);
+ CommandResult result = new CommandResult(CommandStatus.SUCCESS);
+ result.setStdout(
+ "Name: pip\nLocation: "
+ + Paths.get(mVenvDir.getAbsolutePath(), "lib/python3.8/site-packages"));
+ when(runUtil.runTimedCmd(anyLong(), anyString(), eq("show"), eq("pip"))).thenReturn(result);
+
+ PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath());
+
+ verify(runUtil)
+ .setEnvVariable("PATH", pythonBin.getAbsolutePath() + ":" + System.getenv("PATH"));
+ verify(runUtil).setEnvVariable("VIRTUAL_ENV", mVenvDir.getAbsolutePath());
+ verify(runUtil)
+ .setEnvVariable(
+ "PYTHONPATH",
+ new File(mVenvDir, "lib/python3.8/site-packages").getAbsolutePath()
+ + ":"
+ + System.getenv("PYTHONPATH"));
+ verify(runUtil).unsetEnvVariable("PYTHONHOME");
+ }
+
+ @Test
+ public void testActivate_pipShowFails() throws Exception {
+ mVenvDir = FileUtil.createTempDir("venv");
+ File pythonBin = new File(mVenvDir, "bin");
+ pythonBin.mkdir();
+ IRunUtil runUtil = mock(RunUtil.class);
+ when(runUtil.runTimedCmd(anyLong(), anyString(), eq("show"), eq("pip")))
+ .thenReturn(new CommandResult());
+
+ try {
+ PythonVirtualenvHelper.activate(runUtil, mVenvDir.getAbsolutePath());
+ fail("Should have thrown an exception");
+ } catch (RuntimeException e) {
+ assertThat(
+ String.format(
+ "An unexpected exception was thrown, full stack trace: %s",
+ Throwables.getStackTraceAsString(e)),
+ e.getMessage(),
+ containsString("pip3 show pip"));
+ }
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/RemoteZipTest.java b/tests/src/com/android/tradefed/util/RemoteZipTest.java
index 0176a0741..1d6056a16 100644
--- a/tests/src/com/android/tradefed/util/RemoteZipTest.java
+++ b/tests/src/com/android/tradefed/util/RemoteZipTest.java
@@ -103,7 +103,7 @@ public class RemoteZipTest {
List<CentralDirectoryInfo> entries = remoteZip.getZipEntries();
- assertEquals(7, entries.size());
+ assertEquals(8, entries.size());
assertTrue(mExpectedEntries.containsAll(entries));
} finally {
FileUtil.recursiveDelete(destDir);
@@ -122,7 +122,7 @@ public class RemoteZipTest {
destDir = FileUtil.createTempDir("test");
RemoteZip remoteZip = new RemoteZip(REMOTE_FILE, mZipFileSize, mDownloader, true);
List<CentralDirectoryInfo> entries = remoteZip.getZipEntries();
- assertEquals(7, entries.size());
+ assertEquals(8, entries.size());
assertTrue(mExpectedEntries.containsAll(entries));
} finally {
FileUtil.recursiveDelete(destDir);
@@ -151,7 +151,7 @@ public class RemoteZipTest {
targetFile = Paths.get(destDir.getPath(), "executable", "executable_file").toFile();
assertTrue(targetFile.exists());
// File not in the list is not unzipped.
- targetFile = Paths.get(destDir.getPath(), "empty_file").toFile();
+ targetFile = Paths.get(destDir.getPath(), "empty/empty_file").toFile();
assertFalse(targetFile.exists());
} finally {
FileUtil.recursiveDelete(destDir);
diff --git a/tests/src/com/android/tradefed/util/SparseImageUtilTest.java b/tests/src/com/android/tradefed/util/SparseImageUtilTest.java
new file mode 100644
index 000000000..d8858e7ec
--- /dev/null
+++ b/tests/src/com/android/tradefed/util/SparseImageUtilTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2020 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 org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/** Unit tests for {@link SparseImageUtil} */
+@RunWith(JUnit4.class)
+public class SparseImageUtilTest {
+ private File mSparseImageFile;
+
+ @Before
+ public void setUp() throws IOException {
+ mSparseImageFile = FileUtil.createTempFile("sparse", ".img");
+ try (FileOutputStream out = new FileOutputStream(mSparseImageFile)) {
+ out.write(getSparseImageData());
+ }
+ }
+
+ @After
+ public void tearDown() {
+ FileUtil.deleteFile(mSparseImageFile);
+ }
+
+ /** Verify {@link com.android.tradefed.util.SparseImageUtil#isSparse}. */
+ @Test
+ public void testIsSparse() {
+ Assert.assertTrue(SparseImageUtil.isSparse(mSparseImageFile));
+ }
+
+ /** Verify {@link com.android.tradefed.util.SparseImageUtil#unsparse}. */
+ @Test
+ public void testUnsparse() throws IOException {
+ File unsparsedFile = FileUtil.createTempFile("unsparse", ".img");
+ byte[] unsparsedData = null;
+ try {
+ SparseImageUtil.unsparse(mSparseImageFile, unsparsedFile);
+ try (FileInputStream in = new FileInputStream(unsparsedFile)) {
+ unsparsedData = StreamUtil.getByteArrayListFromStream(in).getContents();
+ }
+ Assert.assertArrayEquals(getUnsparsedImageData(), unsparsedData);
+ } finally {
+ FileUtil.deleteFile(unsparsedFile);
+ }
+ }
+
+ /**
+ * Returns some sparse data.
+ *
+ * @see https://android.googlesource.com/platform/system/core/+/master/libsparse/sparse_format.h
+ */
+ private byte[] getSparseImageData() {
+ final int SPARSE_IMAGE_MAGIC = 0xED26FF3A;
+ ByteBuffer buffer = ByteBuffer.allocate(4096);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ // Header
+ buffer.putInt(SPARSE_IMAGE_MAGIC);
+ buffer.putShort((short) 1);
+ buffer.putShort((short) 0);
+ buffer.putShort((short) 28);
+ buffer.putShort((short) 12);
+ buffer.putInt(4); /* block size */
+ buffer.putInt(512 + 256); /* total blocks */
+ buffer.putInt(2); /* total chunks */
+ buffer.putInt(0); /* ignore check sum */
+ // RAW chunk, 2048 bytes of lorem ipsum
+ byte[] loremIpsum = getLoremIpsum();
+ buffer.putShort((short) 0xCAC1);
+ buffer.putShort((short) 0); /* padding */
+ buffer.putInt(loremIpsum.length / 4); /* data size in terms of number of blocks */
+ buffer.putInt(12 + loremIpsum.length); /* header size + data size */
+ buffer.put(loremIpsum);
+ // DONTCARE chunk, 1024 bytes of zeroes
+ byte[] zeroes = new byte[1024];
+ buffer.putShort((short) 0xCAC3);
+ buffer.putShort((short) 0); /* padding */
+ buffer.putInt(zeroes.length / 4); /* data size in terms of number of blocks */
+ buffer.putInt(12 + zeroes.length); /* header size + data size */
+ buffer.put(zeroes);
+ return Arrays.copyOf(buffer.array(), buffer.position());
+ }
+
+ private byte[] getUnsparsedImageData() {
+ byte[] loremIpsum = getLoremIpsum();
+ // Pad lorem ipsum with 1024 bytes of zeroes
+ return Arrays.copyOf(loremIpsum, loremIpsum.length + 1024);
+ }
+
+ /** Returns a chunk of text data. */
+ private byte[] getLoremIpsum() {
+ final int dataLen = 2048; /* Must be a multiple of 4 */
+ final String loremIpsumString =
+ "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor"
+ + " incididunt ut labore et dolore magna aliqua. Enim neque volutpat ac"
+ + " tincidunt vitae semper quis lectus. Est pellentesque elit ullamcorper"
+ + " dignissim cras tincidunt lobortis feugiat vivamus. Vitae ultricies leo"
+ + " integer malesuada nunc vel. Ultrices tincidunt arcu non sodales neque"
+ + " sodales ut etiam sit. Arcu cursus vitae congue mauris rhoncus aenean."
+ + " Consectetur a erat nam at lectus urna duis convallis convallis. Suscipit"
+ + " tellus mauris a diam maecenas sed. At elementum eu facilisis sed odio."
+ + " Neque sodales ut etiam sit.";
+ final byte[] loremIpsumBytes = loremIpsumString.getBytes();
+ ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ while (buffer.size() < dataLen) {
+ buffer.write(loremIpsumBytes, 0, loremIpsumBytes.length);
+ }
+ return Arrays.copyOf(buffer.toByteArray(), dataLen);
+ }
+}
diff --git a/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java b/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java
index 7a81c5ee2..f166b6a5a 100644
--- a/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java
+++ b/tests/src/com/android/tradefed/util/StringEscapeUtilsTest.java
@@ -88,4 +88,30 @@ public class StringEscapeUtilsTest {
assertArrayEquals(new String[]{"foo", "bar bar"},
StringEscapeUtils.paramsToArgs(expected).toArray());
}
+
+ /**
+ * Simple test that {@link StringEscapeUtils#escapeShell(String)} escapes the is greater than
+ * sign.
+ */
+ @Test
+ public void testEscapesGreaterSigns() {
+ String escaped_str = StringEscapeUtils.escapeShell(">greater>signs");
+ assertEquals("\\>greater\\>signs", escaped_str);
+ }
+
+ /**
+ * Simple test that {@link StringEscapeUtils#escapeShell(String)} escapes the is less than sign.
+ */
+ @Test
+ public void testEscapesLessSigns() {
+ String escaped_str = StringEscapeUtils.escapeShell("<less<signs");
+ assertEquals("\\<less\\<signs", escaped_str);
+ }
+
+ /** Simple test that {@link StringEscapeUtils#escapeShell(String)} escapes the or sign. */
+ @Test
+ public void testEscapesOrSigns() {
+ String escaped_str = StringEscapeUtils.escapeShell("|or|signs");
+ assertEquals("\\|or\\|signs", escaped_str);
+ }
}
diff --git a/tests/src/com/android/tradefed/util/ZipUtilTest.java b/tests/src/com/android/tradefed/util/ZipUtilTest.java
index 33841a092..cabf7fa0f 100644
--- a/tests/src/com/android/tradefed/util/ZipUtilTest.java
+++ b/tests/src/com/android/tradefed/util/ZipUtilTest.java
@@ -216,8 +216,8 @@ public class ZipUtilTest extends TestCase {
partialZipFile,
endCentralDirInfo,
endCentralDirInfo.getCentralDirOffset());
- // The zip file has 3 folders, 4 files.
- assertEquals(7, zipEntries.size());
+ // The zip file has 4 folders, 4 files.
+ assertEquals(8, zipEntries.size());
CentralDirectoryInfo zipEntry;
LocalFileHeader localFileHeader;
@@ -228,7 +228,7 @@ public class ZipUtilTest extends TestCase {
zipEntry =
zipEntries
.stream()
- .filter(e -> e.getFileName().equals("empty_file"))
+ .filter(e -> e.getFileName().equals("empty/empty_file"))
.findFirst()
.get();
targetFile = new File(Paths.get(tmpDir.toString(), zipEntry.getFileName()).toString());
@@ -243,6 +243,7 @@ public class ZipUtilTest extends TestCase {
// Verify file permissions - readonly - 644 rw-r--r--
permissions = Files.getPosixFilePermissions(targetFile.toPath());
assertEquals(PosixFilePermissions.fromString("rw-r--r--"), permissions);
+ assertTrue(targetFile.isFile());
// Unzip text file
zipEntry =
@@ -377,8 +378,8 @@ public class ZipUtilTest extends TestCase {
endCentralDirInfo,
endCentralDirInfo.getCentralDirOffset(),
true);
- // The zip file has 3 folders, 4 files.
- assertEquals(7, zipEntries.size());
+ // The zip file has 4 folders, 4 files.
+ assertEquals(8, zipEntries.size());
CentralDirectoryInfo zipEntry;
LocalFileHeader localFileHeader;
@@ -389,7 +390,7 @@ public class ZipUtilTest extends TestCase {
zipEntry =
zipEntries
.stream()
- .filter(e -> e.getFileName().equals("empty_file"))
+ .filter(e -> e.getFileName().equals("empty/empty_file"))
.findFirst()
.get();
targetFile = new File(Paths.get(tmpDir.toString(), zipEntry.getFileName()).toString());
diff --git a/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java b/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java
index db019b604..b3ba0792f 100644
--- a/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java
+++ b/tests/src/com/android/tradefed/util/executor/ParallelDeviceExecutorTest.java
@@ -31,6 +31,7 @@ import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
+import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
/** Unit tests for {@link ParallelDeviceExecutor}. */
@@ -93,4 +94,23 @@ public class ParallelDeviceExecutorTest {
assertTrue(mExecutor.getErrors().get(0).getMessage().contains("one"));
assertTrue(mExecutor.getErrors().get(1).getMessage().contains("two"));
}
+
+ @Test
+ public void testExecution_timeout() {
+ List<Callable<Boolean>> callableTasks = new ArrayList<>();
+ for (ITestDevice device : mDevices) {
+ callableTasks.add(
+ () -> {
+ Thread.sleep(1000L);
+ return true;
+ });
+ }
+
+ List<Boolean> results = mExecutor.invokeAll(callableTasks, 1L, TimeUnit.MILLISECONDS);
+ assertEquals(0, results.size());
+ assertTrue(mExecutor.hasErrors());
+ assertEquals(2, mExecutor.getErrors().size());
+ assertTrue(mExecutor.getErrors().get(0) instanceof CancellationException);
+ assertTrue(mExecutor.getErrors().get(1) instanceof CancellationException);
+ }
}
diff --git a/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java b/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java
index dd4344050..f45238a55 100644
--- a/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java
+++ b/tests/src/com/android/tradefed/util/statsd/MetricUtilTest.java
@@ -176,12 +176,14 @@ public class MetricUtilTest {
any(CollectingByteOutputReceiver.class));
List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID);
// Resulting list should have two metrics.
- assertThat(data.size()).comparesEqualTo(2);
+ assertThat(data.size()).isEquivalentAccordingToCompareTo(2);
// The first metric should correspond to METRIC_2_* as its timestamp is earlier.
- assertThat(data.get(0).getElapsedTimestampNanos()).comparesEqualTo(METRIC_2_NANOS);
+ assertThat(data.get(0).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_2_NANOS);
assertThat(data.get(0).getAtom().hasBleScanResultReceived()).isTrue();
// The second metric should correspond to METRIC_1_*.
- assertThat(data.get(1).getElapsedTimestampNanos()).comparesEqualTo(METRIC_1_NANOS);
+ assertThat(data.get(1).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_1_NANOS);
assertThat(data.get(1).getAtom().hasBleScanStateChanged()).isTrue();
}
@@ -204,7 +206,7 @@ public class MetricUtilTest {
any(CollectingByteOutputReceiver.class));
List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID);
// Resulting list should be empty.
- assertThat(data.size()).comparesEqualTo(0);
+ assertThat(data.size()).isEquivalentAccordingToCompareTo(0);
}
/**
@@ -230,12 +232,14 @@ public class MetricUtilTest {
any(CollectingByteOutputReceiver.class));
List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID);
// Resulting list should have two metrics.
- assertThat(data.size()).comparesEqualTo(2);
+ assertThat(data.size()).isEquivalentAccordingToCompareTo(2);
// The first metric should correspond to METRIC_2_* as its timestamp is earlier.
- assertThat(data.get(0).getElapsedTimestampNanos()).comparesEqualTo(METRIC_2_NANOS);
+ assertThat(data.get(0).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_2_NANOS);
assertThat(data.get(0).getAtom().hasBleScanResultReceived()).isTrue();
// The second metric should correspond to METRIC_1_*.
- assertThat(data.get(1).getElapsedTimestampNanos()).comparesEqualTo(METRIC_1_NANOS);
+ assertThat(data.get(1).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_1_NANOS);
assertThat(data.get(1).getAtom().hasBleScanStateChanged()).isTrue();
}
@@ -261,12 +265,14 @@ public class MetricUtilTest {
any(CollectingByteOutputReceiver.class));
List<EventMetricData> data = MetricUtil.getEventMetricData(mTestDevice, CONFIG_ID);
// Resulting list should have two metrics.
- assertThat(data.size()).comparesEqualTo(2);
+ assertThat(data.size()).isEquivalentAccordingToCompareTo(2);
// The first metric should correspond to METRIC_1_* as its timestamp is earlier.
- assertThat(data.get(0).getElapsedTimestampNanos()).comparesEqualTo(METRIC_1_NANOS);
+ assertThat(data.get(0).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_1_NANOS);
assertThat(data.get(0).getAtom().hasBleScanStateChanged()).isTrue();
// The second metric should correspond to METRIC_3_*.
- assertThat(data.get(1).getElapsedTimestampNanos()).comparesEqualTo(METRIC_3_NANOS);
+ assertThat(data.get(1).getElapsedTimestampNanos())
+ .isEquivalentAccordingToCompareTo(METRIC_3_NANOS);
assertThat(data.get(1).getAtom().hasBleScanStateChanged()).isTrue();
}
diff --git a/util-apps/ContentProvider/main/AndroidManifest.xml b/util-apps/ContentProvider/main/AndroidManifest.xml
index ac37e685e..15cb3fa30 100644
--- a/util-apps/ContentProvider/main/AndroidManifest.xml
+++ b/util-apps/ContentProvider/main/AndroidManifest.xml
@@ -19,6 +19,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<application>
<provider