Snap for 8952093 from 9b2ace18f2df233012b1ba17e506a34fb9ec6240 to sdk-release
Change-Id: Ib86ffced43b4563adfbed5be4b11264ab07d6f6e
diff --git a/.classpath b/.classpath
index 5d181ad..5eb32b6 100644
--- a/.classpath
+++ b/.classpath
@@ -53,5 +53,6 @@
<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/tools/tradefederation/core/tradefed-invocation-grpc/linux_glibc_common/javac/tradefed-invocation-grpc.jar"/>
<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/tools/tradefederation/core/lab-resource-grpc/linux_glibc_common/javac/lab-resource-grpc.jar"/>
<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/tools/tradefederation/core/tradefed-device-manager-grpc/linux_glibc_common/javac/tradefed-device-manager-grpc.jar"/>
+ <classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/external/perfetto/perfetto_trace-full/linux_glibc_common/combined/perfetto_trace-full.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/common_util/Android.bp b/common_util/Android.bp
index 1b4fa4e..e03cc3d 100644
--- a/common_util/Android.bp
+++ b/common_util/Android.bp
@@ -32,6 +32,8 @@
],
static_libs: [
"commons-compress-prebuilt",
+ // Trace protos to do invocation tracing
+ "perfetto_trace-full",
],
libs: [
"ddmlib-prebuilt",
diff --git a/common_util/com/android/tradefed/invoker/logger/InvocationMetricLogger.java b/common_util/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
index 70c7c8c..bba8557 100644
--- a/common_util/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
+++ b/common_util/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
@@ -51,6 +51,7 @@
INSTRUMENTATION_RERUN_FROM_FILE("instrumentation_rerun_from_file", true),
INSTRUMENTATION_RERUN_SERIAL("instrumentation_rerun_serial", true),
DOWNLOAD_RETRY_COUNT("download_retry_count", true),
+ METADATA_RETRY_COUNT("metadata_retry_count", true),
XTS_STAGE_TESTS_TIME("xts_stage_tests_time_ms", true),
XTS_STAGE_TESTS_BYTES("xts_stage_tests_bytes", true),
XTS_PARTIAL_DOWNLOAD_FALLBACK_COUNT("xts_partial_download_fallback_count", true),
@@ -136,6 +137,9 @@
CLOUD_DEVICE_STABLE_HOST_IMAGE("stable_host_image_name", false),
CLOUD_DEVICE_STABLE_HOST_IMAGE_PROJECT("stable_host_image_project", false),
+ SHUTDOWN_BEFORE_TEST("shutdown_before_test", false),
+ SHUTDOWN_AFTER_TEST("shutdown_after_test", false),
+ SHUTDOWN_LATENCY("shutdown_latency_ms", false),
SHUTDOWN_HARD_LATENCY("shutdown_hard_latency_ms", false),
DEVICE_COUNT("device_count", false),
DEVICE_DONE_TIMESTAMP("device_done_timestamp", false),
@@ -181,6 +185,7 @@
SETUP_START("tf_setup_start_timestamp", false),
SETUP_END("tf_setup_end_timestamp", false),
SETUP_PAIR("tf_setup_pair_timestamp", true),
+ TEST_SETUP_PAIR("tf_test_setup_pair_timestamp", true),
FLASHING_FROM_FASTBOOTD("flashing_from_fastbootd", true),
FLASHING_TIME("flashing_time_ms", true),
FLASHING_PERMIT_LATENCY("flashing_permit_latency_ms", true),
@@ -203,6 +208,24 @@
LAB_PREPARER_NOT_ILAB("lab_preparer_not_ilab", true),
TARGET_PREPARER_IS_ILAB("target_preparer_is_ilab", true),
+
+ ART_RUN_TEST_CHECKER_COMMAND_TIME_MS("art_run_test_checker_command_time_ms", true),
+
+ // Following are trace events also reporting as metrics
+ invocation_warm_up("invocation_warm_up", true),
+ dynamic_download("dynamic_download", true),
+ fetch_artifact("fetch_artifact", true),
+ start_logcat("start_logcat", true),
+ pre_sharding_required_setup("pre_sharding_required_setup", true),
+ sharding("sharding", true),
+ lab_setup("lab_setup", true),
+ test_setup("test_setup", true),
+ test_execution("test_execution", true),
+ check_device_availability("check_device_availability", true),
+ bugreport("bugreport", true),
+ test_teardown("test_teardown", true),
+ test_cleanup("test_cleanup", true),
+ log_and_release_device("log_and_release_device", true),
;
private final String mKeyName;
diff --git a/common_util/com/android/tradefed/invoker/tracing/ActiveTrace.java b/common_util/com/android/tradefed/invoker/tracing/ActiveTrace.java
new file mode 100644
index 0000000..2a495a2
--- /dev/null
+++ b/common_util/com/android/tradefed/invoker/tracing/ActiveTrace.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2022 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.invoker.tracing;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.util.FileUtil;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.zip.GZIPInputStream;
+
+import perfetto.protos.PerfettoTrace.DebugAnnotation;
+import perfetto.protos.PerfettoTrace.ProcessDescriptor;
+import perfetto.protos.PerfettoTrace.ThreadDescriptor;
+import perfetto.protos.PerfettoTrace.Trace;
+import perfetto.protos.PerfettoTrace.TracePacket;
+import perfetto.protos.PerfettoTrace.TrackDescriptor;
+import perfetto.protos.PerfettoTrace.TrackEvent;
+
+/** Main class helping to describe and manage an active trace. */
+public class ActiveTrace {
+
+ public static final String TRACE_KEY = "invocation-trace";
+ private final long pid;
+ private final long tid;
+ private final long traceUuid;
+ private final int uid = 5555; // TODO: collect a real uid
+ private final Map<Long, Long> mThreadToTracker;
+ // File where the final trace gets outputed
+ private File mTraceOutput;
+
+ /**
+ * Constructor.
+ *
+ * @param pid Current process id
+ * @param tid Current thread id
+ */
+ public ActiveTrace(long pid, long tid) {
+ this.pid = pid;
+ this.tid = tid;
+ this.traceUuid = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE;
+ mThreadToTracker = new HashMap<>();
+ }
+
+ /** Start the tracing and report the metadata of the trace. */
+ public void startTracing(boolean isSubprocess) {
+ if (mTraceOutput != null) {
+ throw new IllegalStateException("Tracing was already started.");
+ }
+ try {
+ mTraceOutput = FileUtil.createTempFile(TRACE_KEY, ".perfetto-trace");
+ } catch (IOException e) {
+ CLog.e(e);
+ }
+ // Initialize all the trace metadata
+ createMainInvocationTracker((int) pid, (int) tid, traceUuid, isSubprocess);
+ }
+
+ /** Provide the trace file from a subprocess to be added to the parent. */
+ public void addSubprocessTrace(File subTrace) {
+ if (mTraceOutput == null) {
+ return;
+ }
+
+ try (FileInputStream stream = new FileInputStream(subTrace)) {
+ try (GZIPInputStream gzip = new GZIPInputStream(stream)) {
+ CLog.logAndDisplay(LogLevel.DEBUG, "merging with gzipped %s", subTrace);
+ FileUtil.writeToFile(gzip, mTraceOutput, true);
+ return;
+ } catch (IOException e) {
+ CLog.logAndDisplay(LogLevel.DEBUG, "%s isn't gzip.", subTrace);
+ }
+ } catch (IOException e) {
+ CLog.e(e);
+ }
+
+ try (FileInputStream stream = new FileInputStream(subTrace)) {
+ FileUtil.writeToFile(stream, mTraceOutput, true);
+ } catch (IOException e) {
+ CLog.e(e);
+ }
+ }
+
+ public void reportTraceEvent(String categories, String name, TrackEvent.Type type) {
+ reportTraceEvent(categories, name, (int) tid, null, type);
+ }
+
+ /**
+ * Very basic event reporting to do START / END of traces.
+ *
+ * @param categories Category associated with event
+ * @param name Event name
+ * @param type Type of the event being reported
+ */
+ public void reportTraceEvent(
+ String categories, String name, int threadId, String threadName, TrackEvent.Type type) {
+ long traceIdentifier = traceUuid;
+ if (threadId != this.tid) {
+ if (mThreadToTracker.containsKey(Long.valueOf(threadId))) {
+ traceIdentifier = mThreadToTracker.get(Long.valueOf(threadId));
+ } else {
+ traceIdentifier = UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE;
+ createThreadTracker((int) pid, threadId, threadName, traceIdentifier);
+ mThreadToTracker.put(Long.valueOf(threadId), Long.valueOf(traceIdentifier));
+ }
+ }
+ TracePacket.Builder tracePacket =
+ TracePacket.newBuilder()
+ .setTrustedUid(uid)
+ .setTrustedPid((int) pid)
+ .setTimestamp(System.nanoTime())
+ .setTrustedPacketSequenceId(1)
+ .setSequenceFlags(1)
+ .setProcessDescriptor(ProcessDescriptor.newBuilder().setPid((int) pid))
+ .setThreadDescriptor(ThreadDescriptor.newBuilder().setTid(threadId))
+ .setTrackEvent(
+ TrackEvent.newBuilder()
+ .setTrackUuid(traceIdentifier)
+ .setName(name)
+ .setType(type)
+ .addCategories(categories)
+ .addDebugAnnotations(
+ DebugAnnotation.newBuilder().setName(name)));
+ writeToTrace(tracePacket.build());
+ }
+
+ /** Reports the final trace files and clean up resources as needed. */
+ public File finalizeTracing() {
+ CLog.logAndDisplay(LogLevel.DEBUG, "Finalizing trace: %s", mTraceOutput);
+ File trace = mTraceOutput;
+ mTraceOutput = null;
+ return trace;
+ }
+
+ private String createProcessName(boolean isSubprocess) {
+ if (isSubprocess) {
+ return "subprocess-test-invocation";
+ }
+ return "test-invocation";
+ }
+
+ private void createMainInvocationTracker(
+ int pid, int tid, long traceUuid, boolean isSubprocess) {
+ TrackDescriptor.Builder descriptor =
+ TrackDescriptor.newBuilder()
+ .setUuid(traceUuid)
+ .setName(createProcessName(isSubprocess))
+ .setThread(
+ ThreadDescriptor.newBuilder()
+ .setTid(tid)
+ .setThreadName("invocation-thread")
+ .setPid(pid))
+ .setProcess(
+ ProcessDescriptor.newBuilder()
+ .setPid(pid)
+ .setProcessName(createProcessName(isSubprocess)));
+
+ TracePacket.Builder traceTrackDescriptor =
+ TracePacket.newBuilder()
+ .setTrustedUid(uid)
+ .setTimestamp(System.nanoTime())
+ .setTrustedPacketSequenceId(1)
+ .setSequenceFlags(1)
+ .setTrustedPid(pid)
+ .setTrackDescriptor(descriptor.build());
+
+ writeToTrace(traceTrackDescriptor.build());
+ }
+
+ private void createThreadTracker(int pid, int tid, String threadName, long traceUuid) {
+ TrackDescriptor.Builder descriptor =
+ TrackDescriptor.newBuilder()
+ .setUuid(traceUuid)
+ .setThread(
+ ThreadDescriptor.newBuilder()
+ .setTid(tid)
+ .setThreadName(threadName)
+ .setPid(pid));
+
+ TracePacket.Builder traceTrackDescriptor =
+ TracePacket.newBuilder()
+ .setTrustedUid(uid)
+ .setTimestamp(System.nanoTime())
+ .setTrustedPacketSequenceId(1)
+ .setSequenceFlags(1)
+ .setTrustedPid(pid)
+ .setTrackDescriptor(descriptor.build());
+
+ writeToTrace(traceTrackDescriptor.build());
+ }
+
+ private synchronized void writeToTrace(TracePacket packet) {
+ if (mTraceOutput == null) {
+ return;
+ }
+ // Perfetto UI supports repeated Trace
+ Trace wrappingTrace = Trace.newBuilder().addPacket(packet).build();
+ try (FileOutputStream out = new FileOutputStream(mTraceOutput, true)) {
+ wrappingTrace.writeTo(out);
+ out.flush();
+ } catch (IOException e) {
+ CLog.e("Failed to write execution trace to file.");
+ CLog.e(e);
+ }
+ }
+}
diff --git a/common_util/com/android/tradefed/invoker/tracing/CloseableTraceScope.java b/common_util/com/android/tradefed/invoker/tracing/CloseableTraceScope.java
new file mode 100644
index 0000000..d9952f4
--- /dev/null
+++ b/common_util/com/android/tradefed/invoker/tracing/CloseableTraceScope.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2022 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.invoker.tracing;
+
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
+
+import com.google.common.base.Enums;
+import com.google.common.base.Optional;
+
+import perfetto.protos.PerfettoTrace.TrackEvent;
+
+/** A scoped class that allows to report tracing section via try-with-resources */
+public class CloseableTraceScope implements AutoCloseable {
+
+ private static final String DEFAULT_CATEGORY = "invocation";
+ private final String category;
+ private final String name;
+ private final long startTime;
+
+ /**
+ * Report a scoped trace.
+ *
+ * @param category The category of the operation
+ * @param name The name for reporting the section
+ */
+ public CloseableTraceScope(String category, String name) {
+ this.category = category;
+ this.name = name;
+ this.startTime = System.currentTimeMillis();
+ ActiveTrace trace = TracingLogger.getActiveTrace();
+ if (trace == null) {
+ return;
+ }
+ int threadId = (int) Thread.currentThread().getId();
+ String threadName = Thread.currentThread().getName();
+ trace.reportTraceEvent(
+ category, name, threadId, threadName, TrackEvent.Type.TYPE_SLICE_BEGIN);
+
+ }
+
+ /** Constructor. */
+ public CloseableTraceScope(String name) {
+ this(DEFAULT_CATEGORY, name);
+ }
+
+ /** Constructor for reporting scope from threads. */
+ public CloseableTraceScope() {
+ this(DEFAULT_CATEGORY, Thread.currentThread().getName());
+ }
+
+ @Override
+ public void close() {
+ ActiveTrace trace = TracingLogger.getActiveTrace();
+ if (trace == null) {
+ return;
+ }
+ int threadId = (int) Thread.currentThread().getId();
+ String threadName = Thread.currentThread().getName();
+ trace.reportTraceEvent(
+ category, name, threadId, threadName, TrackEvent.Type.TYPE_SLICE_END);
+ Optional<InvocationMetricKey> optionalKey =
+ Enums.getIfPresent(InvocationMetricKey.class, name);
+ if (optionalKey.isPresent()) {
+ InvocationMetricLogger.addInvocationPairMetrics(
+ optionalKey.get(), startTime, System.currentTimeMillis());
+ }
+ }
+}
diff --git a/common_util/com/android/tradefed/invoker/tracing/TracingLogger.java b/common_util/com/android/tradefed/invoker/tracing/TracingLogger.java
new file mode 100644
index 0000000..1b2c866
--- /dev/null
+++ b/common_util/com/android/tradefed/invoker/tracing/TracingLogger.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2022 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.invoker.tracing;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/** Class that helps to manage tracing for each test invocation. */
+public class TracingLogger {
+
+ private static final Map<ThreadGroup, ActiveTrace> mPerGroupActiveTrace =
+ Collections.synchronizedMap(new HashMap<ThreadGroup, ActiveTrace>());
+
+ /**
+ * Creates and register an active trace for an invocation.
+ *
+ * @param pid Current process id
+ * @param tid Current thread id
+ */
+ public static ActiveTrace createActiveTrace(long pid, long tid) {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ synchronized (mPerGroupActiveTrace) {
+ ActiveTrace trace = new ActiveTrace(pid, tid);
+ mPerGroupActiveTrace.put(group, trace);
+ return trace;
+ }
+ }
+
+ /** Returns the current active trace for the invocation, or null if none. */
+ public static ActiveTrace getActiveTrace() {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ synchronized (mPerGroupActiveTrace) {
+ return mPerGroupActiveTrace.get(group);
+ }
+ }
+
+ /** Finalize the tracing and clear the tracking. */
+ public static File finalizeTrace() {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ synchronized (mPerGroupActiveTrace) {
+ ActiveTrace trace = mPerGroupActiveTrace.remove(group);
+ if (trace != null) {
+ return trace.finalizeTracing();
+ }
+ }
+ return null;
+ }
+}
diff --git a/common_util/com/android/tradefed/result/LogDataType.java b/common_util/com/android/tradefed/result/LogDataType.java
index 689e619..5dca1c6 100644
--- a/common_util/com/android/tradefed/result/LogDataType.java
+++ b/common_util/com/android/tradefed/result/LogDataType.java
@@ -73,6 +73,7 @@
CPU_INFO("txt", "text/plain", false, true), // dumpsys cpuinfo
JACOCO_CSV("csv", "text/csv", false, true), // JaCoCo coverage report in CSV format
JACOCO_XML("xml", "text/xml", false, true), // JaCoCo coverage report in XML format
+ JACOCO_EXEC("exec", "application/octet-stream", false, false), //JaCoCo coverage execution file
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),
diff --git a/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java b/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java
index ed952a4..902f9a1 100644
--- a/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java
+++ b/common_util/com/android/tradefed/result/error/InfraErrorIdentifier.java
@@ -41,6 +41,9 @@
LAB_HOST_FILESYSTEM_ERROR(500_013, FailureStatus.INFRA_FAILURE),
TRADEFED_SHUTTING_DOWN(500_014, FailureStatus.INFRA_FAILURE),
LAB_HOST_FILESYSTEM_FULL(500_015, FailureStatus.INFRA_FAILURE),
+ TRADEFED_SKIPPED_TESTS_DURING_SHUTDOWN(500_016, FailureStatus.CANCELLED),
+ // 500_400 - 500_500: General errors - subprocess related
+ INTERRUPTED_DURING_SUBPROCESS_SHUTDOWN(500_401, FailureStatus.INFRA_FAILURE),
// 500_501 - 501_000: Build, Artifacts download related errors
ARTIFACT_REMOTE_PATH_NULL(500_501, FailureStatus.INFRA_FAILURE),
@@ -91,6 +94,7 @@
UNEXPECTED_DEVICE_CONFIGURED(505_254, FailureStatus.CUSTOMER_ISSUE),
KEYSTORE_CONFIG_ERROR(505_255, FailureStatus.DEPENDENCY_ISSUE),
TEST_MAPPING_PATH_COLLISION(505_256, FailureStatus.DEPENDENCY_ISSUE),
+ TEST_MAPPING_FILE_FORMAT_ISSUE(505_257, FailureStatus.CUSTOMER_ISSUE),
UNDETERMINED(510_000, FailureStatus.UNSET);
diff --git a/common_util/com/android/tradefed/util/StreamUtil.java b/common_util/com/android/tradefed/util/StreamUtil.java
index e2b1341..8526886 100644
--- a/common_util/com/android/tradefed/util/StreamUtil.java
+++ b/common_util/com/android/tradefed/util/StreamUtil.java
@@ -219,11 +219,15 @@
inStream.skip(offset);
byte[] buf = new byte[BUF_SIZE];
long totalRetrievedSize = 0;
- int retrievedSize = -1;
try {
- while ((retrievedSize = inStream.read(buf)) != -1) {
- if (size > 0 && size < totalRetrievedSize + retrievedSize) {
- retrievedSize = (int) (size - totalRetrievedSize);
+ while (true) {
+ int maxReadSize =
+ size > 0
+ ? (int) Math.min(size - totalRetrievedSize, buf.length)
+ : buf.length;
+ int retrievedSize = inStream.read(buf, 0, maxReadSize);
+ if (retrievedSize == -1) {
+ break;
}
outStream.write(buf, 0, retrievedSize);
totalRetrievedSize += retrievedSize;
diff --git a/global_configuration/com/android/tradefed/config/GlobalConfiguration.java b/global_configuration/com/android/tradefed/config/GlobalConfiguration.java
index 45452ab..9d93d23 100644
--- a/global_configuration/com/android/tradefed/config/GlobalConfiguration.java
+++ b/global_configuration/com/android/tradefed/config/GlobalConfiguration.java
@@ -873,7 +873,7 @@
public File cloneConfigWithFilter(Set<String> exclusionPatterns, String... allowlistConfigs)
throws IOException {
return cloneConfigWithFilter(
- exclusionPatterns, new NoOpConfigOptionValueTransformer(), allowlistConfigs);
+ exclusionPatterns, new NoOpConfigOptionValueTransformer(), true, allowlistConfigs);
}
/** {@inheritDoc} */
@@ -881,17 +881,22 @@
public File cloneConfigWithFilter(
Set<String> exclusionPatterns,
IConfigOptionValueTransformer transformer,
+ boolean deepCopy,
String... allowlistConfigs)
throws IOException {
IConfigurationFactory configFactory = getConfigurationFactory();
IGlobalConfiguration copy = null;
- try {
- // Use a copy with default original options
- copy =
- configFactory.createGlobalConfigurationFromArgs(
- mOriginalArgs, new ArrayList<>());
- } catch (ConfigurationException e) {
- throw new IOException(e);
+ if (deepCopy) {
+ try {
+ // Use a copy with default original options
+ copy =
+ configFactory.createGlobalConfigurationFromArgs(
+ mOriginalArgs, new ArrayList<>());
+ } catch (ConfigurationException e) {
+ throw new IOException(e);
+ }
+ } else {
+ copy = this;
}
File filteredGlobalConfig = FileUtil.createTempFile("filtered_global_config", ".config");
diff --git a/global_configuration/com/android/tradefed/config/IGlobalConfiguration.java b/global_configuration/com/android/tradefed/config/IGlobalConfiguration.java
index 996d0cd..45ed870 100644
--- a/global_configuration/com/android/tradefed/config/IGlobalConfiguration.java
+++ b/global_configuration/com/android/tradefed/config/IGlobalConfiguration.java
@@ -393,6 +393,7 @@
public File cloneConfigWithFilter(
Set<String> exclusionPatterns,
IConfigOptionValueTransformer transformer,
+ boolean deepCopy,
String... allowlistConfigs)
throws IOException;
diff --git a/global_configuration/com/android/tradefed/host/HostOptions.java b/global_configuration/com/android/tradefed/host/HostOptions.java
index 1cee254..876d0ad 100644
--- a/global_configuration/com/android/tradefed/host/HostOptions.java
+++ b/global_configuration/com/android/tradefed/host/HostOptions.java
@@ -128,6 +128,7 @@
private List<String> mPreconfiguredVirtualDevicePool = new ArrayList<>();
private Map<PermitLimitType, Semaphore> mConcurrentLocks = new HashMap<>();
+ private Map<PermitLimitType, Integer> mInternalConcurrentLimits = new HashMap<>();
/** {@inheritDoc} */
@Override
@@ -213,8 +214,8 @@
/** {@inheritDoc} */
@Override
- public Set<String> getKnownPreconfigureVirtualDevicePool() {
- return new HashSet<>(mPreconfiguredVirtualDevicePool);
+ public List<String> getKnownPreconfigureVirtualDevicePool() {
+ return new ArrayList<>(mPreconfiguredVirtualDevicePool);
}
/** {@inheritDoc} */
@@ -262,15 +263,18 @@
if (!mConcurrentLocks.isEmpty()) {
return;
}
+ mInternalConcurrentLimits.putAll(mConcurrentLimit);
// Backfill flasher & download limit from their dedicated option
- if (!mConcurrentLimit.containsKey(PermitLimitType.CONCURRENT_FLASHER)) {
- mConcurrentLimit.put(PermitLimitType.CONCURRENT_FLASHER, mConcurrentFlasherLimit);
+ if (!mInternalConcurrentLimits.containsKey(PermitLimitType.CONCURRENT_FLASHER)) {
+ mInternalConcurrentLimits.put(
+ PermitLimitType.CONCURRENT_FLASHER, mConcurrentFlasherLimit);
}
- if (!mConcurrentLimit.containsKey(PermitLimitType.CONCURRENT_DOWNLOAD)) {
- mConcurrentLimit.put(PermitLimitType.CONCURRENT_DOWNLOAD, mConcurrentDownloadLimit);
+ if (!mInternalConcurrentLimits.containsKey(PermitLimitType.CONCURRENT_DOWNLOAD)) {
+ mInternalConcurrentLimits.put(
+ PermitLimitType.CONCURRENT_DOWNLOAD, mConcurrentDownloadLimit);
}
- for (Entry<PermitLimitType, Integer> limits : mConcurrentLimit.entrySet()) {
+ for (Entry<PermitLimitType, Integer> limits : mInternalConcurrentLimits.entrySet()) {
if (limits.getValue() == null) {
continue;
}
@@ -287,7 +291,9 @@
CLog.i(
"Requesting a '%s' permit out of the max limit of %s. Current queue "
+ "length: %s",
- type, mConcurrentLimit.get(type), mConcurrentLocks.get(type).getQueueLength());
+ type,
+ mInternalConcurrentLimits.get(type),
+ mConcurrentLocks.get(type).getQueueLength());
try {
mConcurrentLocks.get(type).acquire();
} catch (InterruptedException e) {
@@ -316,6 +322,6 @@
if (!mConcurrentLocks.containsKey(type)) {
return 0;
}
- return mConcurrentLimit.get(type) - mConcurrentLocks.get(type).availablePermits();
+ return mInternalConcurrentLimits.get(type) - mConcurrentLocks.get(type).availablePermits();
}
}
diff --git a/global_configuration/com/android/tradefed/host/IHostOptions.java b/global_configuration/com/android/tradefed/host/IHostOptions.java
index d9634f5..1f27475 100644
--- a/global_configuration/com/android/tradefed/host/IHostOptions.java
+++ b/global_configuration/com/android/tradefed/host/IHostOptions.java
@@ -83,7 +83,7 @@
Set<String> getKnownRemoteDeviceIpPool();
/** Known preconfigured virtual device pool. */
- Set<String> getKnownPreconfigureVirtualDevicePool();
+ List<String> getKnownPreconfigureVirtualDevicePool();
/** Check if it should use the zip64 format in partial download or not. */
boolean getUseZip64InPartialDownload();
diff --git a/javatests/.classpath b/javatests/.classpath
index b950e2d..84cbf5b 100644
--- a/javatests/.classpath
+++ b/javatests/.classpath
@@ -40,5 +40,6 @@
<classpathentry kind="var" path="TRADEFED_ROOT/prebuilts/tools/common/m2/repository/com/google/code/gson/gson/2.8.0/gson-2.8.0.jar"/>
<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/external/opencensus-java/api/opencensus-java-api/linux_glibc_common/javac/opencensus-java-api.jar"/>
<classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/tools/tradefederation/core/tradefed-device-manager-grpc/linux_glibc_common/javac/tradefed-device-manager-grpc.jar"/>
+ <classpathentry kind="var" path="TRADEFED_ROOT/out/soong/.intermediates/external/perfetto/perfetto_trace-full/linux_glibc_common/combined/perfetto_trace-full.jar"/>
<classpathentry kind="output" path="bin"/>
</classpath>
diff --git a/javatests/Android.bp b/javatests/Android.bp
index 99e222e..3e2eabd 100644
--- a/javatests/Android.bp
+++ b/javatests/Android.bp
@@ -61,6 +61,7 @@
"libprotobuf-java-full",
"truth-prebuilt",
"loganalysis",
+ "perfetto_trace-full",
],
manifest: "MANIFEST.mf",
diff --git a/javatests/com/android/tradefed/UnitTests.java b/javatests/com/android/tradefed/UnitTests.java
index cc561cb..fbfdad9 100644
--- a/javatests/com/android/tradefed/UnitTests.java
+++ b/javatests/com/android/tradefed/UnitTests.java
@@ -86,6 +86,7 @@
import com.android.tradefed.device.WaitDeviceRecoveryTest;
import com.android.tradefed.device.WifiHelperTest;
import com.android.tradefed.device.cloud.AcloudConfigParserTest;
+import com.android.tradefed.device.cloud.CommonLogRemoteFileUtilTest;
import com.android.tradefed.device.cloud.GceAvdInfoTest;
import com.android.tradefed.device.cloud.GceManagerTest;
import com.android.tradefed.device.cloud.GceRemoteCmdFormatterTest;
@@ -231,6 +232,9 @@
import com.android.tradefed.suite.checker.SystemServerStatusCheckerTest;
import com.android.tradefed.suite.checker.TimeStatusCheckerTest;
import com.android.tradefed.suite.checker.UserCheckerTest;
+import com.android.tradefed.suite.checker.baseline.DeviceBaselineSetterTest;
+import com.android.tradefed.suite.checker.baseline.LockSettingsBaselineSetterTest;
+import com.android.tradefed.suite.checker.baseline.SettingsBaselineSetterTest;
import com.android.tradefed.targetprep.AllTestAppsInstallSetupTest;
import com.android.tradefed.targetprep.AoaTargetPreparerTest;
import com.android.tradefed.targetprep.AppSetupTest;
@@ -554,6 +558,7 @@
// device.cloud
AcloudConfigParserTest.class,
+ CommonLogRemoteFileUtilTest.class,
GceAvdInfoTest.class,
GceManagerTest.class,
GceRemoteCmdFormatterTest.class,
@@ -808,6 +813,11 @@
TimeStatusCheckerTest.class,
UserCheckerTest.class,
+ // suite/checker/baseline
+ DeviceBaselineSetterTest.class,
+ LockSettingsBaselineSetterTest.class,
+ SettingsBaselineSetterTest.class,
+
// testtype
AndroidJUnitTestTest.class,
ArtGTestTest.class,
diff --git a/javatests/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java b/javatests/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
index 5e7c892..b9fb04a 100644
--- a/javatests/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
+++ b/javatests/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
@@ -253,13 +253,14 @@
}
@Override
- public void execCommand(IScheduledInvocationListener listener, String[] args)
+ public long execCommand(IScheduledInvocationListener listener, String[] args)
throws ConfigurationException, NoDeviceException {
ArrayList<String> execCmdArgs = new ArrayList<>();
for (String arg : args) {
execCmdArgs.add(arg);
}
mExecCmdArgs.push(execCmdArgs);
+ return 1;
}
@Override
@@ -896,13 +897,14 @@
}
@Override
- public void execCommand(IScheduledInvocationListener listener, String[] args)
+ public long execCommand(IScheduledInvocationListener listener, String[] args)
throws ConfigurationException, NoDeviceException {
ArrayList<String> execCmdArgs = new ArrayList<>();
for (String arg : args) {
execCmdArgs.add(arg);
}
mExecCmdArgs.push(execCmdArgs);
+ return 1;
}
@Override
diff --git a/javatests/com/android/tradefed/command/CommandRunnerTest.java b/javatests/com/android/tradefed/command/CommandRunnerTest.java
index dafa532..375d4f2 100644
--- a/javatests/com/android/tradefed/command/CommandRunnerTest.java
+++ b/javatests/com/android/tradefed/command/CommandRunnerTest.java
@@ -175,6 +175,7 @@
"-n",
"--no-return-null",
"--no-throw-build-error",
+ "--no-enable-tracing",
"--log-file-path",
mLogDir.getAbsolutePath()
};
@@ -193,6 +194,7 @@
mConfig.getAbsolutePath(),
"-n",
"--test-throw-unresponsive",
+ "--no-enable-tracing",
"--log-file-path",
mLogDir.getAbsolutePath()
};
@@ -215,6 +217,7 @@
mConfig.getAbsolutePath(),
"-n",
"--test-throw-not-available",
+ "--no-enable-tracing",
"--log-file-path",
mLogDir.getAbsolutePath()
};
@@ -237,6 +240,7 @@
mConfig.getAbsolutePath(),
"-n",
"--test-throw-runtime",
+ "--no-enable-tracing",
"--log-file-path",
mLogDir.getAbsolutePath()
};
@@ -282,6 +286,7 @@
mConfig.getAbsolutePath(),
"-s",
"impossibleSerialThatWillNotBeFound",
+ "--no-enable-tracing",
"--log-file-path",
mLogDir.getAbsolutePath()
};
diff --git a/javatests/com/android/tradefed/command/CommandSchedulerFuncTest.java b/javatests/com/android/tradefed/command/CommandSchedulerFuncTest.java
index 1f01084..c4b2a81 100644
--- a/javatests/com/android/tradefed/command/CommandSchedulerFuncTest.java
+++ b/javatests/com/android/tradefed/command/CommandSchedulerFuncTest.java
@@ -271,7 +271,7 @@
}
@Override
- public void notifyInvocationStopped(String message, ErrorIdentifier errorId) {
+ public void notifyInvocationForceStopped(String message, ErrorIdentifier errorId) {
printedStop = true;
}
}
diff --git a/javatests/com/android/tradefed/command/CommandSchedulerTest.java b/javatests/com/android/tradefed/command/CommandSchedulerTest.java
index 66a8632..3f2cb32 100644
--- a/javatests/com/android/tradefed/command/CommandSchedulerTest.java
+++ b/javatests/com/android/tradefed/command/CommandSchedulerTest.java
@@ -68,6 +68,8 @@
import com.android.tradefed.result.ILogSaver;
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.service.TradefedFeatureServer;
+import com.android.tradefed.service.management.DeviceManagementGrpcServer;
+import com.android.tradefed.service.management.TestInvocationManagementServer;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.Pair;
import com.android.tradefed.util.RunUtil;
@@ -140,6 +142,16 @@
}
@Override
+ protected TestInvocationManagementServer getTestInvocationManagementServer() {
+ return null;
+ }
+
+ @Override
+ protected DeviceManagementGrpcServer getDeviceManagementServer() {
+ return null;
+ }
+
+ @Override
protected IInvocationContext createInvocationContext() {
return mContext;
}
diff --git a/javatests/com/android/tradefed/config/ConfigurationFactoryTest.java b/javatests/com/android/tradefed/config/ConfigurationFactoryTest.java
index a249d5c..ae1005e 100644
--- a/javatests/com/android/tradefed/config/ConfigurationFactoryTest.java
+++ b/javatests/com/android/tradefed/config/ConfigurationFactoryTest.java
@@ -35,6 +35,7 @@
import com.android.tradefed.log.ILeveledLogOutput;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.targetprep.DeviceWiper;
+import com.android.tradefed.targetprep.ILabPreparer;
import com.android.tradefed.targetprep.StubTargetPreparer;
import com.android.tradefed.targetprep.multi.StubMultiTargetPreparer;
import com.android.tradefed.util.FileUtil;
@@ -1801,12 +1802,15 @@
}
}
+ /** Class to test out lab preparer parsing */
+ public static final class TestLabPreparer extends StubTargetPreparer implements ILabPreparer {}
+
@Test
public void testParse_labPreparer() throws Exception {
String normalConfig =
"<configuration description=\"desc\" >\n"
+ " <lab_preparer class=\""
- + StubTargetPreparer.class.getName()
+ + TestLabPreparer.class.getName()
+ "\">\n"
+ " <option name=\"test-boolean-option\" value=\"false\"/>"
+ " </lab_preparer>\n"
diff --git a/javatests/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java b/javatests/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
index 2cd60c2..1d89599 100644
--- a/javatests/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
+++ b/javatests/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
@@ -121,7 +121,8 @@
+ " \"logs\": ["
+ " {"
+ " \"path\": \"%s\","
- + " \"type\": \"KERNEL_LOG\""
+ + " \"type\": \"KERNEL_LOG\","
+ + " \"name\": \"kernel.1.log\""
+ " }"
+ " ]"
+ " }"
@@ -141,7 +142,8 @@
+ " \"logs\": ["
+ " {"
+ " \"path\": \"%s\","
- + " \"type\": \"KERNEL_LOG\""
+ + " \"type\": \"KERNEL_LOG\","
+ + " \"name\": \"kernel.1.log\""
+ " }"
+ " ]"
+ " }"
@@ -445,7 +447,7 @@
assertFinalDeviceState(mLocalAvd.getIDevice());
verify(acloudDeleteRunUtil).setEnvVariable(eq("TMPDIR"), any());
- verify(testLogger).testLog(any(), eq(LogDataType.KERNEL_LOG), any());
+ verify(testLogger).testLog(eq("kernel.1.log"), eq(LogDataType.KERNEL_LOG), any());
Assert.assertFalse(new File(reportFile.getValue()).exists());
for (Map.Entry<String, ArgumentCaptor<String>> entry : captureDirs.entrySet()) {
@@ -501,7 +503,7 @@
assertFinalDeviceState(mLocalAvd.getIDevice());
verify(acloudDeleteRunUtil).setEnvVariable(eq("TMPDIR"), any());
- verify(testLogger).testLog(any(), eq(LogDataType.KERNEL_LOG), any());
+ verify(testLogger).testLog(eq("kernel.1.log"), eq(LogDataType.KERNEL_LOG), any());
Assert.assertFalse(new File(reportFile.getValue()).exists());
Assert.assertFalse(capturedHostPackageDir.exists());
@@ -553,7 +555,7 @@
mLocalAvd.postInvocationTearDown(expectedException);
assertFinalDeviceState(mLocalAvd.getIDevice());
- verify(testLogger).testLog(any(), eq(LogDataType.KERNEL_LOG), any());
+ verify(testLogger).testLog(eq("kernel.1.log"), eq(LogDataType.KERNEL_LOG), any());
Assert.assertFalse(new File(reportFile.getValue()).exists());
Assert.assertFalse(capturedHostPackageDir.exists());
diff --git a/javatests/com/android/tradefed/device/TestDeviceFuncTest.java b/javatests/com/android/tradefed/device/TestDeviceFuncTest.java
index 854af8e..2c7f4b8 100644
--- a/javatests/com/android/tradefed/device/TestDeviceFuncTest.java
+++ b/javatests/com/android/tradefed/device/TestDeviceFuncTest.java
@@ -44,6 +44,7 @@
import com.android.tradefed.util.ProcessInfo;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.StreamUtil;
+import android.platform.test.annotations.FlakyTest;
import org.junit.Assume;
import org.junit.Before;
@@ -664,6 +665,7 @@
}
/** Test device soft-restart detection API. */
+ @FlakyTest
@Test
public void testDeviceSoftRestart() throws DeviceNotAvailableException {
CLog.i("testDeviceSoftRestartSince");
diff --git a/javatests/com/android/tradefed/device/cloud/CommonLogRemoteFileUtilTest.java b/javatests/com/android/tradefed/device/cloud/CommonLogRemoteFileUtilTest.java
new file mode 100644
index 0000000..15f49d0
--- /dev/null
+++ b/javatests/com/android/tradefed/device/cloud/CommonLogRemoteFileUtilTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2022 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.cloud;
+
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.device.TestDeviceOptions;
+import com.android.tradefed.log.ITestLogger;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.IRunUtil;
+import com.google.common.net.HostAndPort;
+import java.util.List;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
+
+/** Unit tests for {@link CommonLogRemoteFileUtil}. */
+@RunWith(JUnit4.class)
+public class CommonLogRemoteFileUtilTest {
+ @Mock private IRunUtil mRunUtil;
+ @Mock private ITestLogger mTestLogger;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ }
+
+ @Test
+ public void testFetchCommonFilesWithLogsEntries() throws ConfigurationException {
+ GceAvdInfo info = new GceAvdInfo("mock-instance", HostAndPort.fromString("192.0.2.2"));
+ List<GceAvdInfo.LogFileEntry> logs = info.getLogs();
+ logs.add(new GceAvdInfo.LogFileEntry("/test/text", LogDataType.TEXT, "log1.txt"));
+ logs.add(new GceAvdInfo.LogFileEntry("/test/log2.txt", LogDataType.UNKNOWN, ""));
+ logs.add(new GceAvdInfo.LogFileEntry("/tombstones", LogDataType.DIR, "tombstones-zip"));
+ TestDeviceOptions options = new TestDeviceOptions();
+ OptionSetter setter = new OptionSetter(options);
+ setter.setOptionValue("use-oxygen", "false");
+ setter.setOptionValue(TestDeviceOptions.INSTANCE_TYPE_OPTION, "CUTTLEFISH");
+ Mockito.when(mRunUtil.runTimedCmd(Mockito.anyLong(), Mockito.any()))
+ .thenReturn(new CommandResult(CommandStatus.SUCCESS));
+
+ CommonLogRemoteFileUtil.fetchCommonFiles(mTestLogger, info, options, mRunUtil);
+
+ Mockito.verify(mTestLogger)
+ .testLog(Mockito.eq("log1.txt"), Mockito.eq(LogDataType.TEXT), Mockito.any());
+ Mockito.verify(mTestLogger)
+ .testLog(
+ Mockito.startsWith("log2"), Mockito.eq(LogDataType.UNKNOWN), Mockito.any());
+ Mockito.verify(mTestLogger, Mockito.times(2))
+ .testLog(Mockito.any(), Mockito.any(), Mockito.any());
+ }
+
+ @Test
+ public void testFetchCommonFilesWithoutLogsEntries() throws ConfigurationException {
+ GceAvdInfo info = new GceAvdInfo("mock-instance", HostAndPort.fromString("192.0.2.2"));
+ TestDeviceOptions options = new TestDeviceOptions();
+ OptionSetter setter = new OptionSetter(options);
+ setter.setOptionValue("use-oxygen", "false");
+ setter.setOptionValue(TestDeviceOptions.INSTANCE_TYPE_OPTION, "CUTTLEFISH");
+ Mockito.when(mRunUtil.runTimedCmd(Mockito.anyLong(), Mockito.any()))
+ .thenReturn(new CommandResult(CommandStatus.SUCCESS));
+
+ CommonLogRemoteFileUtil.fetchCommonFiles(mTestLogger, info, options, mRunUtil);
+
+ Mockito.verify(mTestLogger)
+ .testLog(
+ Mockito.eq("cuttlefish_launcher.log"),
+ Mockito.eq(LogDataType.CUTTLEFISH_LOG),
+ Mockito.any());
+ }
+}
diff --git a/javatests/com/android/tradefed/device/cloud/GceAvdInfoTest.java b/javatests/com/android/tradefed/device/cloud/GceAvdInfoTest.java
index 0c409d5..4885591 100644
--- a/javatests/com/android/tradefed/device/cloud/GceAvdInfoTest.java
+++ b/javatests/com/android/tradefed/device/cloud/GceAvdInfoTest.java
@@ -57,7 +57,8 @@
+ " \"logs\": [\n"
+ " {\n"
+ " \"path\": \"/text/log\",\n"
- + " \"type\": \"TEXT\"\n"
+ + " \"type\": \"TEXT\",\n"
+ + " \"name\": \"log.txt\"\n"
+ " },\n"
+ " {\n"
+ " \"path\": \"/unknown/log\",\n"
@@ -75,10 +76,14 @@
assertNotNull(avd);
assertEquals(avd.hostAndPort().getHost(), "104.154.62.236");
assertEquals(avd.instanceName(), "gce-x86-phone-userdebug-2299773-22cf");
- Map<String, LogDataType> logs = avd.getLogs();
+ List<GceAvdInfo.LogFileEntry> logs = avd.getLogs();
assertEquals(logs.size(), 2);
- assertEquals(logs.get("/text/log"), LogDataType.TEXT);
- assertEquals(logs.get("/unknown/log"), LogDataType.UNKNOWN);
+ assertEquals(logs.get(0).path, "/text/log");
+ assertEquals(logs.get(0).type, LogDataType.TEXT);
+ assertEquals(logs.get(0).name, "log.txt");
+ assertEquals(logs.get(1).path, "/unknown/log");
+ assertEquals(logs.get(1).type, LogDataType.UNKNOWN);
+ assertEquals(logs.get(1).name, "");
assertTrue(avd.getBuildVars().isEmpty());
}
diff --git a/javatests/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java b/javatests/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
index 71191ba..3b90d2b 100644
--- a/javatests/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
+++ b/javatests/com/android/tradefed/device/metric/JavaCodeCoverageCollectorTest.java
@@ -16,6 +16,8 @@
package com.android.tradefed.device.metric;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -53,6 +55,7 @@
import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
+import org.jacoco.core.tools.ExecFileLoader;
import org.jacoco.core.data.ExecutionData;
import org.jacoco.core.data.ExecutionDataStore;
import org.jacoco.core.data.ExecutionDataWriter;
@@ -64,6 +67,7 @@
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
@@ -240,6 +244,7 @@
mockCoverageFileOnDevice(DEVICE_PATH);
when(mMockDevice.isAdbRoot()).thenReturn(false);
doReturn("").when(mMockDevice).executeShellCommand(anyString());
+ returnFileContentsOnShellCommand(mMockDevice, createTarGz(ImmutableMap.of()));
// Simulate a test run.
mCodeCoverageCollector.init(mMockContext, mFakeListener);
@@ -377,6 +382,92 @@
verifyNoMoreInteractions(mMockDevice);
}
+ @Test
+ public void testMergeSingleMeasurement_logReceived() throws Exception {
+ enableJavaCoverage();
+ mCoverageOptionsSetter.setOptionValue("merge-coverage", "true");
+
+ doReturn("").when(mMockDevice).executeShellCommand(anyString());
+
+ ByteString measurement = measurement(firstHalfCovered(JavaCodeCoverageCollector.class));
+ File tarGz = createTarGz(ImmutableMap.of("path/to/coverage.ec", measurement));
+ returnFileContentsOnShellCommand(mMockDevice, tarGz);
+
+ // Simulate a test run.
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, new HashMap<String, Metric>());
+
+ // Validate the logged coverage data.
+ ArgumentCaptor<ByteString> stream = ArgumentCaptor.forClass(ByteString.class);
+ verify(mFakeListener).testLog(anyString(), eq(LogDataType.COVERAGE), stream.capture());
+
+ ExecFileLoader execFileLoader = new ExecFileLoader();
+ execFileLoader.load(stream.getValue().newInput());
+
+ ExecutionDataStore execData = execFileLoader.getExecutionDataStore();
+ boolean[] firstHalf = new boolean[PROBE_COUNT];
+ for (int i = 0; i < PROBE_COUNT / 2; i++) {
+ firstHalf[i] = true;
+ }
+
+ assertThat(execData.contains(vmName(JavaCodeCoverageCollector.class))).isTrue();
+ assertThat(getProbes(JavaCodeCoverageCollector.class, execData)).isEqualTo(firstHalf);
+ }
+
+ @Test
+ public void testMergeMultipleMeasurements_logContainsAllData() throws Exception {
+ enableJavaCoverage();
+ mCoverageOptionsSetter.setOptionValue("merge-coverage", "true");
+
+ doReturn("").when(mMockDevice).executeShellCommand(anyString());
+
+ ByteString firstHalfCollector =
+ measurement(firstHalfCovered(JavaCodeCoverageCollector.class));
+ ByteString secondHalfCollector =
+ measurement(secondHalfCovered(JavaCodeCoverageCollector.class));
+ ByteString partialCollectorTest =
+ measurement(partiallyCovered(JavaCodeCoverageCollectorTest.class));
+ File tarGz =
+ createTarGz(
+ ImmutableMap.of(
+ "JavaCodeCoverageColletor1.ec", firstHalfCollector,
+ "JavaCodeCoverageCollector2.ec", secondHalfCollector,
+ "JavaCodeCoverageCollectorTest.ec", partialCollectorTest));
+ returnFileContentsOnShellCommand(mMockDevice, tarGz);
+
+ // Simulate a test run.
+ mCodeCoverageCollector.init(mMockContext, mFakeListener);
+ mCodeCoverageCollector.testRunStarted(RUN_NAME, TEST_COUNT);
+ mCodeCoverageCollector.testRunEnded(ELAPSED_TIME, new HashMap<String, Metric>());
+
+ // Validate the logged coverage data.
+ ArgumentCaptor<ByteString> stream = ArgumentCaptor.forClass(ByteString.class);
+ verify(mFakeListener).testLog(anyString(), eq(LogDataType.COVERAGE), stream.capture());
+
+ ExecFileLoader execFileLoader = new ExecFileLoader();
+ execFileLoader.load(stream.getValue().newInput());
+
+ ExecutionDataStore execData = execFileLoader.getExecutionDataStore();
+
+ // Check coverage data for JavaCodeCoverageCollector. All probes should be true if the data
+ // merged successfully.
+ boolean[] fullyCovered = new boolean[PROBE_COUNT];
+ Arrays.fill(fullyCovered, Boolean.TRUE);
+
+ assertThat(execData.contains(vmName(JavaCodeCoverageCollector.class))).isTrue();
+ assertThat(getProbes(JavaCodeCoverageCollector.class, execData)).isEqualTo(fullyCovered);
+
+ // Check coverage data for JavaCodeCoverageCollectorTest. Only the first probe should be
+ // true.
+ boolean[] partiallyCovered = new boolean[PROBE_COUNT];
+ partiallyCovered[0] = true;
+
+ assertThat(execData.contains(vmName(JavaCodeCoverageCollectorTest.class))).isTrue();
+ assertThat(getProbes(JavaCodeCoverageCollectorTest.class, execData))
+ .isEqualTo(partiallyCovered);
+ }
+
private void mockCoverageFileOnDevice(String devicePath)
throws IOException, DeviceNotAvailableException {
File coverageFile = folder.newFile(new File(devicePath).getName());
@@ -404,6 +495,22 @@
return new ExecutionData(classId(clazz), vmName(clazz), probes);
}
+ private static <T> ExecutionData firstHalfCovered(Class<T> clazz) throws IOException {
+ boolean[] probes = new boolean[PROBE_COUNT];
+ for (int i = 0; i < PROBE_COUNT / 2; i++) {
+ probes[i] = true;
+ }
+ return new ExecutionData(classId(clazz), vmName(clazz), probes);
+ }
+
+ private static <T> ExecutionData secondHalfCovered(Class<T> clazz) throws IOException {
+ boolean[] probes = new boolean[PROBE_COUNT];
+ for (int i = PROBE_COUNT / 2; i < PROBE_COUNT; i++) {
+ probes[i] = true;
+ }
+ return new ExecutionData(classId(clazz), vmName(clazz), probes);
+ }
+
private static <T> long classId(Class<T> clazz) throws IOException {
return Long.valueOf(CRC64.classId(classBytes(clazz).toByteArray()));
}
diff --git a/javatests/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java b/javatests/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
index b13a738..b90c75a 100644
--- a/javatests/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
+++ b/javatests/com/android/tradefed/invoker/RemoteInvocationExecutionTest.java
@@ -181,6 +181,7 @@
globalConfig.cloneConfigWithFilter(
new HashSet<>(),
new RemoteInvocationExecution.FileOptionValueTransformer("/foo/"),
+ true,
new String[] {
GlobalConfiguration.HOST_OPTIONS_TYPE_NAME,
GlobalConfiguration.KEY_STORE_TYPE_NAME
diff --git a/javatests/com/android/tradefed/invoker/ShardMainResultForwarderTest.java b/javatests/com/android/tradefed/invoker/ShardMainResultForwarderTest.java
index 6ff723a..c0e6679 100644
--- a/javatests/com/android/tradefed/invoker/ShardMainResultForwarderTest.java
+++ b/javatests/com/android/tradefed/invoker/ShardMainResultForwarderTest.java
@@ -184,8 +184,9 @@
Mockito.verify(mMockLogListener, times(1))
.testLog(Mockito.any(), Mockito.any(), Mockito.any());
// The callback was received all the way to the last reporter.
- Mockito.verify(mMockLogListener, times(1))
+ Mockito.verify(mMockLogListener, times(2))
.testLogSaved(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any());
+ Mockito.verify(mMockLogListener, times(1)).logAssociation(Mockito.any(), Mockito.any());
Mockito.verify(mMockLogListener, times(1)).invocationEnded(500L);
}
diff --git a/javatests/com/android/tradefed/invoker/TestInvocationTest.java b/javatests/com/android/tradefed/invoker/TestInvocationTest.java
index 8ac35b2..a2c3f1e 100644
--- a/javatests/com/android/tradefed/invoker/TestInvocationTest.java
+++ b/javatests/com/android/tradefed/invoker/TestInvocationTest.java
@@ -24,6 +24,7 @@
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.lenient;
+import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -363,7 +364,7 @@
}
private void verifyMockSuccessListeners() throws IOException {
- verifyMockListeners(InvocationStatus.SUCCESS, null, false, true, false);
+ verifyMockListeners(InvocationStatus.SUCCESS, null, false, true, false, false);
}
private void stubMockFailureListeners(Throwable throwable) throws IOException {
@@ -371,7 +372,7 @@
}
private void verifyMockFailureListeners(Throwable throwable) throws IOException {
- verifyMockListeners(InvocationStatus.FAILED, throwable, false, true, false);
+ verifyMockListeners(InvocationStatus.FAILED, throwable, false, true, false, false);
}
private void stubMockFailureListenersAny(Throwable throwable, boolean stubFailures)
@@ -381,7 +382,7 @@
private void verifyMockFailureListenersAny(Throwable throwable, boolean stubFailures)
throws IOException {
- verifyMockListeners(InvocationStatus.FAILED, throwable, stubFailures, true, false);
+ verifyMockListeners(InvocationStatus.FAILED, throwable, stubFailures, true, false, false);
}
private void stubMockFailureListeners(
@@ -391,15 +392,16 @@
private void verifyMockFailureListeners(
Throwable throwable, boolean stubFailures, boolean reportHostLog) throws IOException {
- verifyMockListeners(InvocationStatus.FAILED, throwable, stubFailures, reportHostLog, false);
+ verifyMockListeners(
+ InvocationStatus.FAILED, throwable, stubFailures, reportHostLog, false, false);
}
private void stubMockStoppedListeners() throws IOException {
stubMockListeners(InvocationStatus.SUCCESS, null, false, true, true);
}
- private void verifyMockStoppedListeners() throws IOException {
- verifyMockListeners(InvocationStatus.SUCCESS, null, false, true, true);
+ private void verifyMockStoppedListeners(boolean testSkipped) throws IOException {
+ verifyMockListeners(InvocationStatus.SUCCESS, null, false, true, true, testSkipped);
}
private void verifySummaryListener() {
@@ -606,7 +608,8 @@
Throwable throwable,
boolean stubFailures,
boolean reportHostLog,
- boolean stopped)
+ boolean stopped,
+ boolean testSkipped)
throws IOException {
// invocationStarted
mInOrderTestListener
@@ -714,7 +717,9 @@
} else {
// Handle build error bugreport listeners
if (throwable instanceof BuildError) {
- } else if (!(throwable instanceof TargetSetupError) && !mShardingEarlyFailure) {
+ } else if (!(throwable instanceof TargetSetupError)
+ && !mShardingEarlyFailure
+ && !testSkipped) {
// Handle test logcat listeners
mInOrderTestListener
.verify(mMockTestListener)
@@ -1149,7 +1154,8 @@
}
/**
- * Test metrics SHUTDOWN_HARD_LATENCY is collected when the invocation is stopped/interrupted.
+ * Test that tests were skipped and metrics SHUTDOWN_HARD_LATENCY is collected when the
+ * invocation is stopped/interrupted before test phase started.
*/
@Test
public void testInvoke_metricsCollectedWhenStopped() throws Throwable {
@@ -1159,14 +1165,15 @@
stubMockStoppedListeners();
stubNormalInvoke(test);
- mTestInvocation.notifyInvocationStopped("Stopped", InfraErrorIdentifier.INVOCATION_TIMEOUT);
+ mTestInvocation.notifyInvocationForceStopped(
+ "Stopped", InfraErrorIdentifier.INVOCATION_TIMEOUT);
mTestInvocation.invoke(mStubInvocationMetadata, mStubConfiguration, mockRescheduler);
- verify(test).run(Mockito.any(), Mockito.any());
+ verify(test, never()).run(Mockito.any(), Mockito.any());
verify(mMockPreparer).tearDown(Mockito.any(), Mockito.any());
verifyNormalInvoke(test);
- verifyMockStoppedListeners();
+ verifyMockStoppedListeners(true);
assertTrue(
mStubInvocationMetadata
diff --git a/javatests/com/android/tradefed/observatory/TestDiscoveryExecutorTest.java b/javatests/com/android/tradefed/observatory/TestDiscoveryExecutorTest.java
index 3101413..4709ad7 100644
--- a/javatests/com/android/tradefed/observatory/TestDiscoveryExecutorTest.java
+++ b/javatests/com/android/tradefed/observatory/TestDiscoveryExecutorTest.java
@@ -74,7 +74,7 @@
/** Test the executor to discover test modules from multiple tests. */
@Test
- public void testDiscoverTestModules() throws Exception {
+ public void testDiscoverTestDependencies() throws Exception {
// Mock to return some include filters
BaseTestSuite test1 = new BaseTestSuite();
Set<String> includeFilters1 = new HashSet<>();
@@ -107,66 +107,6 @@
try {
String output = mTestDiscoveryExecutor.discoverDependencies(new String[0]);
String expected =
- "{\"TestDependencies\":[\"TestModule1\",\"TestModule2\",\"TestModule3\","
- + "\"TestModule4\",\"TestModule5\",\"TestModule6\""
- + ",\"someapk.apk\"]}";
- assertEquals(expected, output);
- } catch (Exception e) {
- fail(String.format("Should not throw exception %s", e.getMessage()));
- }
- }
-
- /** Test the executor to handle where there is no tests from the config. */
- @Test
- public void testDiscoverNoTestModules() throws Exception {
- // Mock to return no include filters
- when(mMockedConfiguration.getTests()).thenReturn(new ArrayList<>());
- // We don't test with real command line input here. Because for a real command line input,
- // the test module names will be different with respect to those test config resource files
- // can be changed in different builds.
- try {
- mTestDiscoveryExecutor.discoverDependencies(new String[0]);
- fail("Should throw an IllegalStateException");
- } catch (Exception e) {
- assertTrue(e instanceof IllegalStateException);
- }
- }
-
- /** Test the executor to discover test modules from multiple tests. */
- @Test
- public void testDiscoverTestDependenciesV2() throws Exception {
- // Mock to return some include filters
- BaseTestSuite test1 = new BaseTestSuite();
- Set<String> includeFilters1 = new HashSet<>();
- includeFilters1.add("TestModule1 class#function1");
- includeFilters1.add("TestModule2");
- includeFilters1.add("x86_64 TestModule3 class#function3");
- test1.setIncludeFilter(includeFilters1);
-
- BaseTestSuite test2 = new BaseTestSuite();
- Set<String> includeFilters2 = new HashSet<>();
- includeFilters2.add("TestModule1 class#function6");
- includeFilters2.add("x86 TestModule4");
- includeFilters2.add("TestModule5 class#function2");
- includeFilters2.add("TestModule6");
- test2.setIncludeFilter(includeFilters2);
-
- List<IRemoteTest> testList = new ArrayList<>();
- testList.add(test1);
- testList.add(test2);
- when(mMockedConfiguration.getTests()).thenReturn(testList);
- List<Object> preparers = new ArrayList<>();
- preparers.add(new DiscoverablePreparer());
- when(mMockedConfiguration.getAllConfigurationObjectsOfType(
- Configuration.TARGET_PREPARER_TYPE_NAME))
- .thenReturn(preparers);
-
- // We don't test with real command line input here. Because for a real command line input,
- // the test module names will be different with respect to those test config resource files
- // can be changed in different builds.
- try {
- String output = mTestDiscoveryExecutor.discoverDependenciesV2(new String[0]);
- String expected =
"{\"TestModules\":[\"TestModule1\",\"TestModule2\",\"TestModule3\","
+ "\"TestModule4\",\"TestModule5\",\"TestModule6\"],"
+ "\"TestDependencies\":[\"someapk.apk\"]}";
@@ -178,12 +118,12 @@
/** Test the executor to handle where there is no tests from the config. */
@Test
- public void testDiscoverDependenciesV2_NoTestModules() throws Exception {
+ public void testDiscoverDependencies_NoTestModules() throws Exception {
// Mock to return no include filters
when(mMockedConfiguration.getTests()).thenReturn(new ArrayList<>());
try {
- mTestDiscoveryExecutor.discoverDependenciesV2(new String[0]);
+ mTestDiscoveryExecutor.discoverDependencies(new String[0]);
fail("Should throw an TestDiscoveryException");
} catch (Exception e) {
assertTrue(e instanceof TestDiscoveryException);
diff --git a/javatests/com/android/tradefed/observatory/TestDiscoveryInvokerTest.java b/javatests/com/android/tradefed/observatory/TestDiscoveryInvokerTest.java
index 53bf0c2..6d3fc77 100644
--- a/javatests/com/android/tradefed/observatory/TestDiscoveryInvokerTest.java
+++ b/javatests/com/android/tradefed/observatory/TestDiscoveryInvokerTest.java
@@ -56,7 +56,8 @@
private IConfiguration mConfiguration;
private ICommandOptions mCommandOptions;
private TestDiscoveryInvoker mTestDiscoveryInvoker;
- private static final String CONFIG_NAME = "test_config_name";
+ private static final String DEFAULT_TEST_CONFIG_NAME = "default_config_name";
+ private static final String TEST_CONFIG_NAME = "test_config_name";
private static final String TEST_MODULE_1_NAME = "test_module_1";
private static final String TEST_MODULE_2_NAME = "test_module_2";
@@ -69,13 +70,6 @@
when(mCommandOptions.getInvocationData()).thenReturn(new UniqueMultiMap<String, String>());
mRootDir = FileUtil.createTempDir("test_suite_root");
File mainDir = FileUtil.createNamedTempDir(mRootDir, "android-xts");
- mTestDiscoveryInvoker =
- new TestDiscoveryInvoker(mConfiguration, mRootDir) {
- @Override
- IRunUtil getRunUtil() {
- return mRunUtil;
- }
- };
File toolsDir = FileUtil.createNamedTempDir(mainDir, "tools");
mTradefedJar = FileUtil.createNamedTempDir(toolsDir, "tradefed.jar");
mCompatibilityJar = FileUtil.createNamedTempDir(toolsDir, "compatibility_mock.jar");
@@ -88,98 +82,14 @@
/** Test the invocation when all necessary information are in the command line. */
@Test
- public void testSuccessTestDiscoveryInvocation() throws Exception {
- String successStdout =
- "{\"TestDependencies\":[" + TEST_MODULE_1_NAME + "," + TEST_MODULE_2_NAME + "]}";
- String commandLine =
- String.format(
- "random/test/name --cts-package-name android-cts.zip --cts-params"
- + " --include-test-log-tags --cts-params --log-level --cts-params"
- + " VERBOSE --cts-params --logcat-on-failure --config-name %s"
- + " --cts-params --test-tag-suffix --cts-params x86 --cts-params"
- + " --compatibility:test-arg --cts-params"
- + " com.android.tradefed.testtype.HostTest:include-annotation:android.platform.test.annotations.Presubmit"
- + " --cts-params --compatibility:include-filter --cts-params %s"
- + " --cts-params --compatibility:include-filter --cts-params %s"
- + " --test-tag --cts-params camera-presubmit --test-tag"
- + " camera-presubmit --post-method=TEST_ARTIFACT",
- CONFIG_NAME, TEST_MODULE_1_NAME, TEST_MODULE_2_NAME);
- when(mConfiguration.getCommandLine()).thenReturn(commandLine);
- Mockito.doAnswer(
- new Answer<Object>() {
- @Override
- public Object answer(InvocationOnMock mock) throws Throwable {
- Set<String> args = new HashSet<>();
- for (int i = 1; i < mock.getArguments().length; i++) {
- args.add(mock.getArgument(i));
- }
-
- // Those are the necessary args that we care about
- assertTrue(
- args.contains(
- mCompatibilityJar.getAbsolutePath()
- + ":"
- + mTradefedJar.getAbsolutePath()));
- assertTrue(
- args.contains(
- TestDiscoveryInvoker
- .TRADEFED_OBSERVATORY_ENTRY_PATH));
- assertTrue(args.contains(CONFIG_NAME));
- assertTrue(args.contains("--compatibility:include-filter"));
- assertTrue(args.contains(TEST_MODULE_1_NAME));
- assertTrue(args.contains(TEST_MODULE_2_NAME));
-
- // Both cts params and config name should already been filtered out
- // and applied
- assertFalse(args.contains("--cts-params"));
- assertFalse(args.contains("--config-name"));
- CommandResult res = new CommandResult();
- res.setExitCode(0);
- res.setStatus(CommandStatus.SUCCESS);
- res.setStdout(successStdout);
- return res;
- }
- })
- .when(mRunUtil)
- .runTimedCmd(Mockito.anyLong(), Mockito.any());
- List<String> testModules = mTestDiscoveryInvoker.discoverTestModuleNames();
- assertEquals(testModules.size(), 2);
- assertTrue(testModules.contains(TEST_MODULE_1_NAME));
- assertTrue(testModules.contains(TEST_MODULE_2_NAME));
- }
-
- /**
- * Test the invocation when the command line does not have all necessary information for the
- * subprocess.
- */
- @Test
- public void testFailTestDiscoveryInvocation() throws Exception {
- // --config-name is missing from the cmd
- String commandLine =
- String.format(
- "random/test/name --cts-package-name android-cts.zip --cts-params"
- + " --include-test-log-tags --cts-params --log-level --cts-params"
- + " VERBOSE --cts-params --logcat-on-failure --cts-params"
- + " --test-tag-suffix --cts-params x86 --cts-params"
- + " --compatibility:test-arg --cts-params"
- + " com.android.tradefed.testtype.HostTest:include-annotation:android.platform.test.annotations.Presubmit"
- + " --cts-params --compatibility:include-filter --cts-params %s"
- + " --cts-params --compatibility:include-filter --cts-params %s"
- + " --test-tag --cts-params camera-presubmit --test-tag"
- + " camera-presubmit --post-method=TEST_ARTIFACT",
- TEST_MODULE_1_NAME, TEST_MODULE_2_NAME);
- when(mConfiguration.getCommandLine()).thenReturn(commandLine);
- try {
- mTestDiscoveryInvoker.discoverTestModuleNames();
- fail("Should throw a ConfigurationException");
- } catch (ConfigurationException expected) {
- // Expected
- }
- }
-
- /** Test the invocation when all necessary information are in the command line. */
- @Test
public void testSuccessTestDependencyDiscovery() throws Exception {
+ mTestDiscoveryInvoker =
+ new TestDiscoveryInvoker(mConfiguration, mRootDir) {
+ @Override
+ IRunUtil getRunUtil() {
+ return mRunUtil;
+ }
+ };
String successStdout =
"{\"TestModules\":[" + TEST_MODULE_1_NAME + "," + TEST_MODULE_2_NAME + "]}";
String commandLine =
@@ -194,7 +104,7 @@
+ " --cts-params --compatibility:include-filter --cts-params %s"
+ " --test-tag --cts-params camera-presubmit --test-tag"
+ " camera-presubmit --post-method=TEST_ARTIFACT",
- CONFIG_NAME, TEST_MODULE_1_NAME, TEST_MODULE_2_NAME);
+ TEST_CONFIG_NAME, TEST_MODULE_1_NAME, TEST_MODULE_2_NAME);
when(mConfiguration.getCommandLine()).thenReturn(commandLine);
Mockito.doAnswer(
new Answer<Object>() {
@@ -215,7 +125,7 @@
args.contains(
TestDiscoveryInvoker
.TRADEFED_OBSERVATORY_ENTRY_PATH));
- assertTrue(args.contains(CONFIG_NAME));
+ assertTrue(args.contains(TEST_CONFIG_NAME));
assertTrue(args.contains("--compatibility:include-filter"));
assertTrue(args.contains(TEST_MODULE_1_NAME));
assertTrue(args.contains(TEST_MODULE_2_NAME));
@@ -252,7 +162,14 @@
*/
@Test
public void testFailTestDependencyDiscovery() throws Exception {
- // --config-name is missing from the cmd
+ mTestDiscoveryInvoker =
+ new TestDiscoveryInvoker(mConfiguration, mRootDir) {
+ @Override
+ IRunUtil getRunUtil() {
+ return mRunUtil;
+ }
+ };
+ // --config-name is missing from the cmd, and no default is provided
String commandLine =
String.format(
"random/test/name --cts-package-name android-cts.zip --cts-params"
@@ -268,10 +185,89 @@
TEST_MODULE_1_NAME, TEST_MODULE_2_NAME);
when(mConfiguration.getCommandLine()).thenReturn(commandLine);
try {
- mTestDiscoveryInvoker.discoverTestModuleNames();
+ mTestDiscoveryInvoker.discoverTestDependencies();
fail("Should throw a ConfigurationException");
} catch (ConfigurationException expected) {
// Expected
}
}
+
+ /**
+ * Test the invocation when command line args does not contain a config name but default config
+ * name is provided.
+ */
+ @Test
+ public void testTestDependencyDiscovery_NoConfigNameInArgs() throws Exception {
+ mTestDiscoveryInvoker =
+ new TestDiscoveryInvoker(mConfiguration, DEFAULT_TEST_CONFIG_NAME, mRootDir) {
+ @Override
+ IRunUtil getRunUtil() {
+ return mRunUtil;
+ }
+ };
+ String successStdout =
+ "{\"TestModules\":[" + TEST_MODULE_1_NAME + "," + TEST_MODULE_2_NAME + "]}";
+ String commandLine =
+ String.format(
+ "random/test/name --cts-package-name android-cts.zip --cts-params"
+ + " --include-test-log-tags --cts-params --log-level --cts-params"
+ + " VERBOSE --cts-params --logcat-on-failure --cts-params"
+ + " --test-tag-suffix --cts-params x86 --cts-params"
+ + " --compatibility:test-arg --cts-params"
+ + " com.android.tradefed.testtype.HostTest:include-annotation:android.platform.test.annotations.Presubmit"
+ + " --cts-params --compatibility:include-filter --cts-params %s"
+ + " --cts-params --compatibility:include-filter --cts-params %s"
+ + " --test-tag --cts-params camera-presubmit --test-tag"
+ + " camera-presubmit --post-method=TEST_ARTIFACT",
+ TEST_MODULE_1_NAME, TEST_MODULE_2_NAME);
+ when(mConfiguration.getCommandLine()).thenReturn(commandLine);
+ Mockito.doAnswer(
+ new Answer<Object>() {
+ @Override
+ public Object answer(InvocationOnMock mock) throws Throwable {
+ Set<String> args = new HashSet<>();
+ for (int i = 1; i < mock.getArguments().length; i++) {
+ args.add(mock.getArgument(i));
+ }
+
+ // Those are the necessary args that we care about
+ assertTrue(
+ args.contains(
+ mCompatibilityJar.getAbsolutePath()
+ + ":"
+ + mTradefedJar.getAbsolutePath()));
+ assertTrue(
+ args.contains(
+ TestDiscoveryInvoker
+ .TRADEFED_OBSERVATORY_ENTRY_PATH));
+ assertTrue(args.contains(DEFAULT_TEST_CONFIG_NAME));
+ assertTrue(args.contains("--compatibility:include-filter"));
+ assertTrue(args.contains(TEST_MODULE_1_NAME));
+ assertTrue(args.contains(TEST_MODULE_2_NAME));
+
+ // Both cts params and config name should already been filtered out
+ // and applied
+ assertFalse(args.contains("--cts-params"));
+ assertFalse(args.contains("--config-name"));
+ CommandResult res = new CommandResult();
+ res.setExitCode(0);
+ res.setStatus(CommandStatus.SUCCESS);
+ res.setStdout(successStdout);
+ return res;
+ }
+ })
+ .when(mRunUtil)
+ .runTimedCmd(Mockito.anyLong(), Mockito.any());
+ Map<String, List<String>> testDependencies =
+ mTestDiscoveryInvoker.discoverTestDependencies();
+ assertEquals(testDependencies.size(), 1);
+ assertTrue(
+ testDependencies
+ .get(TestDiscoveryInvoker.TEST_MODULES_LIST_KEY)
+ .contains(TEST_MODULE_1_NAME));
+ assertTrue(
+ testDependencies
+ .get(TestDiscoveryInvoker.TEST_MODULES_LIST_KEY)
+ .contains(TEST_MODULE_2_NAME));
+ }
}
diff --git a/javatests/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java b/javatests/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
index 3e0ef3d..805d5da 100644
--- a/javatests/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
+++ b/javatests/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
@@ -331,6 +331,32 @@
15120269);
}
+ /** Test metrics enabled with multiple key and string value prefixing. */
+ @Test
+ public void testParsingWithMultipleKeyAndStringValuePrefixing()
+ throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(KEY_PREFIX_OPTION,
+ "perfetto.protos.ProcessRenderInfo.process_name");
+ mOptionSetter.setOptionValue(KEY_PREFIX_OPTION,
+ "perfetto.protos.ProcessRenderInfo.rt_cpu_time_ms");
+ 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_hwui_metric-process_info-process_name-com.android.systemui-"
+ + "rt_cpu_time_ms-2481-all_mem_min",
+ 15120269);
+ }
+
/** Test metrics enabled with key and integer value prefixing. */
@Test
public void testParsingWithKeyAndIntegerValuePrefixing()
diff --git a/javatests/com/android/tradefed/result/proto/StreamProtoResultReporterTest.java b/javatests/com/android/tradefed/result/proto/StreamProtoResultReporterTest.java
index 414f2a1..e4bdf6f 100644
--- a/javatests/com/android/tradefed/result/proto/StreamProtoResultReporterTest.java
+++ b/javatests/com/android/tradefed/result/proto/StreamProtoResultReporterTest.java
@@ -118,6 +118,7 @@
} finally {
receiver.joinReceiver(5000);
receiver.close();
+ mReporter.closeSocket();
}
InOrder inOrder = Mockito.inOrder(mMockListener);
inOrder.verify(mMockListener).invocationStarted(Mockito.any());
@@ -178,6 +179,7 @@
mReporter.invocationEnded(500L);
} finally {
receiver.close();
+ mReporter.closeSocket();
}
assertNull(receiver.getError());
@@ -227,6 +229,7 @@
} finally {
receiver.joinReceiver(5000);
receiver.close();
+ mReporter.closeSocket();
}
verify(mMockListener).testModuleStarted(Mockito.any());
diff --git a/javatests/com/android/tradefed/service/management/DeviceManagementGrpcServerTest.java b/javatests/com/android/tradefed/service/management/DeviceManagementGrpcServerTest.java
index 3dff8f4..3cfc682 100644
--- a/javatests/com/android/tradefed/service/management/DeviceManagementGrpcServerTest.java
+++ b/javatests/com/android/tradefed/service/management/DeviceManagementGrpcServerTest.java
@@ -22,6 +22,7 @@
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
+import com.android.tradefed.command.ICommandScheduler;
import com.android.tradefed.command.remote.DeviceDescriptor;
import com.android.tradefed.device.DeviceAllocationState;
import com.android.tradefed.device.FreeDeviceState;
@@ -62,6 +63,7 @@
private DeviceManagementGrpcServer mServer;
@Mock private IDeviceManager mMockDeviceManager;
+ @Mock private ICommandScheduler mMockCommandScheduler;
@Mock private StreamObserver<GetDevicesStatusResponse> mGetDevicesStatusObserver;
@Mock private StreamObserver<ReserveDeviceResponse> mReserveDeviceResponseObserver;
@Mock private StreamObserver<ReleaseReservationResponse> mReleaseReservationResponseObserver;
@@ -72,7 +74,7 @@
@Before
public void setUp() {
Server server = null;
- mServer = new DeviceManagementGrpcServer(server, mMockDeviceManager);
+ mServer = new DeviceManagementGrpcServer(server, mMockDeviceManager, mMockCommandScheduler);
}
@Test
diff --git a/javatests/com/android/tradefed/service/management/TestInvocationManagementServerTest.java b/javatests/com/android/tradefed/service/management/TestInvocationManagementServerTest.java
index 20b519c..373861f 100644
--- a/javatests/com/android/tradefed/service/management/TestInvocationManagementServerTest.java
+++ b/javatests/com/android/tradefed/service/management/TestInvocationManagementServerTest.java
@@ -34,6 +34,8 @@
import com.proto.tradefed.invocation.InvocationStatus;
import com.proto.tradefed.invocation.NewTestCommandRequest;
import com.proto.tradefed.invocation.NewTestCommandResponse;
+import com.proto.tradefed.invocation.StopInvocationRequest;
+import com.proto.tradefed.invocation.StopInvocationResponse;
import org.junit.Before;
import org.junit.Rule;
@@ -63,8 +65,10 @@
@Mock private DeviceManagementGrpcServer mMockDeviceManagement;
@Mock private StreamObserver<NewTestCommandResponse> mRequestObserver;
@Mock private StreamObserver<InvocationDetailResponse> mDetailObserver;
+ @Mock private StreamObserver<StopInvocationResponse> mStopInvocationObserver;
@Captor ArgumentCaptor<NewTestCommandResponse> mResponseCaptor;
@Captor ArgumentCaptor<InvocationDetailResponse> mResponseDetailCaptor;
+ @Captor ArgumentCaptor<StopInvocationResponse> mStopInvocationCaptor;
@Before
public void setUp() {
@@ -161,4 +165,47 @@
assertThat(record.exists()).isTrue();
FileUtil.deleteFile(record);
}
+
+ @Test
+ public void testSubmitTestCommand_andStop() throws Exception {
+ doAnswer(
+ invocation -> {
+ Object listeners = invocation.getArgument(0);
+ ((IScheduledInvocationListener) listeners)
+ .invocationComplete(null, null);
+ return 1L;
+ })
+ .when(mMockScheduler)
+ .execCommand(Mockito.any(), Mockito.any());
+ when(mMockScheduler.stopInvocation(Mockito.anyInt(), Mockito.any())).thenReturn(true);
+ NewTestCommandRequest.Builder requestBuilder =
+ NewTestCommandRequest.newBuilder().addArgs("empty");
+ mServer.submitTestCommand(requestBuilder.build(), mRequestObserver);
+
+ verify(mRequestObserver).onNext(mResponseCaptor.capture());
+ NewTestCommandResponse response = mResponseCaptor.getValue();
+ String invocationId = response.getInvocationId();
+ assertThat(invocationId).isNotEmpty();
+
+ StopInvocationRequest.Builder stopRequest =
+ StopInvocationRequest.newBuilder()
+ .setInvocationId(invocationId)
+ .setReason("stopping you");
+ mServer.stopInvocation(stopRequest.build(), mStopInvocationObserver);
+ verify(mStopInvocationObserver).onNext(mStopInvocationCaptor.capture());
+ StopInvocationResponse stopResponse = mStopInvocationCaptor.getValue();
+ assertThat(stopResponse.getStatus()).isEqualTo(StopInvocationResponse.Status.SUCCESS);
+
+ // Query the file to delete it
+ InvocationDetailRequest.Builder detailBuilder =
+ InvocationDetailRequest.newBuilder().setInvocationId(response.getInvocationId());
+ mServer.getInvocationDetail(detailBuilder.build(), mDetailObserver);
+ verify(mDetailObserver).onNext(mResponseDetailCaptor.capture());
+ InvocationDetailResponse responseDetails = mResponseDetailCaptor.getValue();
+ assertThat(responseDetails.getInvocationStatus().getStatus())
+ .isEqualTo(InvocationStatus.Status.DONE);
+ File record = new File(responseDetails.getTestRecordPath());
+ assertThat(record.exists()).isTrue();
+ FileUtil.deleteFile(record);
+ }
}
diff --git a/javatests/com/android/tradefed/suite/checker/baseline/DeviceBaselineSetterTest.java b/javatests/com/android/tradefed/suite/checker/baseline/DeviceBaselineSetterTest.java
new file mode 100644
index 0000000..5e5ada3
--- /dev/null
+++ b/javatests/com/android/tradefed/suite/checker/baseline/DeviceBaselineSetterTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.suite.checker.baseline;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import com.android.tradefed.device.ITestDevice;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link DeviceBaselineSetter}. */
+@RunWith(JUnit4.class)
+public final class DeviceBaselineSetterTest {
+ private static final String SETTING_NAME = "test";
+
+ private static class Setter extends DeviceBaselineSetter {
+ public Setter(JSONObject object, String name) throws JSONException {
+ super(object, name);
+ }
+
+ @Override
+ public boolean setBaseline(ITestDevice mDevice) {
+ return true;
+ }
+ }
+
+ /** Test that the experimental flag is set to false when the input filed is null or false. */
+ @Test
+ public void isExperimental_withoutTrueExperimentalField_returnFalse() throws Exception {
+ assertFalse(new Setter(new JSONObject("{}"), SETTING_NAME).isExperimental());
+ assertFalse(
+ new Setter(new JSONObject("{\"experimental\": false}"), SETTING_NAME)
+ .isExperimental());
+ }
+
+ /** Test that the experimental flag is set to true when the input filed is true. */
+ @Test
+ public void isExperimental_withTrueExperimentalField_returnTrue() throws Exception {
+ assertTrue(
+ new Setter(new JSONObject("{\"experimental\": true}"), SETTING_NAME)
+ .isExperimental());
+ }
+}
diff --git a/javatests/com/android/tradefed/suite/checker/baseline/LockSettingsBaselineSetterTest.java b/javatests/com/android/tradefed/suite/checker/baseline/LockSettingsBaselineSetterTest.java
new file mode 100644
index 0000000..18e2908
--- /dev/null
+++ b/javatests/com/android/tradefed/suite/checker/baseline/LockSettingsBaselineSetterTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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.suite.checker.baseline;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.android.tradefed.device.ITestDevice;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link LockSettingsBaselineSetter}. */
+@RunWith(JUnit4.class)
+public final class LockSettingsBaselineSetterTest {
+
+ private ITestDevice mMockDevice;
+ private LockSettingsBaselineSetter mSetter;
+ private JSONObject mJsonObject;
+ private static final String SETTING_NAME = "test";
+ private static final String SETTING_STRING = "{\"clear_pwds\": [\"0000\", \"1234\"]}";
+ private static final String GET_LOCK_SCREEN_COMMAND = "locksettings get-disabled";
+ private static final String LOCK_SCREEN_OFF_COMMAND = "locksettings set-disabled true";
+ private static final String CLEAR_PWD_COMMAND = "locksettings clear --old %s";
+
+ @Before
+ public void setup() throws Exception {
+ mMockDevice = mock(ITestDevice.class);
+ mJsonObject = new JSONObject(SETTING_STRING);
+ mSetter = new LockSettingsBaselineSetter(mJsonObject, SETTING_NAME);
+ }
+
+ @Test
+ public void lockSettingsDeviceBaselineSetter_noPasswordField_throwsException()
+ throws Exception {
+ mJsonObject.remove("clear_pwds");
+ assertThrows(
+ JSONException.class,
+ () -> new LockSettingsBaselineSetter(mJsonObject, SETTING_NAME));
+ }
+
+ /** Test that the setter skips removing passwords when lock-screen is turned off. */
+ @Test
+ public void setBaseline_lockScreenOff_skipRemovingPasswords() throws Exception {
+ when(mMockDevice.executeShellCommand(GET_LOCK_SCREEN_COMMAND)).thenReturn("true");
+ assertTrue(mSetter.setBaseline(mMockDevice));
+ verify(mMockDevice).executeShellCommand(GET_LOCK_SCREEN_COMMAND);
+ verify(mMockDevice, never()).executeShellCommand(LOCK_SCREEN_OFF_COMMAND);
+ verify(mMockDevice, never()).executeShellCommand(String.format(CLEAR_PWD_COMMAND, "0000"));
+ verify(mMockDevice, never()).executeShellCommand(String.format(CLEAR_PWD_COMMAND, "1234"));
+ }
+
+ /** Test that the setter removes passwords successfully. */
+ @Test
+ public void setBaseline_setSucceeds_passwordsRemoved() throws Exception {
+ when(mMockDevice.executeShellCommand(GET_LOCK_SCREEN_COMMAND)).thenReturn("false", "true");
+ assertTrue(mSetter.setBaseline(mMockDevice));
+ verify(mMockDevice, times(2)).executeShellCommand(GET_LOCK_SCREEN_COMMAND);
+ verify(mMockDevice).executeShellCommand(LOCK_SCREEN_OFF_COMMAND);
+ verify(mMockDevice).executeShellCommand(String.format(CLEAR_PWD_COMMAND, "0000"));
+ verify(mMockDevice).executeShellCommand(String.format(CLEAR_PWD_COMMAND, "1234"));
+ }
+
+ /** Test that the setter returns false when the baseline is failed to set. */
+ @Test
+ public void setBaseline_setFails_returnFalse() throws Exception {
+ when(mMockDevice.executeShellCommand(GET_LOCK_SCREEN_COMMAND)).thenReturn("false");
+ assertFalse(mSetter.setBaseline(mMockDevice));
+ }
+}
diff --git a/javatests/com/android/tradefed/suite/checker/baseline/SettingsBaselineSetterTest.java b/javatests/com/android/tradefed/suite/checker/baseline/SettingsBaselineSetterTest.java
new file mode 100644
index 0000000..5a32610
--- /dev/null
+++ b/javatests/com/android/tradefed/suite/checker/baseline/SettingsBaselineSetterTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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.suite.checker.baseline;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.android.tradefed.device.ITestDevice;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Unit tests for {@link SettingsBaselineSetter}. */
+@RunWith(JUnit4.class)
+public final class SettingsBaselineSetterTest {
+
+ private ITestDevice mMockDevice;
+ private SettingsBaselineSetter mSetter;
+ private JSONObject mJsonObject;
+ private static final String SETTING_NAME = "test";
+ private static final String SETTING_STRING =
+ "{\"namespace\": \"global\", \"key\": "
+ + "\"stay_on_while_plugged_in\", \"value\": \"7\"}";
+
+ @Before
+ public void setup() throws Exception {
+ mMockDevice = mock(ITestDevice.class);
+ mJsonObject = new JSONObject(SETTING_STRING);
+ mSetter = new SettingsBaselineSetter(mJsonObject, SETTING_NAME);
+ }
+
+ @Test
+ public void settingsDeviceBaselineSetter_noNamespaceField_throwsException() throws Exception {
+ mJsonObject.remove("namespace");
+ assertThrows(
+ JSONException.class, () -> new SettingsBaselineSetter(mJsonObject, SETTING_NAME));
+ }
+
+ @Test
+ public void settingsDeviceBaselineSetter_noKeyField_throwsException() throws Exception {
+ mJsonObject.remove("key");
+ assertThrows(
+ JSONException.class, () -> new SettingsBaselineSetter(mJsonObject, SETTING_NAME));
+ }
+
+ @Test
+ public void settingsDeviceBaselineSetter_noValueField_throwsException() throws Exception {
+ mJsonObject.remove("value");
+ assertThrows(
+ JSONException.class, () -> new SettingsBaselineSetter(mJsonObject, SETTING_NAME));
+ }
+
+ /** Test that the setter returns false when the setting key doesn't exist. */
+ @Test
+ public void setBaseline_invalidKey_returnFalse() throws Exception {
+ when(mMockDevice.getSetting("global", "stay_on_while_plugged_in")).thenReturn(null);
+ assertFalse(mSetter.setBaseline(mMockDevice));
+ }
+
+ /** Test that the setter returns false when the baseline is failed to set. */
+ @Test
+ public void setBaseline_setFails_returnFalse() throws Exception {
+ when(mMockDevice.getSetting("global", "stay_on_while_plugged_in")).thenReturn("0");
+ assertFalse(mSetter.setBaseline(mMockDevice));
+ }
+
+ /** Test that the setter returns true when the baseline is set successfully. */
+ @Test
+ public void setBaseline_setSucceeds_returnTrue() throws Exception {
+ when(mMockDevice.getSetting("global", "stay_on_while_plugged_in")).thenReturn("7");
+ assertTrue(mSetter.setBaseline(mMockDevice));
+ }
+}
diff --git a/javatests/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java b/javatests/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java
index e0f0148..32a457c 100644
--- a/javatests/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java
+++ b/javatests/com/android/tradefed/targetprep/DynamicSystemPreparerTest.java
@@ -64,6 +64,7 @@
private File mSystemImageDir;
private File mSystemImageZip;
private File mMultiSystemImageZip;
+ private boolean mShouldTimeOut;
// The object under test.
private DynamicSystemPreparer mPreparer;
@@ -85,7 +86,14 @@
"DynamicSystem_multi");
mBuildInfo = new BuildInfo();
- mPreparer = new DynamicSystemPreparer();
+ mShouldTimeOut = false;
+ mPreparer =
+ new DynamicSystemPreparer() {
+ @Override
+ boolean hasTimedOut(long deadline) {
+ return mShouldTimeOut;
+ }
+ };
IInvocationContext context = new InvocationContext();
context.addAllocatedDevice("device", mMockDevice);
@@ -107,7 +115,7 @@
private File createImageDir(String... fileNames) throws IOException {
File tempDir = FileUtil.createTempDir("createImageDir");
for (String fileName : fileNames) {
- new File(tempDir, fileName).createNewFile();
+ FileUtil.writeToFile("test", new File(tempDir, fileName));
}
return tempDir;
}
@@ -168,6 +176,18 @@
}
@Test
+ public void testSetUp_imageConversionTimeout() throws BuildError, DeviceNotAvailableException {
+ mBuildInfo.setFile(SYSTEM_IMAGE_ZIP_NAME, mSystemImageZip, "0");
+ mShouldTimeOut = true;
+ try {
+ mPreparer.setUp(mTestInfo);
+ Assert.fail("setUp() should have thrown.");
+ } catch (TargetSetupError e) {
+ Assert.assertEquals("Fail to create image archive.", e.getMessage());
+ }
+ }
+
+ @Test
public void testSetUp_installationFail() throws BuildError, DeviceNotAvailableException {
mBuildInfo.setFile(SYSTEM_IMAGE_ZIP_NAME, mSystemImageZip, "0");
Mockito.when(
diff --git a/javatests/com/android/tradefed/targetprep/GkiDeviceFlashPreparerTest.java b/javatests/com/android/tradefed/targetprep/GkiDeviceFlashPreparerTest.java
index ed9e50c..d5f18b4 100644
--- a/javatests/com/android/tradefed/targetprep/GkiDeviceFlashPreparerTest.java
+++ b/javatests/com/android/tradefed/targetprep/GkiDeviceFlashPreparerTest.java
@@ -285,6 +285,58 @@
verify(mMockDevice).postBootSetup();
}
+ /* Verifies that preparer can flash GKI boot image and vendor_boot, vendor_dlkm, dtbo images */
+ @Test
+ public void testSetup_vendor_img_Success() throws Exception {
+ File imgDir = FileUtil.createTempDir("img_folder", mTmpDir);
+ File bootImg = new File(imgDir, "boot-5.4.img");
+ File vendorBootImg = new File(imgDir, "vendor_boot.img");
+ File dtboImg = new File(imgDir, "dtbo.img");
+ File vendorDlkmImg = new File(imgDir, "vendor_dlkm.img");
+ FileUtil.writeToFile("ddd", bootImg);
+ FileUtil.writeToFile("123", vendorBootImg);
+ FileUtil.writeToFile("456", dtboImg);
+ FileUtil.writeToFile("789", vendorDlkmImg);
+ mBuildInfo.setFile("gki_boot.img", bootImg, "0");
+ mBuildInfo.setFile("vendor_boot.img", vendorBootImg, "0");
+ mBuildInfo.setFile("vendor_dlkm.img", vendorDlkmImg, "0");
+ mBuildInfo.setFile("dtbo.img", dtboImg, "0");
+
+ when(mMockDevice.executeLongFastbootCommand(
+ "flash", "boot", mBuildInfo.getFile("gki_boot.img").getAbsolutePath()))
+ .thenReturn(mSuccessResult);
+ when(mMockDevice.executeLongFastbootCommand(
+ "flash",
+ "vendor_boot",
+ mBuildInfo.getFile("vendor_boot.img").getAbsolutePath()))
+ .thenReturn(mSuccessResult);
+ when(mMockDevice.executeLongFastbootCommand(
+ "flash",
+ "vendor_dlkm",
+ mBuildInfo.getFile("vendor_dlkm.img").getAbsolutePath()))
+ .thenReturn(mSuccessResult);
+ when(mMockDevice.executeLongFastbootCommand(
+ "flash", "dtbo", mBuildInfo.getFile("dtbo.img").getAbsolutePath()))
+ .thenReturn(mSuccessResult);
+ when(mMockDevice.executeLongFastbootCommand("-w")).thenReturn(mSuccessResult);
+
+ when(mMockDevice.enableAdbRoot()).thenReturn(Boolean.TRUE);
+
+ mPreparer.setUp(mTestInfo);
+ mPreparer.tearDown(mTestInfo, null);
+
+ verify(mMockDevice).rebootIntoBootloader();
+ verify(mMockRunUtil).allowInterrupt(false);
+ verify(mMockRunUtil).allowInterrupt(true);
+ verify(mMockDevice).rebootIntoFastbootd();
+ verify(mMockRunUtil).sleep(anyLong());
+ verify(mMockDevice).rebootUntilOnline();
+ verify(mMockDevice).setDate(null);
+ verify(mMockDevice).waitForDeviceAvailable(anyLong());
+ verify(mMockDevice).setRecoveryMode(RecoveryMode.AVAILABLE);
+ verify(mMockDevice).postBootSetup();
+ }
+
/* Verifies that preparer can flash GKI boot image from a Zip file*/
@Test
public void testSetup_Success_FromZip() throws Exception {
diff --git a/javatests/com/android/tradefed/testtype/HostTestTest.java b/javatests/com/android/tradefed/testtype/HostTestTest.java
index ed75949..9afdf32 100644
--- a/javatests/com/android/tradefed/testtype/HostTestTest.java
+++ b/javatests/com/android/tradefed/testtype/HostTestTest.java
@@ -573,7 +573,7 @@
assertTrue(mHelloWorld != null && mFoobar != null);
assertTrue(
"Expects 'hello' value to be 'hello:world'", mHelloWorld.equals("hello:world"));
- assertTrue("Expects 'foobar' value to be 'baz:qux'", mFoobar.equals("baz:qux"));
+ assertTrue("Expects 'foobar' value to be 'baz:qux=wap'", mFoobar.equals("baz:qux=wap"));
metrics.addTestMetric("hello", mHelloWorld);
metrics.addTestMetric("foobar", mFoobar);
@@ -2480,7 +2480,8 @@
+ ":gcs-bucket-file:gs\\://bucket/path/file");
setter.setOptionValue("set-option", "hello:hello\\:world");
setter.setOptionValue(
- "set-option", OptionEscapeColonTestCase.class.getName() + ":foobar:baz\\:qux");
+ "set-option",
+ OptionEscapeColonTestCase.class.getName() + ":foobar:baz\\:qux\\=wap");
TestDescription testGcsBucket =
new TestDescription(OptionEscapeColonTestCase.class.getName(), "testGcsBucket");
TestDescription testEscapeStrings =
@@ -2491,9 +2492,12 @@
verify(mListener).testRunStarted((String) Mockito.any(), Mockito.eq(2));
verify(mListener).testStarted(Mockito.eq(testGcsBucket));
+ verify(mListener, times(0)).testFailed(Mockito.eq(testGcsBucket), (String) Mockito.any());
verify(mListener)
.testEnded(Mockito.eq(testGcsBucket), (HashMap<String, Metric>) Mockito.any());
verify(mListener).testStarted(Mockito.eq(testEscapeStrings));
+ verify(mListener, times(0))
+ .testFailed(Mockito.eq(testEscapeStrings), (String) Mockito.any());
verify(mListener)
.testEnded(Mockito.eq(testEscapeStrings), (HashMap<String, Metric>) Mockito.any());
verify(mListener).testRunEnded(Mockito.anyLong(), (HashMap<String, Metric>) Mockito.any());
diff --git a/javatests/com/android/tradefed/testtype/suite/AtestRunnerTest.java b/javatests/com/android/tradefed/testtype/suite/AtestRunnerTest.java
index d2ee71d..472bfad 100644
--- a/javatests/com/android/tradefed/testtype/suite/AtestRunnerTest.java
+++ b/javatests/com/android/tradefed/testtype/suite/AtestRunnerTest.java
@@ -17,12 +17,9 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import com.android.tradefed.build.DeviceBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.OptionSetter;
@@ -41,7 +38,9 @@
import com.android.tradefed.util.FileUtil;
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;
@@ -58,6 +57,8 @@
@RunWith(JUnit4.class)
public class AtestRunnerTest {
+ @Rule public TemporaryFolder mTempFolder = new TemporaryFolder();
+
private static final String TEST_CONFIG =
"<configuration description=\"Runs a stub tests part of some suite\">\n"
+ " <test class=\"com.android.tradefed.testtype.suite.SuiteModuleLoaderTest"
@@ -93,12 +94,12 @@
@Before
public void setUp() throws Exception {
mRunner = new AbiAtestRunner();
- mBuildInfo = spy(new DeviceBuildInfo());
+ mBuildInfo = mock(IDeviceBuildInfo.class);
mMockDevice = mock(ITestDevice.class);
mRunner.setBuild(mBuildInfo);
mRunner.setDevice(mMockDevice);
- doReturn(new File("some-dir")).when(mBuildInfo).getTestsDir();
+ when(mBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
CommandResult result = new CommandResult(CommandStatus.SUCCESS);
result.setStdout("Supported states: [\n" +
diff --git a/javatests/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java b/javatests/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java
index da6cbd7..7bf8092 100644
--- a/javatests/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java
+++ b/javatests/com/android/tradefed/testtype/suite/BaseTestSuiteTest.java
@@ -47,7 +47,9 @@
import com.google.common.truth.Truth;
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;
@@ -66,6 +68,8 @@
/** Unit tests for {@link BaseTestSuite}. */
@RunWith(JUnit4.class)
public class BaseTestSuiteTest {
+ @Rule public TemporaryFolder mTempFolder = new TemporaryFolder();
+
private BaseTestSuite mRunner;
private IDeviceBuildInfo mBuildInfo;
@Mock ITestDevice mMockDevice;
@@ -82,6 +86,8 @@
mRunner.setBuild(mBuildInfo);
mRunner.setDevice(mMockDevice);
+ mBuildInfo.setTestsDir(mTempFolder.newFolder(), "testsdir");
+
IInvocationContext context = new InvocationContext();
context.addAllocatedDevice(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockDevice);
context.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, mBuildInfo);
diff --git a/javatests/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/javatests/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 47a0eef..0e202af 100644
--- a/javatests/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/javatests/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -88,7 +88,9 @@
import com.android.tradefed.util.MultiMap;
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;
@@ -112,6 +114,8 @@
@RunWith(JUnit4.class)
public class ITestSuiteTest {
+ @Rule public TemporaryFolder mTempFolder = new TemporaryFolder();
+
private static final MultiMap<String, String> METADATA_INCLUDES = new MultiMap<>();
private static final MultiMap<String, String> METADATA_EXCLUDES = new MultiMap<>();
private static final String EMPTY_CONFIG = "empty";
@@ -1816,6 +1820,7 @@
@Test
public void testStageTestArtifacts() throws Exception {
String remoteFilePath = "gs://module1/tests.zip";
+ File testsDir = mTempFolder.newFolder();
DynamicRemoteFileResolver dynamicResolver =
new DynamicRemoteFileResolver() {
@Override
@@ -1825,7 +1830,7 @@
List<String> includeFilters,
List<String> excludeFilters)
throws BuildRetrievalError {
- assertEquals(new File("tests_dir"), destDir);
+ assertEquals(destDir, testsDir);
assertEquals(remoteFilePath, remoteFilePath);
assertArrayEquals(new String[] {"/test/"}, includeFilters.toArray());
assertArrayEquals(new String[] {"[.]config$"}, excludeFilters.toArray());
@@ -1835,7 +1840,7 @@
setter.setOptionValue("partial-download-via-feature", "false");
mTestSuite.setDynamicResolver(dynamicResolver);
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("tests_dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(testsDir);
when(mockBuildInfo.getRemoteFiles())
.thenReturn(new HashSet<File>(Arrays.asList(new File(remoteFilePath))));
mTestSuite.setBuild(mockBuildInfo);
diff --git a/javatests/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java b/javatests/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
index 0862114..1e567bd 100644
--- a/javatests/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
+++ b/javatests/com/android/tradefed/testtype/suite/TestMappingSuiteRunnerTest.java
@@ -54,7 +54,9 @@
import com.android.tradefed.util.testmapping.TestOption;
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;
@@ -81,11 +83,12 @@
@RunWith(JUnit4.class)
public class TestMappingSuiteRunnerTest {
+ @Rule public TemporaryFolder mTempFolder = new TemporaryFolder();
+
private static final String ABI_1 = "arm64-v8a";
private static final String ABI_2 = "armeabi-v7a";
private static final String DISABLED_PRESUBMIT_TESTS = "disabled-presubmit-tests";
private static final String EMPTY_CONFIG = "empty";
- private static final String NON_EXISTING_DIR = "non-existing-dir";
private static final String TEST_CONFIG_NAME = "test";
private static final String TEST_DATA_DIR = "testdata";
private static final String TEST_MAPPING = "TEST_MAPPING";
@@ -119,6 +122,7 @@
mRunner = new AbiTestMappingSuite();
mRunner.setBuild(mBuildInfo);
mRunner.setDevice(mMockDevice);
+ mRunner.setSkipjarLoading(false);
mOptionSetter = new OptionSetter(mRunner);
mOptionSetter.setOptionValue("suite-config-prefix", "suite");
@@ -126,6 +130,7 @@
mRunner2 = new FakeTestMappingSuiteRunner();
mRunner2.setBuild(mBuildInfo);
mRunner2.setDevice(mMockDevice);
+ mRunner2.setSkipjarLoading(false);
mMainlineRunner = new FakeMainlineTMSR();
mMainlineRunner.setBuild(mBuildInfo);
@@ -140,7 +145,7 @@
mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build();
when(mBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mBuildInfo.getTestsDir()).thenReturn(new File(NON_EXISTING_DIR));
+ when(mBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mMockDevice.getProperty(Mockito.any())).thenReturn(ABI_1);
when(mMockDevice.getProperty(Mockito.any())).thenReturn(ABI_2);
when(mMockDevice.getIDevice()).thenReturn(mock(IDevice.class));
@@ -236,7 +241,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
mRunner.setBuild(mockBuildInfo);
@@ -330,7 +335,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
mRunner.setBuild(mockBuildInfo);
@@ -395,7 +400,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
mRunner.setBuild(mockBuildInfo);
@@ -508,7 +513,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.HOST_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
mRunner.setBuild(mockBuildInfo);
@@ -551,7 +556,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
when(mockBuildInfo.getRemoteFiles()).thenReturn(null);
@@ -595,7 +600,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
mTestInfo
@@ -630,7 +635,7 @@
ZipUtil.createZip(srcDir, zipFile);
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
mRunner.setBuild(mockBuildInfo);
@@ -689,7 +694,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
mRunner.setBuild(mockBuildInfo);
@@ -738,7 +743,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
mRunner.setBuild(mockBuildInfo);
@@ -879,7 +884,6 @@
@Test
public void testLoadTests_moduleDifferentoptions() throws Exception {
File tempDir = null;
- File tempTestsDir = null;
try {
mOptionSetter.setOptionValue("test-mapping-test-group", "presubmit");
@@ -899,7 +903,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(tempTestsDir);
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
when(mockBuildInfo.getBuildBranch()).thenReturn("branch");
when(mockBuildInfo.getBuildFlavor()).thenReturn("flavor");
@@ -927,7 +931,6 @@
}
} finally {
FileUtil.recursiveDelete(tempDir);
- FileUtil.recursiveDelete(tempTestsDir);
}
}
@@ -1046,7 +1049,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
mRunner.setBuild(mockBuildInfo);
mRunner.setPrioritizeHostConfig(true);
@@ -1114,12 +1117,12 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
when(mockBuildInfo.getFile("extra-zip")).thenReturn(zipFile);
mRunner.setBuild(mockBuildInfo);
- LinkedHashMap<String, IConfiguration> configMap = mRunner.loadTests();
+ mRunner.loadTests();
fail("Should have thrown an exception.");
} catch (HarnessRuntimeException expected) {
// expected
@@ -1146,7 +1149,7 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
when(mockBuildInfo.getFile("extra-zip")).thenReturn(zipFile2);
mRunner.setBuild(mockBuildInfo);
@@ -1175,11 +1178,11 @@
IDeviceBuildInfo mockBuildInfo = mock(IDeviceBuildInfo.class);
when(mockBuildInfo.getFile(BuildInfoFileKey.TARGET_LINKED_DIR)).thenReturn(null);
- when(mockBuildInfo.getTestsDir()).thenReturn(new File("non-existing-dir"));
+ when(mockBuildInfo.getTestsDir()).thenReturn(mTempFolder.newFolder());
when(mockBuildInfo.getFile(TEST_MAPPINGS_ZIP)).thenReturn(zipFile);
when(mockBuildInfo.getFile("extra-zip")).thenReturn(null);
mRunner.setBuild(mockBuildInfo);
- LinkedHashMap<String, IConfiguration> configMap = mRunner.loadTests();
+ mRunner.loadTests();
fail("Should have thrown an exception.");
} catch (HarnessRuntimeException expected) {
// expected
diff --git a/javatests/com/android/tradefed/util/SubprocessTestResultsParserTest.java b/javatests/com/android/tradefed/util/SubprocessTestResultsParserTest.java
index ef65509..241712f 100644
--- a/javatests/com/android/tradefed/util/SubprocessTestResultsParserTest.java
+++ b/javatests/com/android/tradefed/util/SubprocessTestResultsParserTest.java
@@ -549,43 +549,6 @@
}
@Test
- public void testParse_logAssociation_zipped() throws Exception {
- ILogSaverListener mockRunListener = mock(ILogSaverListener.class);
-
- File logDir = FileUtil.createTempDir("log-assos-dir");
- File log = FileUtil.createTempFile("dataname-log-assos", ".txt", logDir);
- File zipLog = ZipUtil.createZip(logDir);
- LogFile logFile = new LogFile(zipLog.getAbsolutePath(), null, LogDataType.TEXT);
- File serializedLogFile = null;
- File tmp = FileUtil.createTempFile("sub", "unit");
- SubprocessTestResultsParser resultParser = null;
- try {
- serializedLogFile = SerializationUtil.serialize(logFile);
- resultParser =
- new SubprocessTestResultsParser(mockRunListener, new InvocationContext());
- String logAssociation =
- String.format(
- "LOG_ASSOCIATION {\"loggedFile\":\"%s\",\"dataName\":\"dataname\"}\n",
- serializedLogFile.getAbsolutePath());
- FileUtil.writeToFile(logAssociation, tmp, true);
- resultParser.parseFile(tmp);
-
- verify(mockRunListener)
- .testLog(
- Mockito.eq("subprocess-dataname"),
- Mockito.eq(LogDataType.ZIP),
- Mockito.any());
- } finally {
- StreamUtil.close(resultParser);
- FileUtil.deleteFile(serializedLogFile);
- FileUtil.deleteFile(tmp);
- FileUtil.deleteFile(log);
- FileUtil.recursiveDelete(logDir);
- FileUtil.deleteFile(zipLog);
- }
- }
-
- @Test
public void testParse_avoidDoubleLog() throws Exception {
ILogSaverListener mockRunListener = mock(ILogSaverListener.class);
diff --git a/javatests/res/config/tf/unit-runner.xml b/javatests/res/config/tf/unit-runner.xml
index ddc527c..aae299b 100644
--- a/javatests/res/config/tf/unit-runner.xml
+++ b/javatests/res/config/tf/unit-runner.xml
@@ -24,4 +24,5 @@
<result_reporter class="com.android.tradefed.result.SubprocessResultsReporter">
<option name="output-test-log" value="true" />
</result_reporter>
+ <result_reporter class="com.android.tradefed.result.proto.StreamProtoResultReporter" />
</configuration>
diff --git a/proto/device/device_manager.proto b/proto/device/device_manager.proto
index 4511d6f..1a792c5 100644
--- a/proto/device/device_manager.proto
+++ b/proto/device/device_manager.proto
@@ -32,6 +32,23 @@
// Get the devices status
rpc GetDevicesStatus(GetDevicesStatusRequest)
returns (GetDevicesStatusResponse) {}
+ // Apply to stop leasing tests. The RPC returns immediately and doesn't wait
+ // for all leasing being stopped.
+ rpc StopLeasing(StopLeasingRequest) returns (StopLeasingResponse) {}
+}
+
+// The request of stopping leasing tests.
+message StopLeasingRequest {}
+
+// The response of stopping leasing tests.
+message StopLeasingResponse {
+ enum Result {
+ UNKNOWN = 0;
+ SUCCEED = 1;
+ FAIL = 2;
+ }
+ Result result = 1;
+ string message = 2;
}
// The request to reserve device.
diff --git a/proto/invocation/invocation_manager.proto b/proto/invocation/invocation_manager.proto
index 2b7ea76..830000c 100644
--- a/proto/invocation/invocation_manager.proto
+++ b/proto/invocation/invocation_manager.proto
@@ -29,6 +29,8 @@
rpc SubmitTestCommand(NewTestCommandRequest) returns (NewTestCommandResponse) {}
// Query the invocation detail info of a specific test command.
rpc GetInvocationDetail(InvocationDetailRequest) returns (InvocationDetailResponse) {}
+ // Request an invocation to be stopped, non-blocking.
+ rpc StopInvocation(StopInvocationRequest) returns (StopInvocationResponse) {}
}
// A new TF test request.
@@ -48,6 +50,27 @@
CommandErrorInfo command_error_info = 2;
}
+// Request the invocation to stop
+message StopInvocationRequest {
+ // Invocation id of the test to request stop
+ string invocation_id = 1;
+ // Specify a reason to be associated with the stop request
+ string reason = 2;
+}
+
+message StopInvocationResponse {
+ // Type of invocation status
+ enum Status {
+ UNSPECIFIED = 0;
+ SUCCESS = 1;
+ ERROR = 2;
+ }
+ // The type of status.
+ Status status = 1;
+ // If set, an error occurred and is described by ErrorInfo.
+ CommandErrorInfo command_error_info = 2;
+}
+
// The current status of the test command.
message InvocationStatus {
// Type of invocation status
diff --git a/res/config/checker/baseline_config.json b/res/config/checker/baseline_config.json
index 5098aa1..7f1d372 100644
--- a/res/config/checker/baseline_config.json
+++ b/res/config/checker/baseline_config.json
@@ -1,12 +1,31 @@
{
"keep_screen_on": {
+ "class_name": "com.android.tradefed.suite.checker.baseline.SettingsBaselineSetter",
"namespace": "global",
"key": "stay_on_while_plugged_in",
"value": "7"
},
"disable_os_auto_update": {
+ "class_name": "com.android.tradefed.suite.checker.baseline.SettingsBaselineSetter",
"namespace": "global",
"key": "ota_disable_automatic_update",
"value": "1"
+ },
+ "disable_device_config_sync": {
+ "class_name": "com.android.tradefed.suite.checker.baseline.SettingsBaselineSetter",
+ "namespace": "global",
+ "key": "device_config_sync_disabled",
+ "value": "1",
+ "experimental": true
+ },
+ "disable_usb_app_verification": {
+ "class_name": "com.android.tradefed.suite.checker.baseline.SettingsBaselineSetter",
+ "namespace": "secure",
+ "key": "verifier_verify_adb_installs",
+ "value": "0"
+ },
+ "clear_lock_screen": {
+ "class_name": "com.android.tradefed.suite.checker.baseline.LockSettingsBaselineSetter",
+ "clear_pwds": ["0000", "1234", "12345", "private"]
}
}
\ No newline at end of file
diff --git a/src/com/android/tradefed/cluster/ClusterCommandEvent.java b/src/com/android/tradefed/cluster/ClusterCommandEvent.java
index 50a11e0..ab2f636 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandEvent.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandEvent.java
@@ -57,7 +57,8 @@
InvocationEnded,
InvocationCompleted,
TestRunInProgress,
- TestEnded
+ TestEnded,
+ Unleased
}
private long mTimestamp;
diff --git a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
index 1c6cd99..23f919c 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
@@ -33,6 +33,7 @@
import com.android.tradefed.device.battery.BatteryController;
import com.android.tradefed.device.battery.IBatteryInfo;
import com.android.tradefed.device.battery.IBatteryInfo.BatteryState;
+import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.error.IHarnessException;
import com.android.tradefed.host.IHostOptions.PermitLimitType;
import com.android.tradefed.invoker.IInvocationContext;
@@ -58,6 +59,7 @@
import java.io.File;
import java.io.IOException;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -290,7 +292,14 @@
mFailureDescription = failure;
mError = failure.getErrorMessage();
if (failure.getCause() != null) {
- mError = StreamUtil.getStackTrace(failure.getCause());
+ Throwable cause = failure.getCause();
+ mError = StreamUtil.getStackTrace(cause);
+ if (cause instanceof HarnessRuntimeException
+ && InfraErrorIdentifier.TRADEFED_SKIPPED_TESTS_DURING_SHUTDOWN.equals(
+ ((HarnessRuntimeException) cause).getErrorId())) {
+ // Tests were not run, so un-lease the command so that it can be rescheduled.
+ unleaseCommands(Arrays.asList(mCommandTask));
+ }
}
}
@@ -530,7 +539,8 @@
return;
}
if (isShuttingDown()) {
- CLog.d("Tradefed shutting down, ignoring commands.");
+ CLog.d("Tradefed shutting down, unleasing commands.");
+ unleaseCommands(commands);
return;
}
execCommands(commands);
@@ -675,9 +685,11 @@
* @param commands a list of {@link ClusterCommand}s fetched from the cluster command queue.
*/
void execCommands(final List<ClusterCommand> commands) {
+ int commandIdx = 0;
for (final ClusterCommand commandTask : commands) {
if (isShuttingDown()) {
- CLog.d("Tradefed shutting down, ignoring remaining commands.");
+ CLog.d("Tradefed shutting down, unleasing remaining commands.");
+ unleaseCommands(commands.subList(commandIdx, commands.size()));
return;
}
try {
@@ -743,6 +755,7 @@
eventUploader.postEvent(eventBuilder.build());
eventUploader.flush();
}
+ commandIdx++;
}
}
@@ -868,4 +881,22 @@
CLog.e("failed to upload host state %s to TFC: %s", state.toString(), e);
}
}
+
+ /**
+ * Notifies TFC of commands that were not executed and need to be rescheduled.
+ *
+ * @param commands a list of {@link ClusterCommand} that need to be unleased to get rescheduled.
+ */
+ private synchronized void unleaseCommands(final List<ClusterCommand> commands) {
+ IClusterEventUploader<ClusterCommandEvent> eventUploader =
+ getClusterClient().getCommandEventUploader();
+ for (ClusterCommand command : commands) {
+ ClusterCommandEvent.Builder eventBuilder =
+ ClusterCommandEvent.createEventBuilder(command)
+ .setHostName(ClusterHostUtil.getHostName())
+ .setType(ClusterCommandEvent.Type.Unleased);
+ eventUploader.postEvent(eventBuilder.build());
+ }
+ eventUploader.flush();
+ }
}
diff --git a/src/com/android/tradefed/command/CommandOptions.java b/src/com/android/tradefed/command/CommandOptions.java
index 2e9970e..78470c2 100644
--- a/src/com/android/tradefed/command/CommandOptions.java
+++ b/src/com/android/tradefed/command/CommandOptions.java
@@ -146,6 +146,11 @@
public static final String USE_REMOTE_SANDBOX = "use-remote-sandbox";
@Option(
+ name = "remote-files",
+ description = "A list of files references to store in build info")
+ private Set<String> mRemoteFiles = new LinkedHashSet<>();
+
+ @Option(
name = USE_SANDBOX,
description = "Set if the invocation should use a sandbox to run or not."
)
@@ -273,6 +278,9 @@
+ "under developing, not for other uses.")
private Integer mMultiDeviceCount;
+ @Option(name = "enable-tracing", description = "Enable test invocation tracing.")
+ private boolean mTracingEnabled = true;
+
/**
* Set the help mode for the config.
* <p/>
@@ -508,6 +516,12 @@
/** {@inheritDoc} */
@Override
+ public Set<String> getRemoteFiles() {
+ return mRemoteFiles;
+ }
+
+ /** {@inheritDoc} */
+ @Override
public boolean shouldUseSandboxing() {
return mUseSandbox;
}
@@ -703,4 +717,10 @@
public void setMultiDeviceCount(int count) {
mMultiDeviceCount = count;
}
+
+ /** {@inheritDoc} */
+ @Override
+ public boolean isTracingEnabled() {
+ return mTracingEnabled;
+ }
}
diff --git a/src/com/android/tradefed/command/CommandScheduler.java b/src/com/android/tradefed/command/CommandScheduler.java
index 0c73817..eb3c36d 100644
--- a/src/com/android/tradefed/command/CommandScheduler.java
+++ b/src/com/android/tradefed/command/CommandScheduler.java
@@ -61,12 +61,19 @@
import com.android.tradefed.invoker.InvocationContext;
import com.android.tradefed.invoker.TestInvocation;
import com.android.tradefed.invoker.shard.ParentShardReplicate;
+import com.android.tradefed.invoker.tracing.ActiveTrace;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
+import com.android.tradefed.invoker.tracing.TracingLogger;
import com.android.tradefed.log.ILogRegistry.EventType;
import com.android.tradefed.log.LogRegistry;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ConsoleResultReporter;
+import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ILogSaver;
+import com.android.tradefed.result.ILogSaverListener;
import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.LogFile;
import com.android.tradefed.result.LogSaverResultForwarder;
import com.android.tradefed.result.ResultForwarder;
import com.android.tradefed.result.error.ErrorIdentifier;
@@ -78,6 +85,7 @@
import com.android.tradefed.service.management.TestInvocationManagementServer;
import com.android.tradefed.targetprep.DeviceFailedToBootError;
import com.android.tradefed.testtype.IRemoteTest;
+import com.android.tradefed.testtype.SubprocessTfLauncher;
import com.android.tradefed.testtype.suite.retry.RetryRescheduler;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.FileUtil;
@@ -142,6 +150,8 @@
/** map of device to active invocation threads */
private Map<IInvocationContext, InvocationThread> mInvocationThreadMap;
+ /** Track invocation that are done and terminating */
+ private Map<IInvocationContext, InvocationThread> mInvocationThreadMapTerminating;
/** timer for scheduling commands to be re-queued for execution */
private ScheduledThreadPoolExecutor mCommandTimer;
@@ -587,22 +597,31 @@
if (mClient != null) {
mClient.notifyTradefedInvocationStartEvent();
}
+ IConfiguration config = mCmd.getConfiguration();
+ if (config.getCommandOptions().isTracingEnabled()) {
+ long pid = ProcessHandle.current().pid();
+ long tid = Thread.currentThread().getId();
+ ActiveTrace trace = TracingLogger.createActiveTrace(pid, tid);
+ trace.startTracing(
+ config.getCommandOptions()
+ .getInvocationData()
+ .containsKey(SubprocessTfLauncher.SUBPROCESS_TAG_NAME));
+ }
mStartTime = System.currentTimeMillis();
ITestInvocation instance = getInvocation();
- IConfiguration config = mCmd.getConfiguration();
-
- for (final IScheduledInvocationListener listener : mListeners) {
- try {
- listener.invocationInitiated(mInvocationContext);
- } catch (Throwable anyException) {
- CLog.e("Exception caught while calling invocationInitiated:");
- CLog.e(anyException);
+ try (CloseableTraceScope ignore = new CloseableTraceScope("init")) {
+ for (final IScheduledInvocationListener listener : mListeners) {
+ try {
+ listener.invocationInitiated(mInvocationContext);
+ } catch (Throwable anyException) {
+ CLog.e("Exception caught while calling invocationInitiated:");
+ CLog.e(anyException);
+ }
}
}
-
Exception trackDeviceException = null;
boolean lastInvocationSet = false;
- try {
+ try (CloseableTraceScope ignore = new CloseableTraceScope("test-invocation")) {
// Copy the command options invocation attributes to the invocation if it has not
// been already done.
if (!config.getConfigurationDescription().shouldUseSandbox()
@@ -633,7 +652,7 @@
e);
setLastInvocationExitCode(ExitCode.FATAL_HOST_ERROR, e);
lastInvocationSet = true;
- shutdown();
+ shutdown(true);
} catch (Throwable e) {
setLastInvocationExitCode(ExitCode.THROWABLE_EXCEPTION, e);
lastInvocationSet = true;
@@ -649,25 +668,26 @@
"Updating command %d with elapsed time %d ms",
mCmd.getCommandTracker().getId(),
elapsedTime);
- // remove invocation thread first so another invocation can be started on device
- // when freed
- removeInvocationThread(this);
+ try (CloseableTraceScope ignore = new CloseableTraceScope("finalize_invocation")) {
+ // remove invocation thread first so another invocation can be started on device
+ // when freed
+ removeInvocationThread(this);
- checkStrayThreads();
+ checkStrayThreads();
- Map<ITestDevice, FreeDeviceState> deviceStates =
- createReleaseMap(mInvocationContext, trackDeviceException);
- for (final IScheduledInvocationListener listener : mListeners) {
- try {
- listener.invocationComplete(mInvocationContext, deviceStates);
- } catch (Throwable anyException) {
- CLog.e("Exception caught while calling invocationComplete:");
- CLog.e(anyException);
+ Map<ITestDevice, FreeDeviceState> deviceStates =
+ createReleaseMap(mInvocationContext, trackDeviceException);
+ for (final IScheduledInvocationListener listener : mListeners) {
+ try {
+ listener.invocationComplete(mInvocationContext, deviceStates);
+ } catch (Throwable anyException) {
+ CLog.e("Exception caught while calling invocationComplete:");
+ CLog.e(anyException);
+ }
}
- }
- if (!lastInvocationSet && instance.getExitInfo() != null) {
- setLastInvocationExitCode(
- instance.getExitInfo().mExitCode, instance.getExitInfo().mStack);
+ if (!lastInvocationSet && instance.getExitInfo() != null) {
+ setLastInvocationExitCode(
+ instance.getExitInfo().mExitCode, instance.getExitInfo().mStack);
}
if (getFeatureServer() != null) {
getFeatureServer().unregisterInvocation(config);
@@ -675,13 +695,55 @@
mCmd.commandFinished(elapsedTime);
logInvocationEndedEvent(
mCmd.getCommandTracker().getId(), elapsedTime, mInvocationContext);
- CLog.d("Finalizing the logger and invocation.");
+ }
+ CLog.logAndDisplay(LogLevel.INFO, "Finalizing the logger and invocation.");
+ ActiveTrace trace = TracingLogger.getActiveTrace();
+ if (trace != null) {
+ File traceFile = trace.finalizeTracing();
+ if (traceFile != null) {
+ logTrace(traceFile, config);
+ }
+ }
if (config.getCommandOptions().reportInvocationComplete()) {
LogSaverResultForwarder.reportEndHostLog(
- config.getLogSaver(), TestInvocation.TRADEFED_INVOC_COMPLETE_HOST_LOG);
+ config.getTestInvocationListeners(),
+ config.getLogSaver(),
+ TestInvocation.TRADEFED_INVOC_COMPLETE_HOST_LOG);
config.getLogOutput().closeLog();
LogRegistry.getLogRegistry().unregisterLogger();
}
+ clearTerminating(this);
+ }
+ }
+
+ /** Special handling to send the trace file from subprocess when needed. */
+ private void logTrace(File traceFile, IConfiguration config) {
+ if (config.getCommandOptions()
+ .getInvocationData()
+ .containsKey(SubprocessTfLauncher.SUBPROCESS_TAG_NAME)) {
+ CLog.logAndDisplay(LogLevel.INFO, "Sending trace from subprocess");
+ LogFile perfettoTrace =
+ new LogFile(traceFile.getAbsolutePath(), null, LogDataType.PERFETTO);
+ for (ITestInvocationListener listener : config.getTestInvocationListeners()) {
+ try {
+ if (listener instanceof ILogSaverListener) {
+ ((ILogSaverListener) listener)
+ .logAssociation(ActiveTrace.TRACE_KEY, perfettoTrace);
+ }
+ } catch (Exception e) {
+ CLog.logAndDisplay(LogLevel.ERROR, e.getMessage());
+ CLog.e(e);
+ }
+ }
+ } else {
+ try (FileInputStreamSource source = new FileInputStreamSource(traceFile, true)) {
+ LogSaverResultForwarder.logFile(
+ config.getTestInvocationListeners(),
+ config.getLogSaver(),
+ source,
+ ActiveTrace.TRACE_KEY,
+ LogDataType.PERFETTO);
+ }
}
}
@@ -738,6 +800,11 @@
return mInvocationContext;
}
+ /** Notify invocation on {@link CommandScheduler#shutdown()}. */
+ public void notifyInvocationStop(String message) {
+ getInvocation().notifyInvocationStopped(message);
+ }
+
/**
* Stops a running invocation. {@link CommandScheduler#shutdownHard()} will stop all running
* invocations.
@@ -751,7 +818,7 @@
* invocations.
*/
public void stopInvocation(String message, ErrorIdentifier errorId) {
- getInvocation().notifyInvocationStopped(message, errorId);
+ getInvocation().notifyInvocationForceStopped(message, errorId);
for (ITestDevice device : mInvocationContext.getDevices()) {
if (TestDeviceState.ONLINE.equals(device.getDeviceState())) {
// Kill all running processes on device.
@@ -938,6 +1005,7 @@
mSleepingCommands = new HashSet<>();
mExecutingCommands = new HashSet<>();
mInvocationThreadMap = new HashMap<IInvocationContext, InvocationThread>();
+ mInvocationThreadMapTerminating = new HashMap<IInvocationContext, InvocationThread>();
// use a ScheduledThreadPoolExecutorTimer as a single-threaded timer. This class
// is used instead of a java.util.Timer because it offers advanced shutdown options
mCommandTimer = new ScheduledThreadPoolExecutor(1);
@@ -1104,6 +1172,7 @@
}
CLog.i("Waiting for invocation threads to complete");
waitForAllInvocationThreads();
+ waitForTerminatingInvocationThreads();
exit(manager);
cleanUp();
CLog.logAndDisplay(LogLevel.INFO, "All done");
@@ -1220,10 +1289,10 @@
}
/** Wait until all invocation threads complete. */
- protected void waitForAllInvocationThreads() {
+ private void waitForAllInvocationThreads() {
List<InvocationThread> threadListCopy;
synchronized (this) {
- threadListCopy = new ArrayList<InvocationThread>(mInvocationThreadMap.size());
+ threadListCopy = new ArrayList<>();
threadListCopy.addAll(mInvocationThreadMap.values());
}
for (Thread thread : threadListCopy) {
@@ -1231,6 +1300,17 @@
}
}
+ private void waitForTerminatingInvocationThreads() {
+ List<InvocationThread> threadListCopy;
+ synchronized (this) {
+ threadListCopy = new ArrayList<>();
+ threadListCopy.addAll(mInvocationThreadMapTerminating.values());
+ }
+ for (Thread thread : threadListCopy) {
+ waitForThread(thread);
+ }
+ }
+
private void exit(IDeviceManager manager) {
if (manager != null) {
manager.terminate();
@@ -1619,7 +1699,7 @@
/** {@inheritDoc} */
@Override
- public void execCommand(
+ public long execCommand(
IInvocationContext context, IScheduledInvocationListener listener, String[] args)
throws ConfigurationException, NoDeviceException {
assertStarted();
@@ -1632,7 +1712,7 @@
// createConfiguration can be long for things like sandbox, so ensure we did not
// start a shutdown in the meantime.
CLog.w("Tradefed is shutting down, ignoring command.");
- return;
+ return -1;
}
ExecutableCommand execCmd = createExecutableCommand(cmdTracker, config, false);
@@ -1646,12 +1726,14 @@
}
CLog.d("Executing '%s' on '%s'", cmdTracker.getArgs()[0], devices);
startInvocation(context, execCmd, listener, new FreeDeviceHandler(manager));
+ return execCmd.getCommandTracker().getId();
} else {
// Log adb output just to help debug
- String adbOutput =
- ((DeviceManager) GlobalConfiguration.getDeviceManagerInstance())
- .executeGlobalAdbCommand("devices");
- CLog.e("'adb devices' output:\n%s", adbOutput);
+ if (getDeviceManager() instanceof DeviceManager) {
+ String adbOutput =
+ ((DeviceManager) getDeviceManager()).executeGlobalAdbCommand("devices");
+ CLog.e("'adb devices' output:\n%s", adbOutput);
+ }
throw new NoDeviceException(
String.format(
"No device match for allocation. Reason: %s.\ncommand: %s",
@@ -1662,7 +1744,7 @@
/** {@inheritDoc} */
@Override
- public void execCommand(
+ public long execCommand(
IScheduledInvocationListener listener, ITestDevice device, String[] args)
throws ConfigurationException {
// TODO: add support for execCommand multi-device allocation
@@ -1690,13 +1772,14 @@
context.setConfigurationDescriptor(config.getConfigurationDescription());
context.addAllocatedDevice(config.getDeviceConfig().get(0).getDeviceName(), device);
startInvocation(context, execCmd, listener);
+ return execCmd.getCommandTracker().getId();
}
/** {@inheritDoc} */
@Override
- public void execCommand(IScheduledInvocationListener listener, String[] args)
+ public long execCommand(IScheduledInvocationListener listener, String[] args)
throws ConfigurationException, NoDeviceException {
- execCommand(createInvocationContext(), listener, args);
+ return execCommand(createInvocationContext(), listener, args);
}
/**
@@ -1812,6 +1895,11 @@
/** Removes a {@link InvocationThread} from the active list. */
private synchronized void removeInvocationThread(InvocationThread invThread) {
mInvocationThreadMap.remove(invThread.getInvocationContext());
+ mInvocationThreadMapTerminating.put(invThread.getInvocationContext(), invThread);
+ }
+
+ private synchronized void clearTerminating(InvocationThread invThread) {
+ mInvocationThreadMapTerminating.remove(invThread.getInvocationContext());
}
private synchronized void throwIfDeviceInInvocationThread(List<ITestDevice> devices) {
@@ -1846,13 +1934,18 @@
return mCommandTimer.isShutdown() || mShutdownOnEmpty;
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
@Override
- public synchronized void shutdown() {
+ public synchronized void shutdown(boolean notifyStop) {
setHostState(HostState.QUITTING);
doShutdown();
+
+ if (notifyStop) {
+ String reason = "Tradefed is notified to stop";
+ for (InvocationThread thread : mInvocationThreadMap.values()) {
+ thread.notifyInvocationStop(reason);
+ }
+ }
}
private synchronized void doShutdown() {
diff --git a/src/com/android/tradefed/command/Console.java b/src/com/android/tradefed/command/Console.java
index 3528a50..5eb8211 100644
--- a/src/com/android/tradefed/command/Console.java
+++ b/src/com/android/tradefed/command/Console.java
@@ -172,7 +172,7 @@
exitMode = "commands";
mScheduler.shutdownOnEmpty();
} else {
- mScheduler.shutdown();
+ mScheduler.shutdown(true);
}
printLine("Signalling command scheduler for shutdown.");
printLine(
@@ -1390,7 +1390,8 @@
deviceManagementServer =
new DeviceManagementGrpcServer(
deviceManagementPort,
- GlobalConfiguration.getDeviceManagerInstance());
+ GlobalConfiguration.getDeviceManagerInstance(),
+ GlobalConfiguration.getInstance().getCommandScheduler());
GlobalConfiguration.getInstance()
.setDeviceManagementServer(deviceManagementServer);
deviceManagementServer.start();
diff --git a/src/com/android/tradefed/command/ICommandOptions.java b/src/com/android/tradefed/command/ICommandOptions.java
index bd537b0..b7f20ea 100644
--- a/src/com/android/tradefed/command/ICommandOptions.java
+++ b/src/com/android/tradefed/command/ICommandOptions.java
@@ -155,6 +155,9 @@
/** Returns the data passed to the invocation to describe it */
public UniqueMultiMap<String, String> getInvocationData();
+ /** Returns the list of remote files configured. */
+ public Set<String> getRemoteFiles();
+
/** Returns true if we should use Tf containers to run the invocation */
public boolean shouldUseSandboxing();
@@ -256,4 +259,7 @@
/** Sets the number of expected devices for multi-device tests. */
public void setMultiDeviceCount(int count);
+
+ /** Returns whether or not invocation tracing is enabled. */
+ public boolean isTracingEnabled();
}
diff --git a/src/com/android/tradefed/command/ICommandScheduler.java b/src/com/android/tradefed/command/ICommandScheduler.java
index 6ddc842..cebfde8 100644
--- a/src/com/android/tradefed/command/ICommandScheduler.java
+++ b/src/com/android/tradefed/command/ICommandScheduler.java
@@ -106,11 +106,11 @@
*
* @param listener the {@link ICommandScheduler.IScheduledInvocationListener} to be informed
* @param args the command arguments
- *
+ * @return The invocation id of the scheduled command.
* @throws ConfigurationException if command was invalid
* @throws NoDeviceException if there is no device to use
*/
- public void execCommand(IScheduledInvocationListener listener, String[] args)
+ public long execCommand(IScheduledInvocationListener listener, String[] args)
throws ConfigurationException, NoDeviceException;
/**
@@ -119,11 +119,12 @@
* @param listener the {@link ICommandScheduler.IScheduledInvocationListener} to be informed
* @param device the {@link ITestDevice} to use
* @param args the command arguments
- *
+ * @return The invocation id of the scheduled command.
* @throws ConfigurationException if command was invalid
*/
- public void execCommand(IScheduledInvocationListener listener, ITestDevice device,
- String[] args) throws ConfigurationException;
+ public long execCommand(
+ IScheduledInvocationListener listener, ITestDevice device, String[] args)
+ throws ConfigurationException;
/**
* Directly allocates a device and executes a command without adding it to the command queue
@@ -135,7 +136,7 @@
* @throws ConfigurationException if command was invalid
* @throws NoDeviceException if there is no device to use
*/
- public void execCommand(
+ public long execCommand(
IInvocationContext context, IScheduledInvocationListener listener, String[] args)
throws ConfigurationException, NoDeviceException;
@@ -146,14 +147,23 @@
/**
* Attempt to gracefully shutdown the command scheduler.
- * <p/>
- * Clears commands waiting to be tested, and requests that all invocations in progress
- * shut down gracefully.
- * <p/>
- * After shutdown is called, the scheduler main loop will wait for all invocations in progress
- * to complete before exiting completely.
+ *
+ * <p>Clears commands waiting to be tested, and requests that all invocations in progress shut
+ * down gracefully.
+ *
+ * <p>After shutdown is called, the scheduler main loop will wait for all invocations in
+ * progress to complete before exiting completely.
*/
- public void shutdown();
+ default void shutdown() {
+ shutdown(false);
+ }
+
+ /**
+ * Attempt to gracefully shutdown the command scheduler.
+ *
+ * @param notifyStop if true, notifies invocations of TF shutdown.
+ */
+ public void shutdown(boolean notifyStop);
/**
* Similar to {@link #shutdown()}, but will instead wait for all commands to be executed
diff --git a/src/com/android/tradefed/config/ConfigurationDef.java b/src/com/android/tradefed/config/ConfigurationDef.java
index e595de6..0f8bc17 100644
--- a/src/com/android/tradefed/config/ConfigurationDef.java
+++ b/src/com/android/tradefed/config/ConfigurationDef.java
@@ -19,6 +19,8 @@
import com.android.tradefed.device.metric.IMetricCollector;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.error.InfraErrorIdentifier;
+import com.android.tradefed.targetprep.ILabPreparer;
+import com.android.tradefed.targetprep.ITargetPreparer;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
@@ -346,6 +348,22 @@
config.setConfigurationObjectList(Configuration.DEVICE_NAME, deviceObjectList);
injectOptions(config, mOptionList);
+ List<ITargetPreparer> notILab = new ArrayList<>();
+ for (IDeviceConfiguration deviceConfig : config.getDeviceConfig()) {
+ for (ITargetPreparer labPreparer : deviceConfig.getLabPreparers()) {
+ if (!(labPreparer instanceof ILabPreparer)) {
+ notILab.add(labPreparer);
+ }
+ }
+ }
+ if (!notILab.isEmpty()) {
+ throw new ConfigurationException(
+ String.format(
+ "The following were specified as lab_preparer "
+ + "but aren't ILabPreparer: %s",
+ notILab),
+ InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR);
+ }
return config;
}
diff --git a/src/com/android/tradefed/config/filter/OptionFetcher.java b/src/com/android/tradefed/config/filter/OptionFetcher.java
index 58a0656..6f62432 100644
--- a/src/com/android/tradefed/config/filter/OptionFetcher.java
+++ b/src/com/android/tradefed/config/filter/OptionFetcher.java
@@ -38,13 +38,9 @@
*/
public class OptionFetcher implements AutoCloseable {
- /**
- * Set of options that should align with the parent process.
- */
- private static final Set<String> OPTION_TO_FETCH = ImmutableSet.of(
- "retry-isolation-grade",
- "avd-in-parent"
- );
+ /** Set of options that should align with the parent process. */
+ private static final Set<String> OPTION_TO_FETCH =
+ ImmutableSet.of("retry-isolation-grade", "avd-in-parent", "enable-tracing");
private TradefedFeatureClient mClient;
diff --git a/src/com/android/tradefed/device/BackgroundDeviceAction.java b/src/com/android/tradefed/device/BackgroundDeviceAction.java
index 53c6fd7..32fda9a 100644
--- a/src/com/android/tradefed/device/BackgroundDeviceAction.java
+++ b/src/com/android/tradefed/device/BackgroundDeviceAction.java
@@ -21,6 +21,7 @@
import com.android.ddmlib.IShellOutputReceiver;
import com.android.ddmlib.ShellCommandUnresponsiveException;
import com.android.ddmlib.TimeoutException;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
@@ -82,27 +83,31 @@
String separator = String.format(
"\n========== beginning of new [%s] output ==========\n", mDescriptor);
while (!isCancelled()) {
- if (mLogStartDelay > 0) {
- CLog.d("Sleep for %d before starting %s for %s.", mLogStartDelay, mDescriptor,
- mTestDevice.getSerialNumber());
- getRunUtil().sleep(mLogStartDelay);
- }
- blockUntilOnlineNoThrow();
- // check again if the operation has been cancelled after the wait for online
- if (isCancelled()) {
- break;
- }
- CLog.d("Starting %s for %s.", mDescriptor, mTestDevice.getSerialNumber());
- mReceiver.addOutput(separator.getBytes(), 0, separator.length());
- try {
- mTestDevice.getIDevice().executeShellCommand(mCommand, mReceiver,
- 0, TimeUnit.MILLISECONDS);
- } catch (AdbCommandRejectedException e) {
- // For command rejected wait a bit to let the device reach a stable state again.
- getRunUtil().sleep(ONLINE_POLL_INTERVAL_MS);
- waitForDeviceRecovery(e.getClass().getName());
- } catch (IOException | ShellCommandUnresponsiveException | TimeoutException e) {
- waitForDeviceRecovery(e.getClass().getName());
+ try (CloseableTraceScope ignore = new CloseableTraceScope()) {
+ if (mLogStartDelay > 0) {
+ CLog.d(
+ "Sleep for %d before starting %s for %s.",
+ mLogStartDelay, mDescriptor, mTestDevice.getSerialNumber());
+ getRunUtil().sleep(mLogStartDelay);
+ }
+ blockUntilOnlineNoThrow();
+ // check again if the operation has been cancelled after the wait for online
+ if (isCancelled()) {
+ break;
+ }
+ CLog.d("Starting %s for %s.", mDescriptor, mTestDevice.getSerialNumber());
+ mReceiver.addOutput(separator.getBytes(), 0, separator.length());
+ try {
+ mTestDevice
+ .getIDevice()
+ .executeShellCommand(mCommand, mReceiver, 0, TimeUnit.MILLISECONDS);
+ } catch (AdbCommandRejectedException e) {
+ // For command rejected wait a bit to let the device reach a stable state again.
+ getRunUtil().sleep(ONLINE_POLL_INTERVAL_MS);
+ waitForDeviceRecovery(e.getClass().getName());
+ } catch (IOException | ShellCommandUnresponsiveException | TimeoutException e) {
+ waitForDeviceRecovery(e.getClass().getName());
+ }
}
}
}
diff --git a/src/com/android/tradefed/device/DeviceManager.java b/src/com/android/tradefed/device/DeviceManager.java
index 9fc5c78..dbd0f6f 100644
--- a/src/com/android/tradefed/device/DeviceManager.java
+++ b/src/com/android/tradefed/device/DeviceManager.java
@@ -548,20 +548,17 @@
// hostname.google.com:vsoc-1
String[] parts = preconfigureDevice.split(":", 2);
preconfigureHostUsers.putIfAbsent(parts[0], new ArrayList<>());
- preconfigureHostUsers.get(parts[0]).add(parts[1]);
+ preconfigureHostUsers.get(parts[0]).add(parts.length > 1 ? parts[1] : null);
}
for (Map.Entry<String, List<String>> hostUsers : preconfigureHostUsers.entrySet()) {
for (int i = 0; i < hostUsers.getValue().size(); i++) {
- addAvailableDevice(
- new RemoteAvdIDevice(
- String.format(
- "%s-%s-%s",
- GCE_DEVICE_SERIAL_PREFIX,
- hostUsers.getKey(),
- hostUsers.getValue().get(i)),
- hostUsers.getKey(),
- hostUsers.getValue().get(i),
- i));
+ String user = hostUsers.getValue().get(i);
+ String serial =
+ String.format("%s-%s-%d", GCE_DEVICE_SERIAL_PREFIX, hostUsers.getKey(), i);
+ if (user != null) {
+ serial += "-" + user;
+ }
+ addAvailableDevice(new RemoteAvdIDevice(serial, hostUsers.getKey(), user, i));
}
}
diff --git a/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java b/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
index f027f48..f3809c8 100644
--- a/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
+++ b/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
@@ -26,7 +26,6 @@
import com.android.tradefed.result.FileInputStreamSource;
import com.android.tradefed.result.ITestLoggerReceiver;
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;
@@ -49,7 +48,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-import java.util.Map;
/** The class for local virtual devices running on TradeFed host. */
public class LocalAndroidVirtualDevice extends RemoteAndroidDevice implements ITestLoggerReceiver {
@@ -559,12 +557,15 @@
CLog.e(ex);
return;
}
- for (Map.Entry<String, LogDataType> log : mGceAvdInfo.getLogs().entrySet()) {
- File file = new File(log.getKey());
+ for (GceAvdInfo.LogFileEntry log : mGceAvdInfo.getLogs()) {
+ File file = new File(log.path);
if (file.exists()) {
try (InputStreamSource source = new FileInputStreamSource(file)) {
if (file.toPath().toRealPath().startsWith(realInstanceDir)) {
- mTestLogger.testLog(file.getName(), log.getValue(), source);
+ mTestLogger.testLog(
+ Strings.isNullOrEmpty(log.name) ? file.getName() : log.name,
+ log.type,
+ source);
} else {
CLog.w("%s is not in instance directory.", file.getAbsolutePath());
}
diff --git a/src/com/android/tradefed/device/ManagedDeviceList.java b/src/com/android/tradefed/device/ManagedDeviceList.java
index c67283d..de07e87 100644
--- a/src/com/android/tradefed/device/ManagedDeviceList.java
+++ b/src/com/android/tradefed/device/ManagedDeviceList.java
@@ -60,7 +60,18 @@
event = DeviceEvent.EXPLICIT_ALLOCATE_REQUEST;
}
DeviceEventResponse r = element.handleAllocationEvent(event);
- return r.stateChanged && r.allocationState == DeviceAllocationState.Allocated;
+ boolean res =
+ r.stateChanged && r.allocationState == DeviceAllocationState.Allocated;
+ if (!res) {
+ mDeviceSelectionMatcher
+ .getNoMatchReason()
+ .put(
+ "already_allocated",
+ String.format(
+ "Device %s is matching but " + "already allocated.",
+ element.getIDevice().getSerialNumber()));
+ }
+ return res;
}
return false;
}
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index 13719d4..4f691de 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -3556,6 +3556,15 @@
doAdbReboot(mode, null);
}
+ // We want to wait on a command that verifies we've rebooted.
+ // However, it is possible to issue this command too quickly and get
+ // a response before the device has begun the reboot process (see
+ // b/242200753).
+ // While not as clean as we'd like, we wait 1.5 seconds before
+ // issuing any waiting commands, as devices generally take much
+ // longer than 1.5 seconds to reboot anyway.
+ getRunUtil().sleep(1500);
+
if (RebootMode.REBOOT_INTO_FASTBOOTD.equals(mode)
&& getHostOptions().isFastbootdEnable()) {
if (!mStateMonitor.waitForDeviceFastbootd(
diff --git a/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java b/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java
index a6070ad..e98b0ea 100644
--- a/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java
+++ b/src/com/android/tradefed/device/cloud/CommonLogRemoteFileUtil.java
@@ -28,7 +28,7 @@
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.ZipUtil;
-
+import com.google.common.base.Strings;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -189,8 +189,7 @@
CLog.e("GceAvdInfo was null, cannot collect remote files.");
return;
}
- // Capture known extra files
- List<KnownLogFileEntry> toFetch = KNOWN_FILES_TO_FETCH.get(options.getInstanceType());
+ List<KnownLogFileEntry> toFetch = null;
if (options.useOxygen()) {
// Override the list of logs to collect when the device is hosted by Oxygen service.
toFetch = new ArrayList<>(OXYGEN_LOG_FILES);
@@ -198,10 +197,28 @@
gceAvd, options, runUtil, 60000, OXYGEN_CUTTLEFISH_LOG_DIR)) {
toFetch.addAll(OXYGEN_LOG_FILES_FALLBACK);
}
+ } else {
+ boolean reported = false;
+ for (GceAvdInfo.LogFileEntry entry : gceAvd.getLogs()) {
+ if (logRemoteFile(
+ testLogger,
+ gceAvd,
+ options,
+ runUtil,
+ entry.path,
+ entry.type,
+ Strings.isNullOrEmpty(entry.name) ? null : entry.name)) {
+ reported = true;
+ }
+ }
+ if (!reported) {
+ CLog.i("GceAvdInfo does not contain logs. Fall back to known log files.");
+ toFetch = KNOWN_FILES_TO_FETCH.get(options.getInstanceType());
+ }
}
if (toFetch != null) {
for (KnownLogFileEntry entry : toFetch) {
- LogRemoteFile(
+ logRemoteFile(
testLogger,
gceAvd,
options,
@@ -218,7 +235,7 @@
}
for (String file : options.getRemoteFetchFilePattern()) {
// TODO: Improve type of files.
- LogRemoteFile(
+ logRemoteFile(
testLogger, gceAvd, options, runUtil, file, LogDataType.CUTTLEFISH_LOG, null);
}
}
@@ -354,8 +371,9 @@
* @param logType The expected type of the pulled log.
* @param baseName The base name that will be used to log the file, if null the actually file
* name will be used.
+ * @return whether the file is logged successfully.
*/
- private static void LogRemoteFile(
+ private static boolean logRemoteFile(
ITestLogger testLogger,
GceAvdInfo gceAvd,
TestDeviceOptions options,
@@ -363,7 +381,11 @@
String fileToRetrieve,
LogDataType logType,
String baseName) {
- GceManager.logNestedRemoteFile(
+ if (baseName != null && baseName.startsWith(TOMBSTONES_ZIP_NAME)) {
+ // TODO(b/154175542): Refactor fetchTombstones.
+ return false;
+ }
+ return GceManager.logNestedRemoteFile(
testLogger, gceAvd, options, runUtil, fileToRetrieve, logType, baseName);
}
}
diff --git a/src/com/android/tradefed/device/cloud/GceAvdInfo.java b/src/com/android/tradefed/device/cloud/GceAvdInfo.java
index efbe3d8..417f37e 100644
--- a/src/com/android/tradefed/device/cloud/GceAvdInfo.java
+++ b/src/com/android/tradefed/device/cloud/GceAvdInfo.java
@@ -41,13 +41,41 @@
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
-import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Structure to hold relevant data for a given GCE AVD instance. */
public class GceAvdInfo {
+ public static class LogFileEntry {
+ public final String path;
+ public final LogDataType type;
+ // The name is optional and defaults to an empty string.
+ public final String name;
+
+ @VisibleForTesting
+ LogFileEntry(String path, LogDataType type, String name) {
+ this.path = path;
+ this.type = type;
+ this.name = name;
+ }
+
+ LogFileEntry(JSONObject log) throws JSONException {
+ path = log.getString("path");
+ type = parseLogDataType(log.getString("type"));
+ name = log.optString("name", "");
+ }
+
+ private LogDataType parseLogDataType(String typeString) {
+ try {
+ return LogDataType.valueOf(typeString);
+ } catch (IllegalArgumentException e) {
+ CLog.w("Unknown log type in GCE AVD info: %s", typeString);
+ return LogDataType.UNKNOWN;
+ }
+ }
+ }
+
public static final List<String> BUILD_VARS =
Arrays.asList(
"build_id",
@@ -69,7 +97,7 @@
private String mErrors;
private GceStatus mStatus;
private HashMap<String, String> mBuildVars;
- private Map<String, LogDataType> mLogs;
+ private List<LogFileEntry> mLogs;
private boolean mIsIpPreconfigured = false;
public static enum GceStatus {
@@ -83,7 +111,7 @@
mInstanceName = instanceName;
mHostAndPort = hostAndPort;
mBuildVars = new HashMap<String, String>();
- mLogs = new HashMap<String, LogDataType>();
+ mLogs = new ArrayList<LogFileEntry>();
}
public GceAvdInfo(
@@ -137,7 +165,7 @@
}
/** Return the map from local or remote log paths to types. */
- public Map<String, LogDataType> getLogs() {
+ public List<LogFileEntry> getLogs() {
return mLogs;
}
@@ -245,7 +273,7 @@
errorId,
errors,
gceStatus);
- avdInfo.mLogs.putAll(parseLogField(d));
+ avdInfo.mLogs.addAll(parseLogField(d));
for (String buildVar : BUILD_VARS) {
if (d.has(buildVar) && !d.getString(buildVar).trim().isEmpty()) {
avdInfo.addBuildVar(buildVar, d.getString(buildVar).trim());
@@ -376,29 +404,18 @@
* Parse log paths from a device object.
*
* @param device the device object in JSON.
- * @return a map from log paths to {@link LogDataType}.
+ * @return a list of {@link LogFileEntry}.
* @throws JSONException if any required property is missing.
*/
- private static Map<String, LogDataType> parseLogField(JSONObject device) throws JSONException {
- Map<String, LogDataType> logs = new HashMap<String, LogDataType>();
+ private static List<LogFileEntry> parseLogField(JSONObject device) throws JSONException {
+ List<LogFileEntry> logs = new ArrayList<LogFileEntry>();
JSONArray logArray = device.optJSONArray("logs");
if (logArray == null) {
return logs;
}
for (int i = 0; i < logArray.length(); i++) {
JSONObject logObject = logArray.getJSONObject(i);
- String path = logObject.getString("path");
- String typeString = logObject.getString("type");
- LogDataType type;
- try {
- type = LogDataType.valueOf(typeString);
- } catch (IllegalArgumentException e) {
- CLog.w("Unknown log type in GCE AVD info: %s", typeString);
- type = LogDataType.UNKNOWN;
- }
- if (logs.put(path, type) != null) {
- CLog.w("Repeated log path in GCE AVD info: %s", path);
- }
+ logs.add(new LogFileEntry(logObject));
}
return logs;
}
diff --git a/src/com/android/tradefed/device/cloud/GceManager.java b/src/com/android/tradefed/device/cloud/GceManager.java
index 5b6f696..584d558 100644
--- a/src/com/android/tradefed/device/cloud/GceManager.java
+++ b/src/com/android/tradefed/device/cloud/GceManager.java
@@ -822,15 +822,16 @@
* @param runUtil a {@link IRunUtil} to execute commands.
* @param remoteFilePath The remote path where to find the file.
* @param type the {@link LogDataType} of the logged file.
+ * @return whether the file is logged successfully.
*/
- public static void logNestedRemoteFile(
+ public static boolean logNestedRemoteFile(
ITestLogger logger,
GceAvdInfo gceAvd,
TestDeviceOptions options,
IRunUtil runUtil,
String remoteFilePath,
LogDataType type) {
- logNestedRemoteFile(logger, gceAvd, options, runUtil, remoteFilePath, type, null);
+ return logNestedRemoteFile(logger, gceAvd, options, runUtil, remoteFilePath, type, null);
}
/**
@@ -845,8 +846,9 @@
* @param type the {@link LogDataType} of the logged file.
* @param baseName The base name to use to log the file. If null the actual file name will be
* used.
+ * @return whether the file is logged successfully.
*/
- public static void logNestedRemoteFile(
+ public static boolean logNestedRemoteFile(
ITestLogger logger,
GceAvdInfo gceAvd,
TestDeviceOptions options,
@@ -864,6 +866,7 @@
if (remoteFile != null) {
// If we happened to fetch a directory, log all the subfiles
logDirectory(remoteFile, baseName, logger, type);
+ return true;
}
} else {
remoteFile =
@@ -871,8 +874,10 @@
gceAvd, options, runUtil, REMOTE_FILE_OP_TIMEOUT, remoteFilePath);
if (remoteFile != null) {
logFile(remoteFile, baseName, logger, type);
+ return true;
}
}
+ return false;
}
private static void logDirectory(
diff --git a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
index 6bb5160..cb4eceb 100644
--- a/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
+++ b/src/com/android/tradefed/device/cloud/RemoteAndroidVirtualDevice.java
@@ -19,6 +19,7 @@
import com.android.ddmlib.IDevice.DeviceState;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.command.remote.DeviceDescriptor;
+import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.IDeviceMonitor;
@@ -29,6 +30,7 @@
import com.android.tradefed.device.TestDeviceOptions;
import com.android.tradefed.device.TestDeviceOptions.InstanceType;
import com.android.tradefed.device.cloud.GceAvdInfo.GceStatus;
+import com.android.tradefed.host.IHostOptions.PermitLimitType;
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.ITestLogger;
@@ -54,6 +56,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
@@ -105,8 +108,30 @@
// Launch GCE helper script.
long startTime = getCurrentTime();
- launchGce(info, attributes);
- long remainingTime = getOptions().getGceCmdTimeout() - (getCurrentTime() - startTime);
+ long remainingTime = 0;
+ try {
+ if (GlobalConfiguration.getInstance().getHostOptions().getConcurrentFlasherLimit()
+ != null) {
+ GlobalConfiguration.getInstance()
+ .getHostOptions()
+ .takePermit(PermitLimitType.CONCURRENT_FLASHER);
+ long queueTime = System.currentTimeMillis() - startTime;
+ CLog.v(
+ "Fetch and launch CVD permit obtained after %ds",
+ TimeUnit.MILLISECONDS.toSeconds(queueTime));
+ }
+ launchGce(info, attributes);
+ remainingTime =
+ getOptions().getGceCmdTimeout()
+ - (getCurrentTime() - startTime);
+ } finally {
+ if (GlobalConfiguration.getInstance().getHostOptions().getConcurrentFlasherLimit()
+ != null) {
+ GlobalConfiguration.getInstance()
+ .getHostOptions()
+ .returnPermit(PermitLimitType.CONCURRENT_FLASHER);
+ }
+ }
if (remainingTime < 0) {
throw new DeviceNotAvailableException(
String.format(
diff --git a/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java b/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
index 653086f..993620b 100644
--- a/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
+++ b/src/com/android/tradefed/device/metric/JavaCodeCoverageCollector.java
@@ -21,7 +21,6 @@
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.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
@@ -43,6 +42,8 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Splitter;
+import org.jacoco.core.tools.ExecFileLoader;
+
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
@@ -71,13 +72,7 @@
// Timeout for pulling coverage files from the device, in minutes.
private static final long TIMEOUT_MINUTES = 20;
- @Deprecated
- @Option(
- name = "merge-coverage-measurements",
- description =
- "Merge coverage measurements after all tests are complete rather than logging"
- + " individual measurements.")
- private boolean mMergeCoverageMeasurements = false;
+ private ExecFileLoader mExecFileLoader;
private JavaCodeCoverageFlusher mFlusher;
private IConfiguration mConfiguration;
@@ -117,15 +112,10 @@
}
@VisibleForTesting
- public void setCoverageFlusher(JavaCodeCoverageFlusher flusher) {
+ void setCoverageFlusher(JavaCodeCoverageFlusher flusher) {
mFlusher = flusher;
}
- @VisibleForTesting
- public void setMergeMeasurements(boolean merge) {
- mMergeCoverageMeasurements = merge;
- }
-
@Override
public void onTestRunEnd(DeviceMetricData runData, final Map<String, Metric> runMetrics)
throws DeviceNotAvailableException {
@@ -166,7 +156,7 @@
"Failed to pull test coverage file %s from the device.",
testCoveragePath);
} else {
- logCoverageMeasurement(testCoverage);
+ saveCoverageMeasurement(testCoverage);
}
}
@@ -192,7 +182,7 @@
// Decompress the files and log the measurements.
untarDir = TarUtil.extractTarGzipToTemp(coverageTarGz, "java_coverage");
for (String coveragePath : FileUtil.findFiles(untarDir, ".*\\.ec")) {
- logCoverageMeasurement(new File(coveragePath));
+ saveCoverageMeasurement(new File(coveragePath));
}
} catch (IOException e) {
throw new RuntimeException(e);
@@ -206,9 +196,36 @@
cleanUpDeviceCoverageFiles(device);
}
}
+
+ // Log the merged coverage data file if the flag is set.
+ if (shouldMergeCoverage() && (mExecFileLoader != null)) {
+ File mergedCoverage = null;
+ try {
+ mergedCoverage = FileUtil.createTempFile("merged_java_coverage", ".ec");
+ mExecFileLoader.save(mergedCoverage, false);
+ logCoverageMeasurement(mergedCoverage);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ } finally {
+ mExecFileLoader = null;
+ FileUtil.deleteFile(mergedCoverage);
+ }
+ }
}
- /** Saves files as Java coverage measurements. */
+ /** Saves Java coverage file data. */
+ private void saveCoverageMeasurement(File coverageFile) throws IOException {
+ if (shouldMergeCoverage()) {
+ if (mExecFileLoader == null) {
+ mExecFileLoader = new ExecFileLoader();
+ }
+ mExecFileLoader.load(coverageFile);
+ } else {
+ logCoverageMeasurement(coverageFile);
+ }
+ }
+
+ /** Logs files as Java coverage measurements. */
private void logCoverageMeasurement(File coverageFile) {
try (FileInputStreamSource source = new FileInputStreamSource(coverageFile, true)) {
testLog(
@@ -264,4 +281,8 @@
.getCoverageToolchains()
.contains(CoverageOptions.Toolchain.JACOCO);
}
+
+ private boolean shouldMergeCoverage() {
+ return mConfiguration != null && mConfiguration.getCoverageOptions().shouldMergeCoverage();
+ }
}
diff --git a/src/com/android/tradefed/invoker/ITestInvocation.java b/src/com/android/tradefed/invoker/ITestInvocation.java
index a3d6dbd..1396547 100644
--- a/src/com/android/tradefed/invoker/ITestInvocation.java
+++ b/src/com/android/tradefed/invoker/ITestInvocation.java
@@ -47,8 +47,16 @@
* Notify the {@link TestInvocation} that TradeFed has been requested to stop.
*
* @param message The message associated with stopping the invocation
+ * @param errorId Identifier associated with the forced stop
*/
- public default void notifyInvocationStopped(String message, ErrorIdentifier errorId) {}
+ public default void notifyInvocationForceStopped(String message, ErrorIdentifier errorId) {}
+
+ /**
+ * Notify the {@link TestInvocation} that TradeFed will eventually shutdown.
+ *
+ * @param message The message associated with stopping the invocation
+ */
+ public default void notifyInvocationStopped(String message) {}
/** The exit information of the given invocation. */
public default ExitInformation getExitInfo() {
diff --git a/src/com/android/tradefed/invoker/InvocationExecution.java b/src/com/android/tradefed/invoker/InvocationExecution.java
index f1920b9..a928e07d 100644
--- a/src/com/android/tradefed/invoker/InvocationExecution.java
+++ b/src/com/android/tradefed/invoker/InvocationExecution.java
@@ -25,6 +25,7 @@
import com.android.tradefed.build.IBuildProvider;
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.build.IDeviceBuildProvider;
+import com.android.tradefed.command.ICommandOptions;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.IConfigurationReceiver;
@@ -48,6 +49,7 @@
import com.android.tradefed.invoker.logger.TfObjectTracker;
import com.android.tradefed.invoker.shard.IShardHelper;
import com.android.tradefed.invoker.shard.TestsPoolPoller;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ByteArrayInputStreamSource;
@@ -191,6 +193,7 @@
throw re;
}
setBinariesVersion(testInfo.getContext());
+ copyRemoteFiles(config.getCommandOptions(), testInfo.getBuildInfo());
return true;
}
@@ -259,7 +262,8 @@
mTrackLabPreparers = new ConcurrentHashMap<>();
mTrackTargetPreparers = new ConcurrentHashMap<>();
InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.SETUP_START, start);
- try {
+ try (CloseableTraceScope ignored =
+ new CloseableTraceScope(InvocationMetricKey.lab_setup.name())) {
for (String deviceName : testInfo.getContext().getDeviceConfigNames()) {
ITestDevice device = testInfo.getContext().getDevice(deviceName);
CLog.d("Starting setup for device: '%s'", device.getSerialNumber());
@@ -276,7 +280,16 @@
listener,
testInfo,
"multi pre target preparer setup");
-
+ runLabPreparersSetup(testInfo, config, listener);
+ } finally {
+ long end = System.currentTimeMillis();
+ InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.SETUP_END, end);
+ InvocationMetricLogger.addInvocationPairMetrics(
+ InvocationMetricKey.SETUP_PAIR, start, end);
+ }
+ long startPreparer = System.currentTimeMillis();
+ try (CloseableTraceScope ignored =
+ new CloseableTraceScope(InvocationMetricKey.test_setup.name())) {
runPreparersSetup(testInfo, config, listener);
// After all the individual setup, make the multi-devices setup
@@ -291,12 +304,11 @@
// Note: These metrics are handled in a try in case of a kernel reset or device issue.
// Setup timing metric. It does not include flashing time on boot tests.
long end = System.currentTimeMillis();
- InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.SETUP_END, end);
InvocationMetricLogger.addInvocationPairMetrics(
- InvocationMetricKey.SETUP_PAIR, start, end);
+ InvocationMetricKey.TEST_SETUP_PAIR, startPreparer, end);
long setupDuration = end - start;
InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.SETUP, setupDuration);
- CLog.d("Setup duration: %s'", TimeUtil.formatElapsedTime(setupDuration));
+ CLog.d("Total setup duration: %s'", TimeUtil.formatElapsedTime(setupDuration));
// Upload the setup logcat after setup is complete.
for (ITestDevice device : testInfo.getDevices()) {
reportLogs(device, listener, Stage.SETUP);
@@ -304,7 +316,7 @@
}
}
- private void runPreparersSetup(
+ private void runLabPreparersSetup(
TestInformation testInfo, IConfiguration config, ITestLogger listener)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
int index = 0;
@@ -330,13 +342,6 @@
getLabPreparersToRun(config, deviceName),
mTrackLabPreparers.get(deviceName),
listener);
- runPreparationOnDevice(
- replicated,
- deviceName,
- deviceIndex,
- getTargetPreparersToRun(config, deviceName),
- mTrackTargetPreparers.get(deviceName),
- listener);
return true;
};
callableTasks.add(callableTask);
@@ -370,6 +375,61 @@
getLabPreparersToRun(config, deviceName),
mTrackLabPreparers.get(deviceName),
listener);
+ index++;
+ }
+ }
+ }
+
+ private void runPreparersSetup(
+ TestInformation testInfo, IConfiguration config, ITestLogger listener)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
+ int index = 0;
+ if ((config.getCommandOptions().shouldUseParallelSetup()
+ || config.getCommandOptions().shouldUseReplicateSetup())
+ && config.getDeviceConfig().size() > 1) {
+ CLog.d("Using parallel setup.");
+ ParallelDeviceExecutor<Boolean> executor =
+ new ParallelDeviceExecutor<>(testInfo.getContext().getDevices().size());
+ List<Callable<Boolean>> callableTasks = new ArrayList<>();
+ for (String deviceName : testInfo.getContext().getDeviceConfigNames()) {
+ final int deviceIndex = index;
+ // Replicate TestInfo
+ TestInformation replicated =
+ TestInformation.createModuleTestInfo(testInfo, testInfo.getContext());
+ Callable<Boolean> callableTask =
+ () -> {
+ runPreparationOnDevice(
+ replicated,
+ deviceName,
+ deviceIndex,
+ getTargetPreparersToRun(config, deviceName),
+ mTrackTargetPreparers.get(deviceName),
+ listener);
+ return true;
+ };
+ callableTasks.add(callableTask);
+ index++;
+ }
+ 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.
+ for (Throwable error : errors) {
+ if (error instanceof TargetSetupError) {
+ throw (TargetSetupError) error;
+ }
+ if (error instanceof BuildError) {
+ throw (BuildError) error;
+ }
+ if (error instanceof DeviceNotAvailableException) {
+ throw (DeviceNotAvailableException) error;
+ }
+ throw new RuntimeException(error);
+ }
+ }
+ } else {
+ for (String deviceName : testInfo.getContext().getDeviceConfigNames()) {
runPreparationOnDevice(
testInfo,
deviceName,
@@ -414,7 +474,8 @@
CLog.d(
"starting lab preparer '%s' on device: '%s'",
preparer, device.getSerialNumber());
- try {
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope(preparer.getClass().getSimpleName())) {
testInfo.setActiveDeviceIndex(index);
preparer.setUp(testInfo);
} finally {
@@ -464,7 +525,8 @@
long startTime = System.currentTimeMillis();
CLog.d("starting preparer '%s' on device: '%s'", preparer, device.getSerialNumber());
- try {
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope(preparer.getClass().getSimpleName())) {
testInfo.setActiveDeviceIndex(index);
preparer.setUp(testInfo);
} finally {
@@ -497,15 +559,18 @@
return;
}
long start = System.currentTimeMillis();
- customizeDevicePreInvocation(config, context);
- for (String deviceName : context.getDeviceConfigNames()) {
- ITestDevice device = context.getDevice(deviceName);
+ try (CloseableTraceScope ignore = new CloseableTraceScope("device_pre_invocation_setup")) {
+ customizeDevicePreInvocation(config, context);
+ for (String deviceName : context.getDeviceConfigNames()) {
+ ITestDevice device = context.getDevice(deviceName);
- CLog.d("Starting device pre invocation setup for : '%s'", device.getSerialNumber());
- if (device instanceof ITestLoggerReceiver) {
- ((ITestLoggerReceiver) context.getDevice(deviceName)).setTestLogger(logger);
+ CLog.d("Starting device pre invocation setup for : '%s'", device.getSerialNumber());
+ if (device instanceof ITestLoggerReceiver) {
+ ((ITestLoggerReceiver) context.getDevice(deviceName)).setTestLogger(logger);
+ }
+ device.preInvocationSetup(
+ context.getBuildInfo(deviceName), context.getAttributes());
}
- device.preInvocationSetup(context.getBuildInfo(deviceName), context.getAttributes());
}
// Also report device pre invocation into setup
InvocationMetricLogger.addInvocationPairMetrics(
@@ -531,14 +596,10 @@
CLog.i("--disable-invocation-setup-and-teardown, skipping post-invocation teardown.");
return;
}
- long start = System.currentTimeMillis();
for (String deviceName : context.getDeviceConfigNames()) {
ITestDevice device = context.getDevice(deviceName);
device.postInvocationTearDown(exception);
}
- // Also report device post invocation into teardown
- InvocationMetricLogger.addInvocationPairMetrics(
- InvocationMetricKey.TEARDOWN_PAIR, start, System.currentTimeMillis());
}
/** Runs the {@link IMultiTargetPreparer} specified. */
@@ -631,82 +692,83 @@
Throwable deferredThrowable;
long start = System.currentTimeMillis();
InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.TEARDOWN_START, start);
-
- List<IMultiTargetPreparer> multiPreparers = config.getMultiTargetPreparers();
- deferredThrowable =
- runMultiTargetPreparersTearDown(
- multiPreparers,
- testInfo,
- logger,
- exception,
- "multi target preparer teardown");
-
- int deviceIndex = 0;
- for (String deviceName : context.getDeviceConfigNames()) {
- ITestDevice device = context.getDevice(deviceName);
- device.clearLastConnectedWifiNetwork();
-
- List<ITargetPreparer> targetPreparersToRun =
- getTargetPreparersToRun(config, deviceName);
- Throwable firstLocalThrowable =
- runPreparersTearDown(
+ try {
+ List<IMultiTargetPreparer> multiPreparers = config.getMultiTargetPreparers();
+ deferredThrowable =
+ runMultiTargetPreparersTearDown(
+ multiPreparers,
testInfo,
- device,
- deviceName,
- deviceIndex,
logger,
exception,
- targetPreparersToRun,
- mTrackTargetPreparers);
- if (deferredThrowable == null) {
- deferredThrowable = firstLocalThrowable;
+ "multi target preparer teardown");
+
+ int deviceIndex = 0;
+ for (String deviceName : context.getDeviceConfigNames()) {
+ ITestDevice device = context.getDevice(deviceName);
+ device.clearLastConnectedWifiNetwork();
+
+ List<ITargetPreparer> targetPreparersToRun =
+ getTargetPreparersToRun(config, deviceName);
+ Throwable firstLocalThrowable =
+ runPreparersTearDown(
+ testInfo,
+ device,
+ deviceName,
+ deviceIndex,
+ logger,
+ exception,
+ targetPreparersToRun,
+ mTrackTargetPreparers);
+ if (deferredThrowable == null) {
+ deferredThrowable = firstLocalThrowable;
+ }
+
+ List<ITargetPreparer> labPreparersToRun = getLabPreparersToRun(config, deviceName);
+ Throwable secondLocalThrowable =
+ runPreparersTearDown(
+ testInfo,
+ device,
+ deviceName,
+ deviceIndex,
+ logger,
+ exception,
+ labPreparersToRun,
+ mTrackLabPreparers);
+ if (deferredThrowable == null) {
+ deferredThrowable = secondLocalThrowable;
+ }
+
+ deviceIndex++;
}
- List<ITargetPreparer> labPreparersToRun = getLabPreparersToRun(config, deviceName);
- Throwable secondLocalThrowable =
- runPreparersTearDown(
+ // Extra tear down step for the device
+ if (exception == null) {
+ exception = deferredThrowable;
+ }
+ runDevicePostInvocationTearDown(context, config, exception);
+
+ // After all, run the multi_pre_target_preparer tearDown.
+ List<IMultiTargetPreparer> multiPrePreparers = config.getMultiPreTargetPreparers();
+ Throwable preTargetTearDownException =
+ runMultiTargetPreparersTearDown(
+ multiPrePreparers,
testInfo,
- device,
- deviceName,
- deviceIndex,
logger,
exception,
- labPreparersToRun,
- mTrackLabPreparers);
+ "multi pre target preparer teardown");
if (deferredThrowable == null) {
- deferredThrowable = secondLocalThrowable;
+ deferredThrowable = preTargetTearDownException;
}
- deviceIndex++;
+ // Collect adb logs.
+ logHostAdb(config, logger);
+ } finally {
+ long end = System.currentTimeMillis();
+ InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.TEARDOWN_END, end);
+ InvocationMetricLogger.addInvocationPairMetrics(
+ InvocationMetricKey.TEARDOWN_PAIR, start, end);
}
- // Extra tear down step for the device
- if (exception == null) {
- exception = deferredThrowable;
- }
- runDevicePostInvocationTearDown(context, config, exception);
-
- // After all, run the multi_pre_target_preparer tearDown.
- List<IMultiTargetPreparer> multiPrePreparers = config.getMultiPreTargetPreparers();
- Throwable preTargetTearDownException =
- runMultiTargetPreparersTearDown(
- multiPrePreparers,
- testInfo,
- logger,
- exception,
- "multi pre target preparer teardown");
- if (deferredThrowable == null) {
- deferredThrowable = preTargetTearDownException;
- }
-
- // Collect adb logs.
- logHostAdb(config, logger);
-
- long end = System.currentTimeMillis();
- InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.TEARDOWN_END, end);
- InvocationMetricLogger.addInvocationPairMetrics(
- InvocationMetricKey.TEARDOWN_PAIR, start, end);
-
if (deferredThrowable != null) {
throw deferredThrowable;
}
@@ -827,118 +889,123 @@
Runtime.getRuntime().addShutdownHook(reporterThread);
TestInvocation.printStageDelimiter(Stage.TEST, false);
long start = System.currentTimeMillis();
- try {
+ try (CloseableTraceScope ignored =
+ new CloseableTraceScope(InvocationMetricKey.test_execution.name())) {
GetPreviousPassedHelper previousPassHelper = new GetPreviousPassedHelper();
// Add new exclude filters to global filters
Set<String> previousPassedFilters = previousPassHelper.getPreviousPassedFilters(config);
// TODO: Ensure global filters are cloned for local sharding
config.getGlobalFilters().addPreviousPassedTests(previousPassedFilters);
for (IRemoteTest test : config.getTests()) {
- TfObjectTracker.countWithParents(test.getClass());
- // For compatibility of those receivers, they are assumed to be single device alloc.
- if (test instanceof IDeviceTest) {
- ((IDeviceTest) test).setDevice(info.getDevice());
- }
- if (test instanceof IBuildReceiver) {
- ((IBuildReceiver) test).setBuild(info.getBuildInfo());
- }
- if (test instanceof ISystemStatusCheckerReceiver) {
- ((ISystemStatusCheckerReceiver) test)
- .setSystemStatusChecker(config.getSystemStatusCheckers());
- }
- if (test instanceof IInvocationContextReceiver) {
- ((IInvocationContextReceiver) test).setInvocationContext(info.getContext());
- }
+ try (CloseableTraceScope remoteTest =
+ new CloseableTraceScope(test.getClass().getSimpleName())) {
+ TfObjectTracker.countWithParents(test.getClass());
+ // For compatibility of those receivers, they are assumed to be single device
+ // alloc.
+ if (test instanceof IDeviceTest) {
+ ((IDeviceTest) test).setDevice(info.getDevice());
+ }
+ if (test instanceof IBuildReceiver) {
+ ((IBuildReceiver) test).setBuild(info.getBuildInfo());
+ }
+ if (test instanceof ISystemStatusCheckerReceiver) {
+ ((ISystemStatusCheckerReceiver) test)
+ .setSystemStatusChecker(config.getSystemStatusCheckers());
+ }
+ if (test instanceof IInvocationContextReceiver) {
+ ((IInvocationContextReceiver) test).setInvocationContext(info.getContext());
+ }
- updateAutoCollectors(config);
+ updateAutoCollectors(config);
- IRetryDecision decision = config.getRetryDecision();
- // Apply the filters
- if (test instanceof ITestFilterReceiver) {
- config.getGlobalFilters().applyFiltersToTest((ITestFilterReceiver) test);
- } else if (test instanceof BaseTestSuite) {
- config.getGlobalFilters().applyFiltersToTest((BaseTestSuite) test);
- }
- // Handle the no-retry use case
- if (!decision.isAutoRetryEnabled()
- || RetryStrategy.NO_RETRY.equals(decision.getRetryStrategy())
- || test instanceof ITestSuite
- // TODO: Handle auto-retry in local-sharding for non-suite
- || test instanceof TestsPoolPoller
- // If test doesn't support auto-retry
- || (!(test instanceof ITestFilterReceiver)
- && !(test instanceof IAutoRetriableTest))) {
+ IRetryDecision decision = config.getRetryDecision();
+ // Apply the filters
+ if (test instanceof ITestFilterReceiver) {
+ config.getGlobalFilters().applyFiltersToTest((ITestFilterReceiver) test);
+ } else if (test instanceof BaseTestSuite) {
+ config.getGlobalFilters().applyFiltersToTest((BaseTestSuite) test);
+ }
+ // Handle the no-retry use case
+ if (!decision.isAutoRetryEnabled()
+ || RetryStrategy.NO_RETRY.equals(decision.getRetryStrategy())
+ || test instanceof ITestSuite
+ // TODO: Handle auto-retry in local-sharding for non-suite
+ || test instanceof TestsPoolPoller
+ // If test doesn't support auto-retry
+ || (!(test instanceof ITestFilterReceiver)
+ && !(test instanceof IAutoRetriableTest))) {
+ try {
+ runTest(config, info, listener, test);
+ } finally {
+ CurrentInvocation.setRunIsolation(IsolationGrade.NOT_ISOLATED);
+ CurrentInvocation.setModuleIsolation(IsolationGrade.NOT_ISOLATED);
+ }
+ remainingTests.remove(test);
+ continue;
+ }
+ CLog.d("Using RetryLogSaverResultForwarder to forward results.");
+ ModuleListener mainGranularRunListener = new ModuleListener(null);
+ RetryLogSaverResultForwarder runListener =
+ initializeListeners(config, listener, mainGranularRunListener);
+ mainGranularRunListener.setAttemptIsolation(
+ CurrentInvocation.runCurrentIsolation());
try {
- runTest(config, info, listener, test);
+ runTest(config, info, runListener, test);
} finally {
CurrentInvocation.setRunIsolation(IsolationGrade.NOT_ISOLATED);
CurrentInvocation.setModuleIsolation(IsolationGrade.NOT_ISOLATED);
}
remainingTests.remove(test);
- continue;
- }
- CLog.d("Using RetryLogSaverResultForwarder to forward results.");
- ModuleListener mainGranularRunListener = new ModuleListener(null);
- RetryLogSaverResultForwarder runListener =
- initializeListeners(config, listener, mainGranularRunListener);
- mainGranularRunListener.setAttemptIsolation(
- CurrentInvocation.runCurrentIsolation());
- try {
- runTest(config, info, runListener, test);
- } finally {
- CurrentInvocation.setRunIsolation(IsolationGrade.NOT_ISOLATED);
- CurrentInvocation.setModuleIsolation(IsolationGrade.NOT_ISOLATED);
- }
- remainingTests.remove(test);
- runListener.incrementAttempt();
+ runListener.incrementAttempt();
- // Avoid entering the loop if no retry to be done.
- if (!decision.shouldRetry(
- test, 0, mainGranularRunListener.getTestRunForAttempts(0))) {
- continue;
- }
- // Avoid rechecking the shouldRetry below the first time as it could retrigger
- // reboot.
- boolean firstCheck = true;
- long startTime = System.currentTimeMillis();
- try {
- PrettyPrintDelimiter.printStageDelimiter("Starting auto-retry");
- for (int attemptNumber = 1;
- attemptNumber < decision.getMaxRetryCount();
- attemptNumber++) {
- if (!firstCheck) {
- boolean retry =
- decision.shouldRetry(
- test,
- attemptNumber - 1,
- mainGranularRunListener.getTestRunForAttempts(
- attemptNumber - 1));
- if (!retry) {
- continue;
- }
- }
- firstCheck = false;
- CLog.d("auto-retry attempt number '%s'", attemptNumber);
- mainGranularRunListener.setAttemptIsolation(
- CurrentInvocation.runCurrentIsolation());
- try {
- // Run the tests again
- runTest(config, info, runListener, test);
- } finally {
- CurrentInvocation.setRunIsolation(IsolationGrade.NOT_ISOLATED);
- CurrentInvocation.setModuleIsolation(IsolationGrade.NOT_ISOLATED);
- }
- runListener.incrementAttempt();
+ // Avoid entering the loop if no retry to be done.
+ if (!decision.shouldRetry(
+ test, 0, mainGranularRunListener.getTestRunForAttempts(0))) {
+ continue;
}
- // Feed the last attempt if we reached here.
- decision.addLastAttempt(
- mainGranularRunListener.getTestRunForAttempts(
- decision.getMaxRetryCount() - 1));
- } finally {
- RetryStatistics retryStats = decision.getRetryStatistics();
- // Track how long we spend in retry
- retryStats.mRetryTime = System.currentTimeMillis() - startTime;
- addRetryTime(retryStats.mRetryTime);
+ // Avoid rechecking the shouldRetry below the first time as it could retrigger
+ // reboot.
+ boolean firstCheck = true;
+ long startTime = System.currentTimeMillis();
+ try {
+ PrettyPrintDelimiter.printStageDelimiter("Starting auto-retry");
+ for (int attemptNumber = 1;
+ attemptNumber < decision.getMaxRetryCount();
+ attemptNumber++) {
+ if (!firstCheck) {
+ boolean retry =
+ decision.shouldRetry(
+ test,
+ attemptNumber - 1,
+ mainGranularRunListener.getTestRunForAttempts(
+ attemptNumber - 1));
+ if (!retry) {
+ continue;
+ }
+ }
+ firstCheck = false;
+ CLog.d("auto-retry attempt number '%s'", attemptNumber);
+ mainGranularRunListener.setAttemptIsolation(
+ CurrentInvocation.runCurrentIsolation());
+ try {
+ // Run the tests again
+ runTest(config, info, runListener, test);
+ } finally {
+ CurrentInvocation.setRunIsolation(IsolationGrade.NOT_ISOLATED);
+ CurrentInvocation.setModuleIsolation(IsolationGrade.NOT_ISOLATED);
+ }
+ runListener.incrementAttempt();
+ }
+ // Feed the last attempt if we reached here.
+ decision.addLastAttempt(
+ mainGranularRunListener.getTestRunForAttempts(
+ decision.getMaxRetryCount() - 1));
+ } finally {
+ RetryStatistics retryStats = decision.getRetryStatistics();
+ // Track how long we spend in retry
+ retryStats.mRetryTime = System.currentTimeMillis() - startTime;
+ addRetryTime(retryStats.mRetryTime);
+ }
}
}
} finally {
@@ -1188,6 +1255,15 @@
}
}
+ private void copyRemoteFiles(ICommandOptions options, IBuildInfo info) {
+ for (String remoteFile : options.getRemoteFiles()) {
+ info.setFile(
+ IBuildInfo.REMOTE_FILE_PREFIX,
+ new File(remoteFile),
+ IBuildInfo.REMOTE_FILE_VERSION);
+ }
+ }
+
/** Convert the legacy *-on-failure options to the new auto-collect. */
private void updateAutoCollectors(IConfiguration config) {
if (config.getCommandOptions().captureScreenshotOnFailure()) {
diff --git a/src/com/android/tradefed/invoker/RemoteInvocationExecution.java b/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
index 555667e..0706df9 100644
--- a/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
+++ b/src/com/android/tradefed/invoker/RemoteInvocationExecution.java
@@ -258,7 +258,7 @@
globalConfig =
GlobalConfiguration.getInstance()
.cloneConfigWithFilter(
- new HashSet<>(), fileTransformer, allowListConfigs);
+ new HashSet<>(), fileTransformer, true, allowListConfigs);
} catch (IOException e) {
listener.invocationFailed(createInvocationFailure(e, FailureStatus.INFRA_FAILURE));
return;
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index b918149..ee0689a 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -60,6 +60,7 @@
import com.android.tradefed.invoker.sandbox.SandboxedInvocationExecution;
import com.android.tradefed.invoker.shard.LastShardDetector;
import com.android.tradefed.invoker.shard.ShardHelper;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.BaseLeveledLogOutput;
import com.android.tradefed.log.ILeveledLogOutput;
import com.android.tradefed.log.ILogRegistry;
@@ -187,7 +188,13 @@
private String mStopCause = null;
private ErrorIdentifier mStopErrorId = null;
private Long mStopRequestTime = null;
+ private Long mSoftStopRequestTime = null;
+ private boolean mShutdownBeforeTest = false;
private boolean mTestStarted = false;
+ private boolean mTestDone = false;
+ private boolean mTestsNotRan = false;
+ private boolean mForcedStopRequestedAfterTest = false;
+
private boolean mInvocationFailed = false;
private boolean mDelegatedInvocation = false;
private List<IScheduledInvocationListener> mSchedulerListeners = new ArrayList<>();
@@ -329,6 +336,8 @@
throw t;
}
} finally {
+ mTestDone = true;
+ long bugreportStartTime = System.currentTimeMillis();
// Only capture logcat for TEST if we started the test phase.
if (mTestStarted) {
for (ITestDevice device : context.getDevices()) {
@@ -347,51 +356,64 @@
}
}
if (bugreportName != null) {
- if (context.getDevices().size() == 1 || badDevice != null) {
- ITestDevice collectBugreport = badDevice;
- if (collectBugreport == null) {
- collectBugreport = context.getDevices().get(0);
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope(InvocationMetricKey.bugreport.name())) {
+ 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 =
+ () -> {
+ CLog.d(
+ "Start taking bugreport on '%s'",
+ device.getSerialNumber());
+ takeBugreport(device, listener, reportName);
+ return true;
+ };
+ callableTasks.add(callableTask);
+ }
+ // Capture the bugreports best effort, ignore the results.
+ executor.invokeAll(callableTasks, 5, TimeUnit.MINUTES);
}
- // 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 =
- () -> {
- CLog.d("Start taking bugreport on '%s'",
- device.getSerialNumber());
- takeBugreport(device, listener, reportName);
- return true;
- };
- callableTasks.add(callableTask);
- }
- // Capture the bugreports best effort, ignore the results.
- executor.invokeAll(callableTasks, 5, TimeUnit.MINUTES);
+ }
+ reportRecoveryLogs(context.getDevices(), listener);
+ }
+ }
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope(InvocationMetricKey.check_device_availability.name())) {
+ // Save the device executeShellCommand logs
+ logExecuteShellCommand(context.getDevices(), listener);
+ if (exception == null) {
+ exception = mUnavailableMonitor.getUnavailableException();
+ if (exception != null) {
+ CLog.e("Found a test level only device unavailable exception:");
+ CLog.e(exception);
}
}
- reportRecoveryLogs(context.getDevices(), listener);
- }
- // Save the device executeShellCommand logs
- logExecuteShellCommand(context.getDevices(), listener);
- if (exception == null) {
- exception = mUnavailableMonitor.getUnavailableException();
- if (exception != null) {
- CLog.e("Found a test level only device unavailable exception:");
- CLog.e(exception);
+ if (exception == null) {
+ CLog.d("Checking that devices are online.");
+ exception = checkDevicesAvailable(context.getDevices(), listener);
+ } else {
+ CLog.d("Skip online check as an exception was already reported: %s", exception);
}
+ // Report bugreport and various check as part of teardown
+ InvocationMetricLogger.addInvocationPairMetrics(
+ InvocationMetricKey.TEARDOWN_PAIR,
+ bugreportStartTime,
+ System.currentTimeMillis());
+ mStatus = "tearing down";
}
- if (exception == null) {
- CLog.d("Checking that devices are online.");
- exception = checkDevicesAvailable(context.getDevices(), listener);
- } else {
- CLog.d("Skip online check as an exception was already reported: %s", exception);
- }
- mStatus = "tearing down";
- try {
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope(InvocationMetricKey.test_teardown.name())) {
invocationPath.doTeardown(testInfo, config, listener, exception);
} catch (Throwable e) {
tearDownException = e;
@@ -405,54 +427,101 @@
listener);
}
}
- // Capture last logcat before releasing the device.
- for (ITestDevice device : context.getDevices()) {
- invocationPath.reportLogs(device, listener, Stage.TEARDOWN);
- }
- mStatus = "done running tests";
- CurrentInvocation.setActionInProgress(ActionInProgress.FREE_RESOURCES);
-
- // Ensure we always deregister the logger
- for (String deviceName : context.getDeviceConfigNames()) {
- if (!(context.getDevice(deviceName).getIDevice() instanceof StubDevice)) {
- context.getDevice(deviceName).stopLogcat();
- CLog.i(
- "Done stopping logcat for %s",
- context.getDevice(deviceName).getSerialNumber());
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope(InvocationMetricKey.log_and_release_device.name())) {
+ // Capture last logcat before releasing the device.
+ for (ITestDevice device : context.getDevices()) {
+ invocationPath.reportLogs(device, listener, Stage.TEARDOWN);
}
- }
+ mStatus = "done running tests";
+ CurrentInvocation.setActionInProgress(ActionInProgress.FREE_RESOURCES);
- Map<ITestDevice, FreeDeviceState> devicesStates =
- handleAndLogReleaseState(context, exception, tearDownException);
- if (config.getCommandOptions().earlyDeviceRelease()) {
- context.markReleasedEarly();
- for (IScheduledInvocationListener scheduleListener : mSchedulerListeners) {
- scheduleListener.releaseDevices(context, devicesStates);
+ // Ensure we always deregister the logger
+ for (String deviceName : context.getDeviceConfigNames()) {
+ if (!(context.getDevice(deviceName).getIDevice() instanceof StubDevice)) {
+ context.getDevice(deviceName).stopLogcat();
+ CLog.i(
+ "Done stopping logcat for %s",
+ context.getDevice(deviceName).getSerialNumber());
+ }
}
+
+ Map<ITestDevice, FreeDeviceState> devicesStates =
+ handleAndLogReleaseState(context, exception, tearDownException);
+ if (config.getCommandOptions().earlyDeviceRelease()) {
+ context.markReleasedEarly();
+ for (IScheduledInvocationListener scheduleListener : mSchedulerListeners) {
+ scheduleListener.releaseDevices(context, devicesStates);
+ }
+ }
+ // Log count of allocated devices for test accounting
+ addInvocationMetric(
+ InvocationMetricKey.DEVICE_COUNT, context.getNumDevicesAllocated());
+ // Track the timestamp when we are done with devices
+ addInvocationMetric(
+ InvocationMetricKey.DEVICE_DONE_TIMESTAMP, System.currentTimeMillis());
}
- // Log count of allocated devices for test accounting
- addInvocationMetric(InvocationMetricKey.DEVICE_COUNT, context.getNumDevicesAllocated());
- // Track the timestamp when we are done with devices
- addInvocationMetric(
- InvocationMetricKey.DEVICE_DONE_TIMESTAMP, System.currentTimeMillis());
- try {
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope(InvocationMetricKey.test_cleanup.name())) {
// Clean up host.
invocationPath.doCleanUp(context, config, exception);
- if (mStopCause != null) {
- String message =
- String.format(
- "Invocation was interrupted due to: %s, results will be "
- + "affected.",
- mStopCause);
- if (mStopErrorId == null) {
- mStopErrorId = InfraErrorIdentifier.INVOCATION_CANCELLED;
+ if (mSoftStopRequestTime != null) { // soft stop occurred
+ long latency = System.currentTimeMillis() - mSoftStopRequestTime;
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.SHUTDOWN_LATENCY, latency);
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.SHUTDOWN_BEFORE_TEST,
+ Boolean.toString(mShutdownBeforeTest));
+ if (mTestsNotRan) {
+ String message =
+ String.format("Notified of soft shut down. Did not run tests");
+ FailureDescription failure =
+ FailureDescription.create(message)
+ .setErrorIdentifier(
+ InfraErrorIdentifier
+ .TRADEFED_SKIPPED_TESTS_DURING_SHUTDOWN)
+ .setCause(
+ new HarnessRuntimeException(
+ message,
+ InfraErrorIdentifier
+ .TRADEFED_SKIPPED_TESTS_DURING_SHUTDOWN));
+ // report failure so that command can be un-leased
+ reportFailure(failure, listener);
}
- FailureDescription failure =
- FailureDescription.create(message)
- .setErrorIdentifier(mStopErrorId)
- .setCause(new HarnessRuntimeException(message, mStopErrorId));
- reportFailure(failure, listener);
- PrettyPrintDelimiter.printStageDelimiter(message);
+ }
+ if (mStopCause != null) { // Forced stop occurred
+ if (mForcedStopRequestedAfterTest) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.SHUTDOWN_AFTER_TEST, "true");
+ CLog.d(
+ "Forced shutdown occurred after test phase execution. It shouldn't"
+ + " have impact on test results.");
+ } else {
+ String message =
+ String.format(
+ "Invocation was interrupted due to: %s%s",
+ mStopCause,
+ mTestsNotRan
+ ? ". Tests were not run."
+ : ", results will be affected");
+ if (mStopErrorId == null) {
+ mStopErrorId = InfraErrorIdentifier.INVOCATION_CANCELLED;
+ }
+ // if invocation is stopped and tests were not run, report invocation
+ // failure with correct error identifier so that command can be
+ // un-leased
+ if (mTestsNotRan) {
+ mStopErrorId =
+ InfraErrorIdentifier.TRADEFED_SKIPPED_TESTS_DURING_SHUTDOWN;
+ }
+ FailureDescription failure =
+ FailureDescription.create(message)
+ .setErrorIdentifier(mStopErrorId)
+ .setCause(
+ new HarnessRuntimeException(message, mStopErrorId));
+ reportFailure(failure, listener);
+ PrettyPrintDelimiter.printStageDelimiter(message);
+ }
if (mStopRequestTime != null) {
// This is not 100% perfect since result reporting can still run a bit
// longer, but this is our last opportunity to report it.
@@ -502,6 +571,15 @@
logDeviceBatteryLevel(testInfo.getContext(), "initial -> setup");
CurrentInvocation.setActionInProgress(ActionInProgress.SETUP);
invocationPath.doSetup(testInfo, config, listener);
+ // Don't run tests if notified of soft/forced shutdown
+ if (mSoftStopRequestTime != null || mStopRequestTime != null) {
+ // Throw an exception so that it can be reported as an invocation failure
+ // and command can be un-leased
+ mTestsNotRan = true;
+ throw new RunInterruptedException(
+ "Notified of shut down. Will not run tests",
+ InfraErrorIdentifier.TRADEFED_SKIPPED_TESTS_DURING_SHUTDOWN);
+ }
logDeviceBatteryLevel(testInfo.getContext(), "setup -> test");
mTestStarted = true;
CurrentInvocation.setActionInProgress(ActionInProgress.TEST);
@@ -853,144 +931,156 @@
IRescheduler rescheduler,
ITestInvocationListener... extraListeners)
throws DeviceNotAvailableException, Throwable {
- if (!config.getInopOptions().isEmpty()) {
- context.addInvocationAttribute(
- "inop-options", Joiner.on(",").join(config.getInopOptions()));
- }
- // Carry the reference of the server so it can be used within the same process.
- if (config.getConfigurationDescription().getAllMetaData().getUniqueMap()
- .containsKey(TradefedFeatureServer.SERVER_REFERENCE)) {
- InvocationMetricLogger.addInvocationMetrics(
- InvocationMetricKey.SERVER_REFERENCE,
- config.getConfigurationDescription().getAllMetaData().getUniqueMap()
- .get(TradefedFeatureServer.SERVER_REFERENCE));
- }
- // Only log invocation_start in parent
- boolean isSuprocess = isSubprocess(config);
- if (!isSuprocess) {
- InvocationMetricLogger.addInvocationMetrics(
- InvocationMetricKey.INVOCATION_START, System.currentTimeMillis());
- } else {
- CLog.d("Fetching options from parent.");
- // Get options from the parent process
- try (OptionFetcher fetchOtpions = new OptionFetcher()) {
- fetchOtpions.fetchParentOptions(config);
- }
- }
- // Handle the automated reporting
- applyAutomatedReporters(config);
-
- if (config.getCommandOptions().delegatedEarlyDeviceRelease()
- && System.getenv(DelegatedInvocationExecution.DELEGATED_MODE_VAR) != null) {
- // If in a subprocess, add the early device release feature as a listener.
- mSchedulerListeners.add(new DeviceReleaseReporter());
- }
-
- for (ITestInvocationListener listener : extraListeners) {
- if (listener instanceof IScheduledInvocationListener) {
- mSchedulerListeners.add((IScheduledInvocationListener) listener);
- }
- }
- // Create the TestInformation for the invocation
- // TODO: Use invocation-id in the workfolder name
- Object sharedInfoObject =
- config.getConfigurationObject(ShardHelper.SHARED_TEST_INFORMATION);
- TestInformation sharedTestInfo = null;
- TestInformation info = null;
- if (sharedInfoObject != null) {
- sharedTestInfo = (TestInformation) sharedInfoObject;
- // During sharding we share everything except the invocation context
- info = TestInformation.createModuleTestInfo(sharedTestInfo, context);
- }
- if (info == null) {
- File mWorkFolder = FileUtil.createTempDir("tf-workfolder");
- info =
- TestInformation.newBuilder()
- .setInvocationContext(context)
- .setDependenciesFolder(mWorkFolder)
- .build();
- }
- // Register the test info to the configuration to be usable.
- config.setConfigurationObject(TradefedFeatureServer.TEST_INFORMATION_OBJECT, info);
- CurrentInvocation.addInvocationInfo(InvocationInfo.WORK_FOLDER, info.dependenciesFolder());
-
- CleanUpInvocationFiles cleanUpThread = new CleanUpInvocationFiles(info, config);
- Runtime.getRuntime().addShutdownHook(cleanUpThread);
- registerExecutionFiles(info.executionFiles());
-
- List<ITestInvocationListener> allListeners =
- new ArrayList<>(config.getTestInvocationListeners().size() + extraListeners.length);
- // If it's not a subprocess, report the passed tests.
- ReportPassedTests reportPass = null;
- if (config.getConfigurationObject(TradefedDelegator.DELEGATE_OBJECT) == null
- && config.getCommandOptions().reportPassedTests()
- && !isSubprocess(config)) {
- reportPass = new ReportPassedTests();
- reportPass.setConfiguration(config);
- allListeners.add(reportPass);
- }
- allListeners.addAll(config.getTestInvocationListeners());
- allListeners.addAll(Arrays.asList(extraListeners));
- allListeners.add(mUnavailableMonitor);
+ RunMode mode = RunMode.REGULAR;
ITestInvocationListener listener = null;
-
- // Auto retry feature
- IRetryDecision decision = config.getRetryDecision();
+ TestInformation info = null;
ResultAggregator aggregator = null;
- decision.setInvocationContext(context);
- if (decision instanceof ITestInformationReceiver) {
- ((ITestInformationReceiver) decision).setTestInformation(info);
- }
- // We don't need the aggregator in the subprocess because the parent will take care of it.
- if (!config.getCommandOptions()
- .getInvocationData()
- .containsKey(SubprocessTfLauncher.SUBPROCESS_TAG_NAME)) {
- if (decision.isAutoRetryEnabled()
- && decision.getMaxRetryCount() > 1
- && !RetryStrategy.NO_RETRY.equals(decision.getRetryStrategy())) {
- CLog.d(
- "Auto-retry enabled, using the ResultAggregator to handle multiple"
- + " retries.");
- aggregator = new ResultAggregator(allListeners, decision.getRetryStrategy());
- aggregator.setUpdatedReporting(decision.useUpdatedReporting());
- allListeners = Arrays.asList(aggregator);
- } else {
- mEventsLogger = new EventsLoggerListener("all-events");
- allListeners.add(mEventsLogger);
+ CleanUpInvocationFiles cleanUpThread = null;
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope(InvocationMetricKey.invocation_warm_up.name())) {
+ if (!config.getInopOptions().isEmpty()) {
+ context.addInvocationAttribute(
+ "inop-options", Joiner.on(",").join(config.getInopOptions()));
}
- }
-
- if (!config.getPostProcessors().isEmpty()) {
- ITestInvocationListener forwarder = new ResultAndLogForwarder(allListeners);
- // Post-processors are the first layer around the final reporters.
- for (IPostProcessor postProcessor : config.getPostProcessors()) {
- if (postProcessor.isDisabled()) {
- CLog.d("%s has been disabled. skipping.", postProcessor);
- } else {
- forwarder = postProcessor.init(forwarder);
+ // Carry the reference of the server so it can be used within the same process.
+ if (config.getConfigurationDescription()
+ .getAllMetaData()
+ .getUniqueMap()
+ .containsKey(TradefedFeatureServer.SERVER_REFERENCE)) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.SERVER_REFERENCE,
+ config.getConfigurationDescription()
+ .getAllMetaData()
+ .getUniqueMap()
+ .get(TradefedFeatureServer.SERVER_REFERENCE));
+ }
+ // Only log invocation_start in parent
+ boolean isSuprocess = isSubprocess(config);
+ if (!isSuprocess) {
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.INVOCATION_START, System.currentTimeMillis());
+ } else {
+ CLog.d("Fetching options from parent.");
+ // Get options from the parent process
+ try (OptionFetcher fetchOtpions = new OptionFetcher()) {
+ fetchOtpions.fetchParentOptions(config);
}
}
- listener = new LogSaverResultForwarder(config.getLogSaver(), Arrays.asList(forwarder));
- } else {
- listener = new LogSaverResultForwarder(config.getLogSaver(), allListeners);
- }
- if (reportPass != null) {
- reportPass.setLogger(listener);
- }
+ // Handle the automated reporting
+ applyAutomatedReporters(config);
- RunMode mode = RunMode.REGULAR;
- if (config.getConfigurationDescription().shouldUseSandbox()) {
- mode = RunMode.SANDBOX;
- }
- if (config.getCommandOptions().shouldUseSandboxing()) {
- mode = RunMode.PARENT_SANDBOX;
- }
- if (context.getDevices().get(0) instanceof ManagedRemoteDevice) {
- mode = RunMode.REMOTE_INVOCATION;
- }
- if (config.getConfigurationObject(TradefedDelegator.DELEGATE_OBJECT) != null) {
- mDelegatedInvocation = true;
- mode = RunMode.DELEGATED_INVOCATION;
+ if (config.getCommandOptions().delegatedEarlyDeviceRelease()
+ && System.getenv(DelegatedInvocationExecution.DELEGATED_MODE_VAR) != null) {
+ // If in a subprocess, add the early device release feature as a listener.
+ mSchedulerListeners.add(new DeviceReleaseReporter());
+ }
+
+ for (ITestInvocationListener extra : extraListeners) {
+ if (extra instanceof IScheduledInvocationListener) {
+ mSchedulerListeners.add((IScheduledInvocationListener) extra);
+ }
+ }
+ // Create the TestInformation for the invocation
+ // TODO: Use invocation-id in the workfolder name
+ Object sharedInfoObject =
+ config.getConfigurationObject(ShardHelper.SHARED_TEST_INFORMATION);
+ TestInformation sharedTestInfo = null;
+ if (sharedInfoObject != null) {
+ sharedTestInfo = (TestInformation) sharedInfoObject;
+ // During sharding we share everything except the invocation context
+ info = TestInformation.createModuleTestInfo(sharedTestInfo, context);
+ }
+ if (info == null) {
+ File mWorkFolder = FileUtil.createTempDir("tf-workfolder");
+ info =
+ TestInformation.newBuilder()
+ .setInvocationContext(context)
+ .setDependenciesFolder(mWorkFolder)
+ .build();
+ }
+ // Register the test info to the configuration to be usable.
+ config.setConfigurationObject(TradefedFeatureServer.TEST_INFORMATION_OBJECT, info);
+ CurrentInvocation.addInvocationInfo(
+ InvocationInfo.WORK_FOLDER, info.dependenciesFolder());
+
+ cleanUpThread = new CleanUpInvocationFiles(info, config);
+ Runtime.getRuntime().addShutdownHook(cleanUpThread);
+ registerExecutionFiles(info.executionFiles());
+
+ List<ITestInvocationListener> allListeners =
+ new ArrayList<>(
+ config.getTestInvocationListeners().size() + extraListeners.length);
+ // If it's not a subprocess, report the passed tests.
+ ReportPassedTests reportPass = null;
+ if (config.getConfigurationObject(TradefedDelegator.DELEGATE_OBJECT) == null
+ && config.getCommandOptions().reportPassedTests()
+ && !isSubprocess(config)) {
+ reportPass = new ReportPassedTests();
+ reportPass.setConfiguration(config);
+ allListeners.add(reportPass);
+ }
+ allListeners.addAll(config.getTestInvocationListeners());
+ allListeners.addAll(Arrays.asList(extraListeners));
+ allListeners.add(mUnavailableMonitor);
+
+ // Auto retry feature
+ IRetryDecision decision = config.getRetryDecision();
+ decision.setInvocationContext(context);
+ if (decision instanceof ITestInformationReceiver) {
+ ((ITestInformationReceiver) decision).setTestInformation(info);
+ }
+ // We don't need the aggregator in the subprocess because the parent will take care of
+ // it.
+ if (!config.getCommandOptions()
+ .getInvocationData()
+ .containsKey(SubprocessTfLauncher.SUBPROCESS_TAG_NAME)) {
+ if (decision.isAutoRetryEnabled()
+ && decision.getMaxRetryCount() > 1
+ && !RetryStrategy.NO_RETRY.equals(decision.getRetryStrategy())) {
+ CLog.d(
+ "Auto-retry enabled, using the ResultAggregator to handle multiple"
+ + " retries.");
+ aggregator = new ResultAggregator(allListeners, decision.getRetryStrategy());
+ aggregator.setUpdatedReporting(decision.useUpdatedReporting());
+ allListeners = Arrays.asList(aggregator);
+ } else {
+ mEventsLogger = new EventsLoggerListener("all-events");
+ allListeners.add(mEventsLogger);
+ }
+ }
+
+ if (!config.getPostProcessors().isEmpty()) {
+ ITestInvocationListener forwarder = new ResultAndLogForwarder(allListeners);
+ // Post-processors are the first layer around the final reporters.
+ for (IPostProcessor postProcessor : config.getPostProcessors()) {
+ if (postProcessor.isDisabled()) {
+ CLog.d("%s has been disabled. skipping.", postProcessor);
+ } else {
+ forwarder = postProcessor.init(forwarder);
+ }
+ }
+ listener =
+ new LogSaverResultForwarder(config.getLogSaver(), Arrays.asList(forwarder));
+ } else {
+ listener = new LogSaverResultForwarder(config.getLogSaver(), allListeners);
+ }
+ if (reportPass != null) {
+ reportPass.setLogger(listener);
+ }
+
+ if (config.getConfigurationDescription().shouldUseSandbox()) {
+ mode = RunMode.SANDBOX;
+ }
+ if (config.getCommandOptions().shouldUseSandboxing()) {
+ mode = RunMode.PARENT_SANDBOX;
+ }
+ if (context.getDevices().get(0) instanceof ManagedRemoteDevice) {
+ mode = RunMode.REMOTE_INVOCATION;
+ }
+ if (config.getConfigurationObject(TradefedDelegator.DELEGATE_OBJECT) != null) {
+ mDelegatedInvocation = true;
+ mode = RunMode.DELEGATED_INVOCATION;
+ }
}
IInvocationExecution invocationPath = createInvocationExec(mode);
updateInvocationContext(context, config);
@@ -1006,7 +1096,8 @@
mStatus = "resolving dynamic options";
long startDynamic = System.currentTimeMillis();
boolean resolverSuccess = false;
- try {
+ try (CloseableTraceScope ignored =
+ new CloseableTraceScope(InvocationMetricKey.dynamic_download.name())) {
resolverSuccess =
invokeRemoteDynamic(context, config, listener, invocationPath, mode);
} finally {
@@ -1029,7 +1120,8 @@
InvocationMetricLogger.addInvocationMetrics(
InvocationMetricKey.FETCH_BUILD_START, start);
boolean providerSuccess = false;
- try {
+ try (CloseableTraceScope ignored =
+ new CloseableTraceScope(InvocationMetricKey.fetch_artifact.name())) {
providerSuccess =
invokeFetchBuild(info, config, rescheduler, listener, invocationPath);
} finally {
@@ -1046,7 +1138,8 @@
if (!providerSuccess) {
return;
}
- try {
+ try (CloseableTraceScope ignore =
+ new CloseableTraceScope(InvocationMetricKey.start_logcat.name())) {
for (String deviceName : context.getDeviceConfigNames()) {
context.getDevice(deviceName).clearLastConnectedWifiNetwork();
// TODO: Report invocation error if setOptions() fails
@@ -1088,10 +1181,12 @@
// we call the device setup early to meet all the requirements.
boolean startInvocationCalled = false;
if (shardCount != null && shardIndex != null) {
- deviceInit = true;
- startInvocation(config, context, listener);
- startInvocationCalled = true;
- try {
+ try (CloseableTraceScope ignored =
+ new CloseableTraceScope(
+ InvocationMetricKey.pre_sharding_required_setup.name())) {
+ deviceInit = true;
+ startInvocation(config, context, listener);
+ startInvocationCalled = true;
invocationPath.runDevicePreInvocationSetup(context, config, listener);
} catch (DeviceNotAvailableException | TargetSetupError e) {
CLog.e(e);
@@ -1122,7 +1217,8 @@
// Apply global filters before sharding so they are taken into account.
config.getGlobalFilters().setUpFilters(config);
- try {
+ try (CloseableTraceScope ignored =
+ new CloseableTraceScope(InvocationMetricKey.sharding.name())) {
sharding = invocationPath.shardConfig(config, info, rescheduler, listener);
} catch (RuntimeException unexpected) {
CLog.e("Exception during sharding.");
@@ -1199,8 +1295,9 @@
}
config.cleanConfigurationData();
-
- Runtime.getRuntime().removeShutdownHook(cleanUpThread);
+ if (cleanUpThread != null) {
+ Runtime.getRuntime().removeShutdownHook(cleanUpThread);
+ }
}
}
@@ -1234,11 +1331,21 @@
}
@Override
- public void notifyInvocationStopped(String message, ErrorIdentifier errorId) {
+ public void notifyInvocationForceStopped(String message, ErrorIdentifier errorId) {
mStopCause = message;
mStopErrorId = errorId;
if (mStopRequestTime == null) {
mStopRequestTime = System.currentTimeMillis();
+ mForcedStopRequestedAfterTest = mTestDone;
+ }
+ }
+
+ @Override
+ public void notifyInvocationStopped(String message) {
+ if (mSoftStopRequestTime == null) {
+ mSoftStopRequestTime = System.currentTimeMillis();
+ // If test isn't started yet, we know we could have stopped.
+ mShutdownBeforeTest = !mTestStarted;
}
}
@@ -1278,16 +1385,19 @@
private void logExecuteShellCommand(List<ITestDevice> devices, ITestLogger logger) {
for (ITestDevice device : devices) {
+ if (device.getIDevice() instanceof StubDevice) {
+ continue;
+ }
if (!(device instanceof NativeDevice)) {
- return;
+ continue;
}
File log = ((NativeDevice) device).getExecuteShellCommandLog();
if (log == null || !log.exists()) {
- return;
+ continue;
}
if (log.length() == 0) {
CLog.d("executeShellCommandLog file was empty, skip logging.");
- return;
+ continue;
}
try (InputStreamSource source = new FileInputStreamSource(log)) {
logger.testLog(
diff --git a/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecution.java b/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecution.java
index 180d1d3..eb4021b 100644
--- a/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecution.java
+++ b/src/com/android/tradefed/invoker/sandbox/ParentSandboxInvocationExecution.java
@@ -30,6 +30,7 @@
import com.android.tradefed.invoker.InvocationExecution;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.invoker.TestInvocation.Stage;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.ITestInvocationListener;
@@ -159,7 +160,9 @@
public void runTests(
TestInformation info, IConfiguration config, ITestInvocationListener listener)
throws Throwable {
- prepareAndRunSandbox(info, config, listener);
+ try (CloseableTraceScope ignore = new CloseableTraceScope("prepareAndRunSandbox")) {
+ prepareAndRunSandbox(info, config, listener);
+ }
}
@Override
diff --git a/src/com/android/tradefed/result/LogFileSaver.java b/src/com/android/tradefed/result/LogFileSaver.java
index 59c1678..9976e40 100644
--- a/src/com/android/tradefed/result/LogFileSaver.java
+++ b/src/com/android/tradefed/result/LogFileSaver.java
@@ -307,7 +307,7 @@
*/
public File saveAndGZipLogFile(String dataName, LogDataType dataType, File fileToLog)
throws IOException {
- if (dataType.isCompressed()) {
+ if (dataType.isCompressed() || fileToLog.getName().endsWith(".gz")) {
CLog.d("Log data for %s is already compressed, skipping compression", dataName);
return saveLogFile(dataName, dataType, fileToLog);
}
@@ -343,6 +343,9 @@
*/
public File createCompressedLogFile(String dataName, LogDataType origDataType)
throws IOException {
+ if (mInvLogDir != null && !mInvLogDir.exists()) {
+ mInvLogDir.mkdirs();
+ }
// add underscore to end of data name to make generated name more readable
return FileUtil.createTempFile(dataName + "_",
String.format(".%s.%s", origDataType.getFileExt(), LogDataType.GZIP.getFileExt()),
diff --git a/src/com/android/tradefed/result/LogSaverResultForwarder.java b/src/com/android/tradefed/result/LogSaverResultForwarder.java
index 97a7641..06d7758 100644
--- a/src/com/android/tradefed/result/LogSaverResultForwarder.java
+++ b/src/com/android/tradefed/result/LogSaverResultForwarder.java
@@ -16,6 +16,7 @@
package com.android.tradefed.result;
+import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.TestInvocation;
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
@@ -74,11 +75,38 @@
CLog.e("Caught runtime exception from log saver: %s", mLogSaver.getClass().getName());
CLog.e(e);
}
- reportEndHostLog(mLogSaver, TestInvocation.TRADEFED_END_HOST_LOG);
+ reportEndHostLog(getListeners(), mLogSaver, TestInvocation.TRADEFED_END_HOST_LOG);
+ }
+
+ /** Log a final file before completion */
+ public static void logFile(
+ List<ITestInvocationListener> listeners,
+ ILogSaver saver,
+ InputStreamSource source,
+ String name,
+ LogDataType type) {
+ try (InputStream stream = source.createInputStream()) {
+ LogFile logFile = saver.saveLogData(name, type, stream);
+
+ for (ITestInvocationListener listener : listeners) {
+ try {
+ if (listener instanceof ILogSaverListener) {
+ ((ILogSaverListener) listener).testLogSaved(name, type, source, logFile);
+ ((ILogSaverListener) listener).logAssociation(name, logFile);
+ }
+ } catch (Exception e) {
+ CLog.logAndDisplay(LogLevel.ERROR, e.getMessage());
+ CLog.e(e);
+ }
+ }
+ } catch (IOException e) {
+ CLog.e(e);
+ }
}
/** Reports host_log from session in progress. */
- public static void reportEndHostLog(ILogSaver saver, String name) {
+ public static void reportEndHostLog(
+ List<ITestInvocationListener> listeners, ILogSaver saver, String name) {
LogRegistry registry = (LogRegistry) LogRegistry.getLogRegistry();
try (InputStreamSource source = registry.getLogger().getLog()) {
if (source == null) {
@@ -87,9 +115,7 @@
}
return;
}
- try (InputStream stream = source.createInputStream()) {
- saver.saveLogData(name, LogDataType.HOST_LOG, stream);
- }
+ logFile(listeners, saver, source, name, LogDataType.HOST_LOG);
if (SystemUtil.isRemoteEnvironment()) {
try (InputStream stream = source.createInputStream()) {
// In remote environment, dump to the stdout so we can get the logs in the
diff --git a/src/com/android/tradefed/result/ResultAndLogForwarder.java b/src/com/android/tradefed/result/ResultAndLogForwarder.java
index f2e34e2..45f2eb4 100644
--- a/src/com/android/tradefed/result/ResultAndLogForwarder.java
+++ b/src/com/android/tradefed/result/ResultAndLogForwarder.java
@@ -28,6 +28,10 @@
super(listeners);
}
+ public ResultAndLogForwarder(ITestInvocationListener... listeners) {
+ super(listeners);
+ }
+
@Override
public void invocationStarted(IInvocationContext context) {
InvocationSummaryHelper.reportInvocationStarted(getListeners(), context);
diff --git a/src/com/android/tradefed/result/proto/ProtoResultParser.java b/src/com/android/tradefed/result/proto/ProtoResultParser.java
index d1f3f63..a6f9eb3 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultParser.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultParser.java
@@ -23,6 +23,8 @@
import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.invoker.logger.TfObjectTracker;
import com.android.tradefed.invoker.proto.InvocationContext.Context;
+import com.android.tradefed.invoker.tracing.ActiveTrace;
+import com.android.tradefed.invoker.tracing.TracingLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ActionInProgress;
@@ -42,6 +44,7 @@
import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.result.proto.TestRecordProto.TestRecord;
import com.android.tradefed.testtype.suite.ModuleDefinition;
+import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.SerializationUtil;
import com.android.tradefed.util.proto.TestRecordProtoUtil;
@@ -323,6 +326,11 @@
// Still report the logs even if not reporting the invocation level.
handleLogs(endInvocationProto);
+ if (mInvocationEnded) {
+ CLog.d("Re-entry in invocationEnded, most likely for subprocess final logs.");
+ return;
+ }
+
// Get final context in case it changed.
Any anyDescription = endInvocationProto.getDescription();
if (!anyDescription.is(Context.class)) {
@@ -569,8 +577,8 @@
}
File path = new File(file.getPath());
if (Strings.isNullOrEmpty(file.getUrl()) && path.exists()) {
+ LogDataType type = file.getType();
try (InputStreamSource source = new FileInputStreamSource(path)) {
- LogDataType type = file.getType();
// File might have already been compressed
if (file.getPath().endsWith(LogDataType.ZIP.getFileExt())) {
type = LogDataType.ZIP;
@@ -578,10 +586,22 @@
log("Logging %s from subprocess: %s ", entry.getKey(), file.getPath());
logger.testLog(mFilePrefix + entry.getKey(), type, source);
}
+ if (ActiveTrace.TRACE_KEY.equals(entry.getKey())
+ && LogDataType.PERFETTO.equals(type)) {
+ CLog.d("Log the subprocess trace");
+ TracingLogger.getActiveTrace().addSubprocessTrace(path);
+ FileUtil.deleteFile(path);
+ }
} else {
log(
- "Logging %s from subprocess. url: %s, path: %s",
- entry.getKey(), file.getUrl(), file.getPath());
+ "Logging %s from subprocess. url: %s, path: %s [exists: %s]",
+ entry.getKey(), file.getUrl(), file.getPath(), path.exists());
+ if (ActiveTrace.TRACE_KEY.equals(entry.getKey())
+ && LogDataType.PERFETTO.equals(file.getType())
+ && path.exists()) {
+ CLog.d("Log the subprocess trace");
+ TracingLogger.getActiveTrace().addSubprocessTrace(path);
+ }
logger.logAssociation(mFilePrefix + entry.getKey(), file);
}
} catch (InvalidProtocolBufferException e) {
diff --git a/src/com/android/tradefed/result/proto/ProtoResultReporter.java b/src/com/android/tradefed/result/proto/ProtoResultReporter.java
index 5bccc48..ce78efb 100644
--- a/src/com/android/tradefed/result/proto/ProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/ProtoResultReporter.java
@@ -15,6 +15,7 @@
*/
package com.android.tradefed.result.proto;
+import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
import com.android.tradefed.error.HarnessException;
@@ -70,6 +71,8 @@
private FailureDescription mInvocationFailureDescription = null;
/** Whether or not a testModuleStart had currently been called. */
private boolean mModuleInProgress = false;
+ /** Track whether or not invocation ended has been reported. */
+ private boolean mInvocationEnded = false;
@Override
public boolean supportGranularResults() {
@@ -145,6 +148,13 @@
*/
public void processTestCaseEnded(TestRecord testCaseRecord) {}
+ /**
+ * Use the invocation record to send one by one all the final logs of the invocation.
+ *
+ * @param invocationLogs The finalized proto representing the invocation.
+ */
+ public void processFinalInvocationLogs(TestRecord invocationLogs) {}
+
// Invocation events
@Override
@@ -221,6 +231,7 @@
CLog.e("Failed to process invocation ended:");
CLog.e(e);
}
+ mInvocationEnded = true;
}
// Module events (optional when there is no suite)
@@ -510,6 +521,11 @@
return;
}
TestRecord.Builder current = mLatestChild.peek();
+ if (mInvocationEnded) {
+ // For after invocation ended events, report artifacts one by one.
+ current.clearArtifacts();
+ current.clearChildren();
+ }
Map<String, Any> fullmap = new HashMap<>();
fullmap.putAll(current.getArtifactsMap());
Any any = Any.pack(createFileProto(logFile));
@@ -522,6 +538,10 @@
} while (fullmap.containsKey(key));
fullmap.put(key, any);
current.putAllArtifacts(fullmap);
+ if (mInvocationEnded) {
+ CLog.logAndDisplay(LogLevel.DEBUG, "process final logs: %s", logFile.getPath());
+ processFinalInvocationLogs(current.build());
+ }
}
/**
diff --git a/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java b/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
index fab982a..5ccea82 100644
--- a/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
+++ b/src/com/android/tradefed/result/proto/StreamProtoResultReporter.java
@@ -83,11 +83,18 @@
}
@Override
+ public void processFinalInvocationLogs(TestRecord invocationLogs) {
+ writeRecordToSocket(invocationLogs);
+ }
+
+ @Override
public void processFinalProto(TestRecord finalRecord) {
try {
writeRecordToSocket(finalRecord);
} finally {
- closeSocket();
+ // Upon invocation ended, trigger the end of the socket when the process finishes
+ SocketFinisher thread = new SocketFinisher();
+ Runtime.getRuntime().addShutdownHook(thread);
}
}
@@ -112,4 +119,18 @@
CLog.e(e);
}
}
+
+ /** Threads that help terminating the socket. */
+ private class SocketFinisher extends Thread {
+
+ public SocketFinisher() {
+ super();
+ setName("StreamProtoResultReporter-socket-finisher");
+ }
+
+ @Override
+ public void run() {
+ closeSocket();
+ }
+ }
}
diff --git a/src/com/android/tradefed/sandbox/SandboxConfigUtil.java b/src/com/android/tradefed/sandbox/SandboxConfigUtil.java
index 199fcb4..826b4ec 100644
--- a/src/com/android/tradefed/sandbox/SandboxConfigUtil.java
+++ b/src/com/android/tradefed/sandbox/SandboxConfigUtil.java
@@ -18,6 +18,7 @@
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.GlobalConfiguration;
import com.android.tradefed.config.IConfiguration;
+import com.android.tradefed.config.NoOpConfigOptionValueTransformer;
import com.android.tradefed.config.proxy.AutomatedReporters;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.error.ErrorIdentifier;
@@ -124,6 +125,9 @@
if (result.getStderr().contains(InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR.name())) {
error = InfraErrorIdentifier.OPTION_CONFIGURATION_ERROR;
}
+ if (result.getStderr().contains(InfraErrorIdentifier.GCS_ERROR.name())) {
+ error = InfraErrorIdentifier.GCS_ERROR;
+ }
throw new SandboxConfigurationException(errorMessage, error);
}
@@ -151,6 +155,8 @@
/** Create a global config with only the keystore to make it available in subprocess. */
public static File dumpFilteredGlobalConfig(Set<String> exclusionPatterns) throws IOException {
- return GlobalConfiguration.getInstance().cloneConfigWithFilter(exclusionPatterns);
+ return GlobalConfiguration.getInstance()
+ .cloneConfigWithFilter(
+ exclusionPatterns, new NoOpConfigOptionValueTransformer(), false);
}
}
diff --git a/src/com/android/tradefed/service/management/DeviceManagementGrpcServer.java b/src/com/android/tradefed/service/management/DeviceManagementGrpcServer.java
index 03672aa..54e3955 100644
--- a/src/com/android/tradefed/service/management/DeviceManagementGrpcServer.java
+++ b/src/com/android/tradefed/service/management/DeviceManagementGrpcServer.java
@@ -1,6 +1,7 @@
package com.android.tradefed.service.management;
import com.android.annotations.VisibleForTesting;
+import com.android.tradefed.command.ICommandScheduler;
import com.android.tradefed.command.remote.DeviceDescriptor;
import com.android.tradefed.device.DeviceAllocationState;
import com.android.tradefed.device.DeviceSelectionOptions;
@@ -20,6 +21,8 @@
import com.proto.tradefed.device.ReserveDeviceRequest;
import com.proto.tradefed.device.ReserveDeviceResponse;
import com.proto.tradefed.device.ReserveDeviceResponse.Result;
+import com.proto.tradefed.device.StopLeasingRequest;
+import com.proto.tradefed.device.StopLeasingResponse;
import java.io.IOException;
import java.util.Map;
@@ -37,6 +40,7 @@
private final Server mServer;
private final IDeviceManager mDeviceManager;
+ private final ICommandScheduler mCommandScheduler;
private final Map<String, ReservationInformation> mSerialToReservation =
new ConcurrentHashMap<>();
@@ -47,21 +51,27 @@
: null;
}
- public DeviceManagementGrpcServer(int port, IDeviceManager deviceManager) {
- this(ServerBuilder.forPort(port), deviceManager);
+ public DeviceManagementGrpcServer(
+ int port, IDeviceManager deviceManager, ICommandScheduler scheduler) {
+ this(ServerBuilder.forPort(port), deviceManager, scheduler);
}
@VisibleForTesting
public DeviceManagementGrpcServer(
- ServerBuilder<?> serverBuilder, IDeviceManager deviceManager) {
+ ServerBuilder<?> serverBuilder,
+ IDeviceManager deviceManager,
+ ICommandScheduler scheduler) {
mServer = serverBuilder.addService(this).build();
mDeviceManager = deviceManager;
+ mCommandScheduler = scheduler;
}
@VisibleForTesting
- public DeviceManagementGrpcServer(Server server, IDeviceManager deviceManager) {
+ public DeviceManagementGrpcServer(
+ Server server, IDeviceManager deviceManager, ICommandScheduler scheduler) {
mServer = server;
mDeviceManager = deviceManager;
+ mCommandScheduler = scheduler;
}
/** Start the grpc server. */
@@ -135,9 +145,18 @@
.setMessage("serial requested was null or empty.");
responseObserver.onNext(responseBuilder.build());
responseObserver.onCompleted();
+ return;
}
DeviceDescriptor descriptor = mDeviceManager.getDeviceDescriptor(serial);
+ if (descriptor == null) {
+ responseBuilder
+ .setResult(Result.UNKNOWN)
+ .setMessage("No descriptor found for serial " + serial);
+ responseObserver.onNext(responseBuilder.build());
+ responseObserver.onCompleted();
+ return;
+ }
if (DeviceAllocationState.Allocated.equals(descriptor.getState())) {
Result result = Result.ALREADY_ALLOCATED;
if (mSerialToReservation.containsKey(serial)) {
@@ -169,6 +188,25 @@
responseObserver.onCompleted();
}
+ @Override
+ public void stopLeasing(
+ StopLeasingRequest request, StreamObserver<StopLeasingResponse> responseObserver) {
+ StopLeasingResponse.Builder responseBuilder = StopLeasingResponse.newBuilder();
+
+ // Notify to stop leasing
+ try {
+ mCommandScheduler.shutdown();
+ responseBuilder.setResult(StopLeasingResponse.Result.SUCCEED);
+ } catch (RuntimeException e) {
+ // This might happen in case scheduler isn't started or in bad state.
+ responseBuilder.setResult(StopLeasingResponse.Result.FAIL);
+ responseBuilder.setMessage(e.getMessage());
+ }
+
+ responseObserver.onNext(responseBuilder.build());
+ responseObserver.onCompleted();
+ }
+
private DeviceStatus descriptorToStatus(DeviceDescriptor descriptor) {
DeviceStatus.Builder deviceStatusBuilder = DeviceStatus.newBuilder();
deviceStatusBuilder.setDeviceId(descriptor.getSerial());
diff --git a/src/com/android/tradefed/service/management/TestInvocationManagementServer.java b/src/com/android/tradefed/service/management/TestInvocationManagementServer.java
index dabb313..240b449 100644
--- a/src/com/android/tradefed/service/management/TestInvocationManagementServer.java
+++ b/src/com/android/tradefed/service/management/TestInvocationManagementServer.java
@@ -34,6 +34,8 @@
import com.proto.tradefed.invocation.InvocationStatus.Status;
import com.proto.tradefed.invocation.NewTestCommandRequest;
import com.proto.tradefed.invocation.NewTestCommandResponse;
+import com.proto.tradefed.invocation.StopInvocationRequest;
+import com.proto.tradefed.invocation.StopInvocationResponse;
import com.proto.tradefed.invocation.TestInvocationManagementGrpc.TestInvocationManagementImplBase;
import java.io.File;
@@ -57,7 +59,18 @@
private final Server mServer;
private final ICommandScheduler mCommandScheduler;
private final DeviceManagementGrpcServer mDeviceReservationManager;
- private Map<String, ScheduledInvocationForwarder> mTracker = new HashMap<>();
+ private Map<String, InvocationInformation> mTracker = new HashMap<>();
+
+ public class InvocationInformation {
+ public final long invocationId;
+ public final ScheduledInvocationForwarder scheduledInvocationForwarder;
+
+ InvocationInformation(
+ long invocationId, ScheduledInvocationForwarder scheduledInvocationForwarder) {
+ this.invocationId = invocationId;
+ this.scheduledInvocationForwarder = scheduledInvocationForwarder;
+ }
+ }
/** Returns the port used by the server. */
public static Integer getPort() {
@@ -132,15 +145,22 @@
if (!request.getReservationIdList().isEmpty()) {
device = getReservedDevices(request.getReservationIdList());
}
+ long invocationId = -1;
if (device == null) {
- mCommandScheduler.execCommand(forwarder, command);
+ invocationId = mCommandScheduler.execCommand(forwarder, command);
} else {
- mCommandScheduler.execCommand(forwarder, device, command);
+ invocationId = mCommandScheduler.execCommand(forwarder, device, command);
}
- // TODO: Align trackerId with true invocation id
- String trackerId = UUID.randomUUID().toString();
- mTracker.put(trackerId, forwarder);
- responseBuilder.setInvocationId(trackerId);
+ if (invocationId == -1) {
+ responseBuilder.setCommandErrorInfo(
+ CommandErrorInfo.newBuilder()
+ .setErrorMessage("Something went wrong to execute the command."));
+ } else {
+ // TODO: Align trackerId with true invocation id
+ String trackerId = UUID.randomUUID().toString();
+ mTracker.put(trackerId, new InvocationInformation(invocationId, forwarder));
+ responseBuilder.setInvocationId(trackerId);
+ }
} catch (ConfigurationException | IOException | RuntimeException e) {
// TODO: Expand proto to convey those errors
// return a response without invocation id
@@ -165,10 +185,16 @@
String invocationId = request.getInvocationId();
if (mTracker.containsKey(invocationId)) {
responseBuilder.setInvocationStatus(
- createStatus(mTracker.get(invocationId).getListeners()));
+ createStatus(
+ mTracker.get(invocationId)
+ .scheduledInvocationForwarder
+ .getListeners()));
if (responseBuilder.getInvocationStatus().getStatus().equals(Status.DONE)) {
responseBuilder.setTestRecordPath(
- getProtoPath(mTracker.get(invocationId).getListeners()));
+ getProtoPath(
+ mTracker.get(invocationId)
+ .scheduledInvocationForwarder
+ .getListeners()));
// Finish the tracking after returning the first status done.
mTracker.remove(invocationId);
}
@@ -182,6 +208,37 @@
responseObserver.onCompleted();
}
+ @Override
+ public void stopInvocation(
+ StopInvocationRequest request,
+ StreamObserver<StopInvocationResponse> responseObserver) {
+ StopInvocationResponse.Builder responseBuilder = StopInvocationResponse.newBuilder();
+ String invocationId = request.getInvocationId();
+ if (mTracker.containsKey(invocationId)) {
+ long realInvocationId = mTracker.get(invocationId).invocationId;
+ boolean found =
+ mCommandScheduler.stopInvocation((int) realInvocationId, request.getReason());
+ if (found) {
+ responseBuilder.setStatus(StopInvocationResponse.Status.SUCCESS);
+ } else {
+ responseBuilder
+ .setStatus(StopInvocationResponse.Status.ERROR)
+ .setCommandErrorInfo(
+ CommandErrorInfo.newBuilder()
+ .setErrorMessage(
+ "No running matching invocation to stop."));
+ }
+ } else {
+ responseBuilder
+ .setStatus(StopInvocationResponse.Status.ERROR)
+ .setCommandErrorInfo(
+ CommandErrorInfo.newBuilder()
+ .setErrorMessage("invocation id is not tracked."));
+ }
+ responseObserver.onNext(responseBuilder.build());
+ responseObserver.onCompleted();
+ }
+
private InvocationStatus createStatus(List<ITestInvocationListener> listeners) {
InvocationStatus.Builder invocationStatusBuilder = InvocationStatus.newBuilder();
Status status = Status.UNKNOWN;
diff --git a/src/com/android/tradefed/suite/checker/DeviceBaselineChecker.java b/src/com/android/tradefed/suite/checker/DeviceBaselineChecker.java
index 106e9fb..63b9e30 100644
--- a/src/com/android/tradefed/suite/checker/DeviceBaselineChecker.java
+++ b/src/com/android/tradefed/suite/checker/DeviceBaselineChecker.java
@@ -21,7 +21,6 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.suite.checker.StatusCheckerResult.CheckStatus;
import com.android.tradefed.suite.checker.baseline.DeviceBaselineSetter;
-import com.android.tradefed.suite.checker.baseline.SettingsBaselineSetter;
import com.android.tradefed.testtype.suite.ITestSuite;
import com.android.tradefed.util.StreamUtil;
@@ -33,17 +32,26 @@
import java.io.InputStream;
import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
/** Set device baseline settings before each module. */
public class DeviceBaselineChecker implements ISystemStatusChecker {
- private static final String DEVICE_BASELINE_CONFIG_FILE =
- "/config/checker/baseline_config.json";
- private List<DeviceBaselineSetter> mDeviceBaselineSetters = null;
+ private static final String DEVICE_BASELINE_CONFIG_FILE = "/config/checker/baseline_config.json";
+ // Thread pool size to set device baselines.
+ private static final int N_THREAD = 8;
+ private static final String SET_SUCCESS_MESSAGE = "SUCCESS";
+ private List<DeviceBaselineSetter> mDeviceBaselineSetters;
@Option(
name = "enable-device-baseline-settings",
@@ -58,6 +66,10 @@
+ "Each value is the setter’s name")
private Set<String> mEnableExperimentDeviceBaselineSetters = new HashSet<>();
+ public static String getSetSuccessMessage() {
+ return SET_SUCCESS_MESSAGE;
+ }
+
@VisibleForTesting
void setDeviceBaselineSetters(List<DeviceBaselineSetter> deviceBaselineSetters) {
mDeviceBaselineSetters = deviceBaselineSetters;
@@ -76,17 +88,21 @@
for (int i = 0; i < names.length(); i++) {
String name = names.getString(i);
JSONObject objectValue = jsonObject.getJSONObject(name);
- DeviceBaselineSetter deviceBaselineSetter =
- new SettingsBaselineSetter(
- name,
- objectValue.getString("namespace"),
- objectValue.getString("key"),
- objectValue.getString("value"),
- objectValue.has("experimental")
- && objectValue.getBoolean("experimental"));
- deviceBaselineSetters.add(deviceBaselineSetter);
+ // Create a setter according to the class name.
+ String className = objectValue.getString("class_name");
+ Class<? extends DeviceBaselineSetter> setterClass =
+ (Class<? extends DeviceBaselineSetter>) Class.forName(className);
+ Constructor<? extends DeviceBaselineSetter> constructor =
+ setterClass.getConstructor(JSONObject.class, String.class);
+ deviceBaselineSetters.add(constructor.newInstance(objectValue, name));
}
- } catch (JSONException | IOException e) {
+ } catch (JSONException
+ | IOException
+ | ClassNotFoundException
+ | NoSuchMethodException
+ | InstantiationException
+ | IllegalAccessException
+ | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
@@ -95,23 +111,41 @@
/** {@inheritDoc} */
@Override
- public StatusCheckerResult preExecutionCheck(ITestDevice mDevice)
+ public StatusCheckerResult preExecutionCheck(ITestDevice device)
throws DeviceNotAvailableException {
if (mDeviceBaselineSetters == null) {
initializeDeviceBaselineSetters();
}
StatusCheckerResult result = new StatusCheckerResult(CheckStatus.SUCCESS);
StringBuilder errorMessage = new StringBuilder();
+ ExecutorService pool = Executors.newFixedThreadPool(N_THREAD);
+ List<SetterHelper> setterHelperList = new ArrayList<>();
for (DeviceBaselineSetter setter : mDeviceBaselineSetters) {
// Check if the device baseline setting should be skipped.
if (setter.isExperimental()
&& !mEnableExperimentDeviceBaselineSetters.contains(setter.getName())) {
continue;
}
- if (!setter.setBaseline(mDevice)) {
- result.setStatus(CheckStatus.FAILED);
- errorMessage.append(String.format("Failed to set baseline %s. ", setter.getName()));
+ setterHelperList.add(new SetterHelper(setter, device));
+ }
+ try {
+ // Set device baseline settings in parallel.
+ List<Future<String>> setterResultList = pool.invokeAll(setterHelperList);
+ for (Future<String> setterResult : setterResultList) {
+ if (!SET_SUCCESS_MESSAGE.equals(setterResult.get())) {
+ result.setStatus(CheckStatus.FAILED);
+ errorMessage.append(setterResult.get());
+ }
}
+ } catch (ExecutionException e) {
+ result.setStatus(CheckStatus.FAILED);
+ errorMessage.append(e.getMessage());
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ result.setStatus(CheckStatus.FAILED);
+ errorMessage.append(e.getMessage());
+ } finally {
+ pool.shutdown();
}
if (result.getStatus() == CheckStatus.FAILED) {
result.setErrorMessage(errorMessage.toString());
@@ -120,3 +154,23 @@
return result;
}
}
+
+class SetterHelper implements Callable<String> {
+
+ private final DeviceBaselineSetter mSetter;
+ private final ITestDevice mDevice;
+
+ SetterHelper(DeviceBaselineSetter setter, ITestDevice device) {
+ mSetter = setter;
+ mDevice = device;
+ }
+
+ @Override
+ public String call() throws Exception {
+ // Set device baseline settings.
+ if (!mSetter.setBaseline(mDevice)) {
+ return String.format("Failed to set baseline %s. ", mSetter.getName());
+ }
+ return DeviceBaselineChecker.getSetSuccessMessage();
+ }
+}
diff --git a/src/com/android/tradefed/suite/checker/baseline/DeviceBaselineSetter.java b/src/com/android/tradefed/suite/checker/baseline/DeviceBaselineSetter.java
index 6645b53..29aee7d 100644
--- a/src/com/android/tradefed/suite/checker/baseline/DeviceBaselineSetter.java
+++ b/src/com/android/tradefed/suite/checker/baseline/DeviceBaselineSetter.java
@@ -19,11 +19,24 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import org.json.JSONException;
+import org.json.JSONObject;
+
/** Abstract class used to create a device baseline setting. */
public abstract class DeviceBaselineSetter {
+ private final String mName;
+ private final boolean mExperimental;
+
+ public DeviceBaselineSetter(JSONObject object, String name) throws JSONException {
+ mName = name;
+ mExperimental = object.has("experimental") && object.getBoolean("experimental");
+ }
+
/** Gets the unique name of the setter. */
- public abstract String getName();
+ public String getName() {
+ return mName;
+ }
/** Sets the baseline setting for the device. */
public abstract boolean setBaseline(ITestDevice mDevice) throws DeviceNotAvailableException;
@@ -35,6 +48,6 @@
* applied unless the option enable-device-baseline-settings is set to false.
*/
public boolean isExperimental() {
- return false;
+ return mExperimental;
}
}
diff --git a/src/com/android/tradefed/suite/checker/baseline/LockSettingsBaselineSetter.java b/src/com/android/tradefed/suite/checker/baseline/LockSettingsBaselineSetter.java
new file mode 100644
index 0000000..65623e2
--- /dev/null
+++ b/src/com/android/tradefed/suite/checker/baseline/LockSettingsBaselineSetter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 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.suite.checker.baseline;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/** A setter to remove screen lock settings. */
+public class LockSettingsBaselineSetter extends DeviceBaselineSetter {
+ private final List<String> mClearPwdCommands;
+ private static final String GET_LOCK_SCREEN_COMMAND = "locksettings get-disabled";
+ private static final String LOCK_SCREEN_OFF_COMMAND = "locksettings set-disabled true";
+ private static final String CLEAR_PWD_COMMAND = "locksettings clear --old %s";
+
+ public LockSettingsBaselineSetter(JSONObject object, String name) throws JSONException {
+ super(object, name);
+ List<String> clearPwdCommands = new ArrayList<>();
+ JSONArray pwds = object.getJSONArray("clear_pwds");
+ for (int index = 0; index < pwds.length(); index++) {
+ clearPwdCommands.add(String.format(CLEAR_PWD_COMMAND, pwds.getString(index)));
+ }
+ mClearPwdCommands = clearPwdCommands;
+ }
+
+ @Override
+ public boolean setBaseline(ITestDevice mDevice) throws DeviceNotAvailableException {
+ if ("true".equals(mDevice.executeShellCommand(GET_LOCK_SCREEN_COMMAND).trim())) {
+ return true;
+ }
+ // Clear old passwords.
+ for (String command : mClearPwdCommands) {
+ mDevice.executeShellCommand(command);
+ }
+ // Turn off lock-screen option.
+ mDevice.executeShellCommand(LOCK_SCREEN_OFF_COMMAND);
+ return "true".equals(mDevice.executeShellCommand(GET_LOCK_SCREEN_COMMAND).trim());
+ }
+}
diff --git a/src/com/android/tradefed/suite/checker/baseline/SettingsBaselineSetter.java b/src/com/android/tradefed/suite/checker/baseline/SettingsBaselineSetter.java
index 72b2f96..17476c2 100644
--- a/src/com/android/tradefed/suite/checker/baseline/SettingsBaselineSetter.java
+++ b/src/com/android/tradefed/suite/checker/baseline/SettingsBaselineSetter.java
@@ -19,22 +19,21 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
+import org.json.JSONException;
+import org.json.JSONObject;
+
/** A common setter to handle device baseline settings via ITestDevice.setSetting. */
public class SettingsBaselineSetter extends DeviceBaselineSetter {
- private final String mName;
private final String mNamespace;
private final String mKey;
private final String mValue;
- private final boolean mExperimental;
- public SettingsBaselineSetter(
- String name, String namespace, String key, String value, boolean experimental) {
- mName = name;
- mNamespace = namespace;
- mKey = key;
- mValue = value;
- mExperimental = experimental;
+ public SettingsBaselineSetter(JSONObject object, String name) throws JSONException {
+ super(object, name);
+ mNamespace = object.getString("namespace");
+ mKey = object.getString("key");
+ mValue = object.getString("value");
}
@Override
@@ -43,14 +42,4 @@
String settingValue = mDevice.getSetting(mNamespace, mKey);
return settingValue != null && settingValue.equals(mValue);
}
-
- @Override
- public String getName() {
- return mName;
- }
-
- @Override
- public boolean isExperimental() {
- return mExperimental;
- }
}
\ No newline at end of file
diff --git a/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java b/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java
index dc4c672..d24fee9 100644
--- a/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java
+++ b/src/com/android/tradefed/targetprep/GkiDeviceFlashPreparer.java
@@ -82,6 +82,11 @@
private String mDtboImageName = "dtbo.img";
@Option(
+ name = "vendor-dlkm-image-name",
+ description = "The file name in BuildInfo that provides vendor_dlkm image.")
+ private String mVendorDlkmImageName = "vendor_dlkm.img";
+
+ @Option(
name = "boot-image-file-name",
description =
"The boot image file name to search for if gki-boot-image-name in "
@@ -103,6 +108,13 @@
private String mDtboImageFileName = "dtbo.img";
@Option(
+ name = "vendor-dlkm-image-file-name",
+ description =
+ "The vendor_dlkm image file name to search for if vendor-dlkm-image-name in "
+ + "BuildInfo is a zip file or directory, for example vendor_dlkm.img.")
+ private String mVendorDlkmImageFileName = "vendor_dlkm.img";
+
+ @Option(
name = "post-reboot-device-into-user-space",
description = "whether to boot the device in user space after flash.")
private boolean mPostRebootDeviceIntoUserSpace = true;
@@ -112,6 +124,9 @@
description = "Whether to wipe device after GKI boot image flash.")
private boolean mShouldWipeDevice = true;
+ @Option(name = "oem-disable-verity", description = "Whether to run oem disable-verity.")
+ private boolean mShouldDisableOemVerity = false;
+
@Option(
name = "boot-header-version",
description = "The version of the boot.img header. Set to 3 by default.")
@@ -193,6 +208,9 @@
private void flashGki(ITestDevice device, IBuildInfo buildInfo, File tmpDir)
throws TargetSetupError, DeviceNotAvailableException {
device.rebootIntoBootloader();
+ if (mShouldDisableOemVerity) {
+ executeFastbootCmd(device, "oem disable-verity");
+ }
long start = System.currentTimeMillis();
getHostOptions().takePermit(PermitLimitType.CONCURRENT_FLASHER);
CLog.v(
@@ -219,8 +237,20 @@
tmpDir);
executeFastbootCmd(device, "flash", "dtbo", dtboImg.getAbsolutePath());
}
+
executeFastbootCmd(device, "flash", "boot", mBootImg.getAbsolutePath());
+ if (buildInfo.getFile(mVendorDlkmImageName) != null) {
+ File vendorDlkmImg =
+ getRequestedFile(
+ device,
+ mVendorDlkmImageFileName,
+ buildInfo.getFile(mVendorDlkmImageName),
+ tmpDir);
+ device.rebootIntoFastbootd();
+ executeFastbootCmd(device, "flash", "vendor_dlkm", vendorDlkmImg.getAbsolutePath());
+ }
+
if (mShouldWipeDevice) {
executeFastbootCmd(device, "-w");
}
diff --git a/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java b/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java
index 3847358..6fa973d 100644
--- a/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/RunOnSecondaryUserTargetPreparer.java
@@ -154,7 +154,7 @@
"instrumentation-arg", SKIP_TESTS_REASON_KEY, reason.replace(" ", "\\ "));
} catch (ConfigurationException e) {
throw new TargetSetupError(
- "Error setting skip-tests-reason", device.getDeviceDescriptor());
+ "Error setting skip-tests-reason", e, device.getDeviceDescriptor());
}
}
diff --git a/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java b/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java
index aba3523..8c1bf8e 100644
--- a/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java
+++ b/src/com/android/tradefed/targetprep/RunOnWorkProfileTargetPreparer.java
@@ -195,7 +195,7 @@
"instrumentation-arg", SKIP_TESTS_REASON_KEY, reason.replace(" ", "\\ "));
} catch (ConfigurationException e) {
throw new TargetSetupError(
- "Error setting skip-tests-reason", device.getDeviceDescriptor());
+ "Error setting skip-tests-reason", e, device.getDeviceDescriptor());
}
}
diff --git a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
index 657359c..bc60a5b 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.invoker.InvocationContext;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.observatory.IDiscoverDependencies;
import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.testtype.IAbi;
@@ -77,7 +78,8 @@
* the first.
*/
@OptionClass(alias = "tests-zip-app")
-public class TestAppInstallSetup extends BaseTargetPreparer implements IAbiReceiver {
+public class TestAppInstallSetup extends BaseTargetPreparer
+ implements IAbiReceiver, IDiscoverDependencies {
/** The mode the apk should be install in. */
private enum InstallMode {
@@ -824,4 +826,17 @@
return incrementalInstallSessionBuilder;
}
+
+ @Override
+ public Set<String> reportDependencies() {
+ Set<String> deps = new HashSet<String>();
+ for (File f : getTestsFileName()) {
+ if (!f.exists()) deps.add(f.getName());
+ }
+ for (String testAppNames : mSplitApkFileNames) {
+ List<String> apkNames = Arrays.asList(testAppNames.split(","));
+ deps.addAll(apkNames);
+ }
+ return deps;
+ }
}
diff --git a/src/com/android/tradefed/testtype/SubprocessTfLauncher.java b/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
index 67a506a..5f1c571 100644
--- a/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
+++ b/src/com/android/tradefed/testtype/SubprocessTfLauncher.java
@@ -25,6 +25,7 @@
import com.android.tradefed.config.proxy.AutomatedReporters;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.error.HarnessRuntimeException;
+import com.android.tradefed.invoker.DelegatedInvocationExecution;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.RemoteInvocationExecution;
import com.android.tradefed.invoker.TestInformation;
@@ -276,6 +277,7 @@
mRunUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_SERVER_CONFIG_VARIABLE);
mRunUtil.unsetEnvVariable(ANDROID_SERIAL_VAR);
mRunUtil.unsetEnvVariable(RemoteInvocationExecution.START_FEATURE_SERVER);
+ mRunUtil.unsetEnvVariable(DelegatedInvocationExecution.DELEGATED_MODE_VAR);
for (String variable : AutomatedReporters.REPORTER_MAPPING) {
mRunUtil.unsetEnvVariable(variable);
}
diff --git a/src/com/android/tradefed/testtype/TfTestLauncher.java b/src/com/android/tradefed/testtype/TfTestLauncher.java
index e3b65e7..f09128a 100644
--- a/src/com/android/tradefed/testtype/TfTestLauncher.java
+++ b/src/com/android/tradefed/testtype/TfTestLauncher.java
@@ -96,6 +96,9 @@
+ "Can be repeated.")
private List<String> mSubApkPath = new ArrayList<String>();
+ @Option(name = "skip-temp-dir-check", description = "Whether or not to skip temp dir check.")
+ private boolean mSkipTmpDirCheck = false;
+
// The regex pattern of temp files to be found in the temporary dir of the subprocess.
// Any file not matching the patterns, or multiple files in the temporary dir match the same
// pattern, is considered as test failure.
@@ -228,6 +231,9 @@
*/
@VisibleForTesting
protected void testTmpDirClean(File tmpDir, ITestInvocationListener listener) {
+ if (mSkipTmpDirCheck) {
+ return;
+ }
listener.testRunStarted("temporaryFiles", 1);
TestDescription tid = new TestDescription("temporary-files", "testIfClean");
listener.testStarted(tid);
diff --git a/src/com/android/tradefed/testtype/coverage/CoverageOptions.java b/src/com/android/tradefed/testtype/coverage/CoverageOptions.java
index dfb3263..2c5f7f4 100644
--- a/src/com/android/tradefed/testtype/coverage/CoverageOptions.java
+++ b/src/com/android/tradefed/testtype/coverage/CoverageOptions.java
@@ -61,6 +61,9 @@
)
private List<String> mCoverageProcesses = new ArrayList<>();
+ @Option(name = "merge-coverage", description = "Merge coverage measurements before logging.")
+ private boolean mMergeCoverage = false;
+
@Option(
name = "reset-coverage-before-test",
description = "Reset coverage before running each test.")
@@ -113,6 +116,11 @@
return ImmutableList.copyOf(mCoverageProcesses);
}
+ /** Returns whether to merge coverage measurements together before logging. */
+ public boolean shouldMergeCoverage() {
+ return mMergeCoverage;
+ }
+
/**
* Returns whether coverage measurements should be reset before each test.
*
diff --git a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
index 1f1c98f..1b04f5f 100644
--- a/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/BaseTestSuite.java
@@ -229,6 +229,10 @@
private Set<IAbi> mAbis = new LinkedHashSet<>();
private Set<DeviceFoldableState> mFoldableStates = new LinkedHashSet<>();
+ public void setSkipjarLoading(boolean skipJarLoading) {
+ mSkipJarLoading = skipJarLoading;
+ }
+
/** {@inheritDoc} */
@Override
public LinkedHashMap<String, IConfiguration> loadTests() {
diff --git a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
index 0220229..c13803c 100644
--- a/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
+++ b/src/com/android/tradefed/testtype/suite/GranularRetriableTestWrapper.java
@@ -31,9 +31,11 @@
import com.android.tradefed.invoker.logger.CurrentInvocation;
import com.android.tradefed.invoker.logger.CurrentInvocation.IsolationGrade;
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.ILogSaver;
import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.ResultAndLogForwarder;
import com.android.tradefed.result.TestRunResult;
import com.android.tradefed.result.error.ErrorIdentifier;
import com.android.tradefed.retry.IRetryDecision;
@@ -48,6 +50,7 @@
import java.time.Duration;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -316,6 +319,8 @@
private final void intraModuleRun(TestInformation testInfo, ITestInvocationListener runListener)
throws DeviceNotAvailableException {
mMainGranularRunListener.setAttemptIsolation(CurrentInvocation.runCurrentIsolation());
+ StartEndCollector startEndCollector = new StartEndCollector(runListener);
+ runListener = startEndCollector;
try {
List<IMetricCollector> clonedCollectors = cloneCollectors(mRunMetricCollectors);
if (mTest instanceof IMetricCollectorReceiver) {
@@ -347,7 +352,15 @@
CLog.e("Module '%s' - test '%s' threw exception:", mModuleId, mTest.getClass());
CLog.e(re);
CLog.e("Proceeding to the next test.");
+ if (!startEndCollector.mRunStartReported) {
+ CLog.e("Event mismatch ! the test runner didn't report any testRunStart.");
+ runListener.testRunStarted(mModule.getId(), 0);
+ }
runListener.testRunFailed(createFromException(re));
+ if (!startEndCollector.mRunEndedReported) {
+ CLog.e("Event mismatch ! the test runner didn't report any testRunEnded.");
+ runListener.testRunEnded(0L, new HashMap<String, Metric>());
+ }
} catch (DeviceUnresponsiveException due) {
// being able to catch a DeviceUnresponsiveException here implies that recovery was
// successful, and test execution should proceed to next module.
@@ -433,4 +446,46 @@
}
return failure;
}
+
+ /** Class helper to catch missing run start and end. */
+ public class StartEndCollector extends ResultAndLogForwarder {
+
+ public boolean mRunStartReported = false;
+ public boolean mRunEndedReported = false;
+
+ StartEndCollector(ITestInvocationListener listener) {
+ super(listener);
+ }
+
+ @Override
+ public void testRunStarted(String runName, int testCount) {
+ super.testRunStarted(runName, testCount);
+ mRunStartReported = true;
+ }
+
+ @Override
+ public void testRunStarted(String runName, int testCount, int attemptNumber) {
+ super.testRunStarted(runName, testCount, attemptNumber);
+ mRunStartReported = true;
+ }
+
+ @Override
+ public void testRunStarted(
+ String runName, int testCount, int attemptNumber, long startTime) {
+ super.testRunStarted(runName, testCount, attemptNumber, startTime);
+ mRunStartReported = true;
+ }
+
+ @Override
+ public void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) {
+ super.testRunEnded(elapsedTime, runMetrics);
+ mRunEndedReported = true;
+ }
+
+ @Override
+ public void testRunEnded(long elapsedTimeMillis, Map<String, String> runMetrics) {
+ super.testRunEnded(elapsedTimeMillis, runMetrics);
+ mRunEndedReported = true;
+ }
+ }
}
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index 6118bc0..573ccc0 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -48,6 +48,7 @@
import com.android.tradefed.invoker.logger.TfObjectTracker;
import com.android.tradefed.invoker.shard.token.ITokenRequest;
import com.android.tradefed.invoker.shard.token.TokenProperty;
+import com.android.tradefed.invoker.tracing.CloseableTraceScope;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
@@ -394,8 +395,12 @@
public File getTestsDir() throws FileNotFoundException {
IBuildInfo build = getBuildInfo();
+ File testsDir = null;
if (build instanceof IDeviceBuildInfo) {
- return ((IDeviceBuildInfo) build).getTestsDir();
+ testsDir = ((IDeviceBuildInfo) build).getTestsDir();
+ }
+ if (testsDir != null && testsDir.exists()) {
+ return testsDir;
}
// TODO: handle multi build?
throw new FileNotFoundException("Could not found a tests dir folder.");
@@ -546,46 +551,47 @@
mDirectModule.setBuild(mBuildInfo);
return runModules;
}
+ try (CloseableTraceScope ignore = new CloseableTraceScope("suite:createExecutionList")) {
+ LinkedHashMap<String, IConfiguration> runConfig = loadAndFilter();
+ if (runConfig.isEmpty()) {
+ CLog.i("No config were loaded. Nothing to run.");
+ return runModules;
+ }
- LinkedHashMap<String, IConfiguration> runConfig = loadAndFilter();
- if (runConfig.isEmpty()) {
- CLog.i("No config were loaded. Nothing to run.");
+ 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());
+ Map<String, List<ITargetPreparer>> preparersPerDevice =
+ getPreparerPerDevice(config.getValue());
+ ModuleDefinition module =
+ new ModuleDefinition(
+ config.getKey(),
+ config.getValue().getTests(),
+ preparersPerDevice,
+ suitePreparersPerDevice,
+ config.getValue().getMultiTargetPreparers(),
+ config.getValue());
+ if (mDisableAutoRetryTimeReporting) {
+ module.disableAutoRetryReportingTime();
+ }
+ module.setDevice(mDevice);
+ module.setBuild(mBuildInfo);
+ runModules.add(module);
+ }
+
+ /** Randomize all the modules to be ran if random-order is set and no sharding. */
+ if (mRandomOrder) {
+ randomizeTestModules(runModules, mRandomSeed);
+ }
+
+ CLog.logAndDisplay(LogLevel.DEBUG, "[Total Unique Modules = %s]", runModules.size());
+ // Free the map once we are done with it.
+ runConfig = null;
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());
- Map<String, List<ITargetPreparer>> preparersPerDevice =
- getPreparerPerDevice(config.getValue());
- ModuleDefinition module =
- new ModuleDefinition(
- config.getKey(),
- config.getValue().getTests(),
- preparersPerDevice,
- suitePreparersPerDevice,
- config.getValue().getMultiTargetPreparers(),
- config.getValue());
- if (mDisableAutoRetryTimeReporting) {
- module.disableAutoRetryReportingTime();
- }
- module.setDevice(mDevice);
- module.setBuild(mBuildInfo);
- runModules.add(module);
- }
-
- /** Randomize all the modules to be ran if random-order is set and no sharding.*/
- if (mRandomOrder) {
- randomizeTestModules(runModules, mRandomSeed);
- }
-
- CLog.logAndDisplay(LogLevel.DEBUG, "[Total Unique Modules = %s]", runModules.size());
- // Free the map once we are done with it.
- runConfig = null;
- return runModules;
}
/**
@@ -731,63 +737,66 @@
continue;
}
- // Populate the module context with devices and builds
- for (String deviceName : mContext.getDeviceConfigNames()) {
- module.getModuleInvocationContext()
- .addAllocatedDevice(deviceName, mContext.getDevice(deviceName));
- module.getModuleInvocationContext()
- .addDeviceBuildInfo(deviceName, mContext.getBuildInfo(deviceName));
- }
- // Add isolation status before module start for reporting
- if (!IsolationGrade.NOT_ISOLATED.equals(
- CurrentInvocation.moduleCurrentIsolation())) {
- module.getModuleInvocationContext()
- .addInvocationAttribute(
- ModuleDefinition.MODULE_ISOLATED,
- CurrentInvocation.moduleCurrentIsolation().toString());
- }
- // Only the module callback will be called here.
- ITestInvocationListener listenerWithCollectors = listener;
- if (mMetricCollectors != null) {
- for (IMetricCollector collector :
- CollectorHelper.cloneCollectors(mMetricCollectors)) {
- if (collector.isDisabled()) {
- CLog.d("%s has been disabled. Skipping.", collector);
- } else {
- if (collector instanceof IConfigurationReceiver) {
- ((IConfigurationReceiver) collector)
- .setConfiguration(module.getModuleConfiguration());
+ try (CloseableTraceScope ignore = new CloseableTraceScope(module.getId())) {
+ // Populate the module context with devices and builds
+ for (String deviceName : mContext.getDeviceConfigNames()) {
+ module.getModuleInvocationContext()
+ .addAllocatedDevice(deviceName, mContext.getDevice(deviceName));
+ module.getModuleInvocationContext()
+ .addDeviceBuildInfo(deviceName, mContext.getBuildInfo(deviceName));
+ }
+ // Add isolation status before module start for reporting
+ if (!IsolationGrade.NOT_ISOLATED.equals(
+ CurrentInvocation.moduleCurrentIsolation())) {
+ module.getModuleInvocationContext()
+ .addInvocationAttribute(
+ ModuleDefinition.MODULE_ISOLATED,
+ CurrentInvocation.moduleCurrentIsolation().toString());
+ }
+ // Only the module callback will be called here.
+ ITestInvocationListener listenerWithCollectors = listener;
+ if (mMetricCollectors != null) {
+ for (IMetricCollector collector :
+ CollectorHelper.cloneCollectors(mMetricCollectors)) {
+ if (collector.isDisabled()) {
+ CLog.d("%s has been disabled. Skipping.", collector);
+ } else {
+ if (collector instanceof IConfigurationReceiver) {
+ ((IConfigurationReceiver) collector)
+ .setConfiguration(module.getModuleConfiguration());
+ }
+ listenerWithCollectors =
+ collector.init(
+ module.getModuleInvocationContext(),
+ listenerWithCollectors);
+ TfObjectTracker.countWithParents(collector.getClass());
}
- listenerWithCollectors =
- collector.init(
- module.getModuleInvocationContext(),
- listenerWithCollectors);
- TfObjectTracker.countWithParents(collector.getClass());
}
}
+ listenerWithCollectors.testModuleStarted(module.getModuleInvocationContext());
+ mModuleInProgress = module;
+ // Trigger module start on module level listener too
+ new ResultForwarder(moduleListeners)
+ .testModuleStarted(module.getModuleInvocationContext());
+ TestInformation moduleInfo =
+ TestInformation.createModuleTestInfo(
+ testInfo, module.getModuleInvocationContext());
+ try {
+ runSingleModule(
+ module, moduleInfo, listener, moduleListeners, failureListener);
+ } finally {
+ // Trigger module end on module level listener too
+ new ResultForwarder(moduleListeners).testModuleEnded();
+ // clear out module invocation context since we are now done with module
+ // execution
+ listenerWithCollectors.testModuleEnded();
+ mModuleInProgress = null;
+ // Following modules will not be isolated if no action is taken
+ CurrentInvocation.setModuleIsolation(IsolationGrade.NOT_ISOLATED);
+ }
+ // Module isolation routine
+ moduleIsolation(mContext, listener);
}
- listenerWithCollectors.testModuleStarted(module.getModuleInvocationContext());
- mModuleInProgress = module;
- // Trigger module start on module level listener too
- new ResultForwarder(moduleListeners)
- .testModuleStarted(module.getModuleInvocationContext());
- TestInformation moduleInfo =
- TestInformation.createModuleTestInfo(
- testInfo, module.getModuleInvocationContext());
- try {
- runSingleModule(module, moduleInfo, listener, moduleListeners, failureListener);
- } finally {
- // Trigger module end on module level listener too
- new ResultForwarder(moduleListeners).testModuleEnded();
- // clear out module invocation context since we are now done with module
- // execution
- listenerWithCollectors.testModuleEnded();
- mModuleInProgress = null;
- // Following modules will not be isolated if no action is taken
- CurrentInvocation.setModuleIsolation(IsolationGrade.NOT_ISOLATED);
- }
- // Module isolation routine
- moduleIsolation(mContext, listener);
}
} catch (DeviceNotAvailableException e) {
CLog.e(
diff --git a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
index 40da0c4..99027a2 100644
--- a/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
+++ b/src/com/android/tradefed/testtype/suite/TestMappingSuiteRunner.java
@@ -122,6 +122,15 @@
+ "only the tests on the triggering device build will be run.")
private List<String> mAdditionalTestMappingZips = new ArrayList<>();
+ @Option(
+ name = "test-mapping-unmatched-file-pattern-paths",
+ description =
+ "A list of modified paths that does not match with a certain file_pattern in "
+ + "the TEST_MAPPING file. This is used only for Work Node, and handled "
+ + "by provider service. If none is specified, all tests are needed "
+ + "to run for the given change.")
+ private Set<String> mUnmatchedFilePatternPaths = new HashSet<>();
+
/** Special definition in the test mapping structure. */
private static final String TEST_MAPPING_INCLUDE_FILTER = "include-filter";
@@ -129,6 +138,10 @@
private IBuildInfo mBuildInfo;
+ public TestMappingSuiteRunner() {
+ setSkipjarLoading(true);
+ }
+
/**
* Load the tests configuration that will be run. Each tests is defined by a {@link
* IConfiguration} and a unique name under which it will report results. There are 2 ways to
diff --git a/src/com/android/tradefed/util/GCSFileDownloader.java b/src/com/android/tradefed/util/GCSFileDownloader.java
index bcea029..ab49a98 100644
--- a/src/com/android/tradefed/util/GCSFileDownloader.java
+++ b/src/com/android/tradefed/util/GCSFileDownloader.java
@@ -248,8 +248,10 @@
for (String subRemoteFolder : subRemoteFolders) {
String subFolderName = Paths.get(subRemoteFolder).getFileName().toString();
File subFolder = new File(localFolder, subFolderName);
- if (new File(localFolder, subFolderName).exists()
- && !new File(localFolder, subFolderName).isDirectory()) {
+ if (!subFolder.exists()) {
+ return false;
+ }
+ if (!subFolder.isDirectory()) {
CLog.w("%s exists as a non-directory.", subFolder);
subFolder = new File(localFolder, subFolderName + "_folder");
}
diff --git a/src/com/android/tradefed/util/SubprocessExceptionParser.java b/src/com/android/tradefed/util/SubprocessExceptionParser.java
index 5214a1a..91ad69f 100644
--- a/src/com/android/tradefed/util/SubprocessExceptionParser.java
+++ b/src/com/android/tradefed/util/SubprocessExceptionParser.java
@@ -19,6 +19,7 @@
import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.error.IHarnessException;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.error.ErrorIdentifier;
import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.sandbox.TradefedSandboxRunner;
@@ -83,6 +84,10 @@
+ "Using HarnessRuntimeException instead.");
}
}
- throw new HarnessRuntimeException(message, InfraErrorIdentifier.UNDETERMINED);
+ ErrorIdentifier id = InfraErrorIdentifier.UNDETERMINED;
+ if (CommandStatus.TIMED_OUT.equals(result.getStatus())) {
+ id = InfraErrorIdentifier.INVOCATION_TIMEOUT;
+ }
+ throw new HarnessRuntimeException(message, id);
}
}
diff --git a/src/com/android/tradefed/util/SubprocessTestResultsParser.java b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
index 6caeac3..7a1d697 100644
--- a/src/com/android/tradefed/util/SubprocessTestResultsParser.java
+++ b/src/com/android/tradefed/util/SubprocessTestResultsParser.java
@@ -20,6 +20,8 @@
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationGroupMetricKey;
import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
+import com.android.tradefed.invoker.tracing.ActiveTrace;
+import com.android.tradefed.invoker.tracing.TracingLogger;
import com.android.tradefed.invoker.logger.TfObjectTracker;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
@@ -554,13 +556,14 @@
assosInfo.mDataName);
return;
}
- try (InputStreamSource source = new FileInputStreamSource(path)) {
+ try (InputStreamSource source = new FileInputStreamSource(path, true)) {
LogDataType type = file.getType();
- // File might have already been compressed
- if (file.getPath().endsWith(LogDataType.ZIP.getFileExt())) {
- type = LogDataType.ZIP;
- }
CLog.d("Logging %s from subprocess: %s ", assosInfo.mDataName, file.getPath());
+ if (ActiveTrace.TRACE_KEY.equals(assosInfo.mDataName)
+ && LogDataType.PERFETTO.equals(type)) {
+ CLog.d("Log the subprocess trace");
+ TracingLogger.getActiveTrace().addSubprocessTrace(path);
+ }
mListener.testLog(name, type, source);
}
} else {
diff --git a/src/com/android/tradefed/util/testmapping/TestMapping.java b/src/com/android/tradefed/util/testmapping/TestMapping.java
index 4f5f4fc..62f5cb4 100644
--- a/src/com/android/tradefed/util/testmapping/TestMapping.java
+++ b/src/com/android/tradefed/util/testmapping/TestMapping.java
@@ -212,7 +212,8 @@
if (errorMessage != null) {
CLog.e(errorMessage);
- throw new RuntimeException(errorMessage);
+ throw new HarnessRuntimeException(
+ errorMessage, InfraErrorIdentifier.TEST_MAPPING_FILE_FORMAT_ISSUE);
}
}
diff --git a/test_framework/com/android/tradefed/device/metric/BluetoothConnectionLatencyCollector.java b/test_framework/com/android/tradefed/device/metric/BluetoothConnectionLatencyCollector.java
index 711eba6..4b294fe 100644
--- a/test_framework/com/android/tradefed/device/metric/BluetoothConnectionLatencyCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/BluetoothConnectionLatencyCollector.java
@@ -44,7 +44,7 @@
"bluetooth_connection_latency";
/** A map associates Bluetooth profile number to the descriptive name used for metric key. */
- protected static final ImmutableMap<Integer, String> bluetoothProfilesMap =
+ protected static final ImmutableMap<Integer, String> BLUETOOTH_PROFILES_MAP =
ImmutableMap.<Integer, String>builder()
.put(BluetoothProfile.HEADSET.getProfile(), "headset")
.put(BluetoothProfile.A2DP.getProfile(), "a2dp")
@@ -105,12 +105,12 @@
}
int bluetoothProfile = metric.getDimensionLeafValuesInWhat(0).getValueInt();
String metricKey;
- if (bluetoothProfilesMap.containsKey(bluetoothProfile)) {
+ if (BLUETOOTH_PROFILES_MAP.containsKey(bluetoothProfile)) {
metricKey =
String.join(
"_",
BLUETOOTH_CONNECTION_LATENCY_METRIC_KEY,
- bluetoothProfilesMap.get(bluetoothProfile),
+ BLUETOOTH_PROFILES_MAP.get(bluetoothProfile),
"ms");
} else {
metricKey =
diff --git a/test_framework/com/android/tradefed/device/metric/BluetoothConnectionStateCollector.java b/test_framework/com/android/tradefed/device/metric/BluetoothConnectionStateCollector.java
index a55fcda..1fc4d57 100644
--- a/test_framework/com/android/tradefed/device/metric/BluetoothConnectionStateCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/BluetoothConnectionStateCollector.java
@@ -69,15 +69,15 @@
HashMap<String, List<Long>> btProfilesStates) {
for (String key : btProfilesStates.keySet()) {
List<Long> states = btProfilesStates.get(key);
- String metricKey2 = String.join("_", key, "connection_state_changed");
+ String metricKey = String.join("_", key, "connection_state_changed");
NumericValues values = NumericValues.newBuilder().addAllNumericValue(states).build();
Measurements measurements = Measurements.newBuilder().setNumericValues(values).build();
CLog.d(
"Adding metric on device %s with key %s and values %s",
- device.getSerialNumber(), metricKey2, states.toString());
+ device.getSerialNumber(), metricKey, states.toString());
runData.addMetricForDevice(
- device, metricKey2, Metric.newBuilder().setMeasurements(measurements));
+ device, metricKey, Metric.newBuilder().setMeasurements(measurements));
}
}
@@ -85,25 +85,28 @@
ITestDevice device,
EventMetricData metric,
HashMap<String, List<Long>> btProfilesStates) {
- Atom atom = metric.getAtom();
- if (atom.hasBluetoothConnectionStateChanged()) {
- BluetoothConnectionStateChanged bluetoothConnectionStateChanged =
- atom.getBluetoothConnectionStateChanged();
- int btState = bluetoothConnectionStateChanged.getState().getNumber();
- int btProfile = bluetoothConnectionStateChanged.getBtProfile();
+ Atom atom = metric.hasAtom() ? metric.getAtom() : metric.getAggregatedAtomInfo().getAtom();
+ if (!atom.hasBluetoothConnectionStateChanged()) {
CLog.d(
- "Processing connection state changed atom on device %s for profile number %d",
- device.getSerialNumber(), btProfile);
- if (bluetoothProfilesMap.containsKey(btProfile)) {
- String btProfileName = bluetoothProfilesMap.get(btProfile);
- List<Long> states =
- btProfilesStates.getOrDefault(btProfileName, new ArrayList<Long>());
- states.add((long) btState);
- btProfilesStates.put(btProfileName, states);
- CLog.d(
- "Processed connection state changed atom on device %s profile %s value %d",
- device.getSerialNumber(), btProfileName, btState);
- }
+ "Atom does not have a bluetooth_connection_state_changed info."
+ + " Skipping reporting");
+ return;
+ }
+ BluetoothConnectionStateChanged bluetoothConnectionStateChanged =
+ atom.getBluetoothConnectionStateChanged();
+ int btState = bluetoothConnectionStateChanged.getState().getNumber();
+ int btProfile = bluetoothConnectionStateChanged.getBtProfile();
+ CLog.d(
+ "Processing connection state changed atom on device %s for profile number %d",
+ device.getSerialNumber(), btProfile);
+ if (BLUETOOTH_PROFILES_MAP.containsKey(btProfile)) {
+ String btProfileName = BLUETOOTH_PROFILES_MAP.get(btProfile);
+ List<Long> states = btProfilesStates.getOrDefault(btProfileName, new ArrayList<Long>());
+ states.add((long) btState);
+ btProfilesStates.put(btProfileName, states);
+ CLog.d(
+ "Processed connection state changed atom on device %s profile %s value %d",
+ device.getSerialNumber(), btProfileName, btState);
}
}
}
diff --git a/test_framework/com/android/tradefed/device/metric/HostStatsdMetricCollector.java b/test_framework/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
index cbaeb0d..3528631 100644
--- a/test_framework/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
@@ -61,7 +61,7 @@
@Override
public void onTestRunStart(DeviceMetricData runData) throws DeviceNotAvailableException {
- if (mPerRun) {
+ if (mPerRun && mBinaryConfig != null) {
mDeviceConfigIds.clear();
startCollection();
}
@@ -69,7 +69,7 @@
@Override
public void onTestStart(DeviceMetricData testData) throws DeviceNotAvailableException {
- if (!mPerRun) {
+ if (!mPerRun && mBinaryConfig != null) {
mDeviceConfigIds.clear();
startCollection();
}
@@ -84,7 +84,7 @@
public void onTestEnd(
DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics)
throws DeviceNotAvailableException {
- if (!mPerRun) {
+ if (!mPerRun && mBinaryConfig != null) {
stopCollection(testData, !mTestFailed);
}
mTestCount++;
@@ -94,7 +94,7 @@
@Override
public void onTestRunEnd(DeviceMetricData runData, final Map<String, Metric> currentRunMetrics)
throws DeviceNotAvailableException {
- if (mPerRun) {
+ if (mPerRun && mBinaryConfig != null) {
stopCollection(runData, true);
}
}
diff --git a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
index 02a48c7..d1b9930 100644
--- a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
+++ b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
@@ -438,8 +438,8 @@
Map<String, Metric.Builder> convertedMetrics = new HashMap<String, Metric.Builder>();
List<String> keyPrefixes = new ArrayList<String>();
- // Keys that will be used to prefix the other keys in the same proto message.
- List<String> keyPrefixOtherFields = new ArrayList<>();
+ // Key that will be used to prefix the other keys in the same proto message.
+ String keyPrefixOtherFields = "";
// TODO(b/15014555): Cleanup the parsing logic.
for (Entry<FieldDescriptor, Object> entry : fields.entrySet()) {
@@ -448,7 +448,10 @@
// 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",
+ if (!keyPrefixOtherFields.isEmpty()) {
+ keyPrefixOtherFields = keyPrefixOtherFields.concat("-");
+ }
+ keyPrefixOtherFields = keyPrefixOtherFields.concat(String.format("%s-%s",
entry.getKey().getName().toString(), entry.getValue().toString()));
continue;
}
@@ -476,7 +479,10 @@
entry.getKey().getName().toString(),
entry.getValue().toString()));
if (mPerfettoPrefixKeyFields.contains(entry.getKey().toString())) {
- keyPrefixOtherFields.add(String.format("%s-%s",
+ if (!keyPrefixOtherFields.isEmpty()) {
+ keyPrefixOtherFields = keyPrefixOtherFields.concat("-");
+ }
+ keyPrefixOtherFields = keyPrefixOtherFields.concat(String.format("%s-%s",
entry.getKey().getName().toString(), entry.getValue().toString()));
}
}
@@ -556,9 +562,9 @@
// 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) {
+ if (!keyPrefixOtherFields.isEmpty()) {
for (Map.Entry<String, Metric.Builder> currentMetric : convertedMetrics.entrySet()) {
- additionalConvertedMetrics.put(String.format("%s-%s", prefix,
+ additionalConvertedMetrics.put(String.format("%s-%s", keyPrefixOtherFields,
currentMetric.getKey()), currentMetric.getValue());
}
}
diff --git a/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java b/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java
index df3ffd9..e23e10b 100644
--- a/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/ArtChrootPreparer.java
@@ -25,14 +25,15 @@
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
+import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
+import org.apache.commons.compress.archivers.zip.ZipFile;
+
import java.io.File;
import java.io.FileNotFoundException;
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;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.ListIterator;
/** Create chroot directory for ART tests. */
@OptionClass(alias = "art-chroot-preparer")
@@ -53,12 +54,16 @@
"/apex/com.android.os.statsd",
"/apex/com.android.runtime",
"/dev",
+ "/dev/cpuset",
+ "/etc",
"/linkerconfig",
"/proc",
"/sys",
"/system",
};
+ private List<String> mMountPoints = new ArrayList<>();
+
@Override
public void setUp(TestInformation testInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
@@ -76,6 +81,7 @@
for (String dir : MOUNTS) {
adbShell(device, "mkdir -p %s%s", CHROOT_PATH, dir);
adbShell(device, "mount --bind %s %s%s", dir, CHROOT_PATH, dir);
+ mMountPoints.add(CHROOT_PATH + dir);
}
// Activate APEXes in the chroot.
@@ -119,6 +125,7 @@
String loopbackDevice = adbShell(device, "losetup -f -s %s", deviceApexImg);
adbShell(device, "mkdir -p %s", deviceApexDir);
adbShell(device, "mount -o loop,ro %s %s", loopbackDevice, deviceApexDir);
+ mMountPoints.add(deviceApexDir);
}
@Override
@@ -142,17 +149,10 @@
}
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(
- String.format("Failed to parse mount command output: %s", mount));
- }
- if (matcher.group(2).startsWith(CHROOT_PATH)) {
- adbShell(device, "umount %s", matcher.group(2));
- }
+ // Unmount in the reverse order because there are nested mount points.
+ ListIterator listIterator = mMountPoints.listIterator(mMountPoints.size());
+ while (listIterator.hasPrevious()) {
+ adbShell(device, "umount %s", listIterator.previous());
}
adbShell(device, "rm -rf %s", CHROOT_PATH);
}
diff --git a/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java b/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java
index 5887e47..d9622e6 100644
--- a/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/DynamicSystemPreparer.java
@@ -15,8 +15,6 @@
*/
package com.android.tradefed.targetprep;
-import static com.android.tradefed.util.SparseImageUtil.SparseInputStream;
-
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.config.Option;
import com.android.tradefed.config.OptionClass;
@@ -30,8 +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.SparseImageUtil.SparseInputStream;
import com.android.tradefed.util.StreamUtil;
-
+import com.google.common.annotations.VisibleForTesting;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
@@ -40,6 +39,7 @@
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -54,7 +54,7 @@
* System Update.
*/
@OptionClass(alias = "dynamic-system-update")
-public class DynamicSystemPreparer extends BaseTargetPreparer implements ILabPreparer {
+public class DynamicSystemPreparer extends BaseTargetPreparer {
static final int DSU_MAX_WAIT_SEC = 10 * 60;
private static final int ANDROID_API_R = 30;
@@ -63,6 +63,7 @@
private static final String SYSTEM_EXT_IMAGE_NAME = "system_ext.img";
private static final String PRODUCT_IMAGE_NAME = "product.img";
+ private static final long COPY_STREAM_SIZE = 16 << 20;
private static final String DEST_PATH = "/sdcard/system.raw.gz";
private static final String DEST_ZIP_PATH = "/sdcard/system.zip";
@@ -92,6 +93,12 @@
description = "Number of GB to be allocated for DSU user-data.")
private long mUserDataSizeInGb = 16L; // 16GB
+ @Option(
+ name = "image-conversion-timeout",
+ description = "The timeout for decompressing, unsparsing, and compressing the images.",
+ isTimeVal = true)
+ private long mImageConversionTimeoutMs = 20 * 60 * 1000;
+
private boolean isDSURunning(ITestDevice device) throws DeviceNotAvailableException {
CollectingOutputReceiver receiver = new CollectingOutputReceiver();
device.executeShellCommand("gsi_tool status", receiver);
@@ -144,11 +151,31 @@
}
}
+ @VisibleForTesting
+ boolean hasTimedOut(long deadline) {
+ return System.currentTimeMillis() >= deadline;
+ }
+
+ private void copyStreamsWithDeadline(
+ InputStream inStream, OutputStream outStream, long size, long deadline)
+ throws IOException {
+ long totalCopiedSize = 0;
+ while (totalCopiedSize < size) {
+ if (hasTimedOut(deadline)) {
+ throw new IOException("Cannot copy streams within timeout.");
+ }
+ long copySize = Math.min(COPY_STREAM_SIZE, size - totalCopiedSize);
+ StreamUtil.copyStreams(inStream, outStream, 0, copySize);
+ totalCopiedSize += copySize;
+ }
+ }
+
@Override
public void setUp(TestInformation testInfo)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
ITestDevice device = testInfo.getDevice();
IBuildInfo buildInfo = testInfo.getBuildInfo();
+ final long imageConversionDeadline = System.currentTimeMillis() + mImageConversionTimeoutMs;
File systemImageZipFile = buildInfo.getFile(mSystemImageZipName);
if (systemImageZipFile == null) {
throw new BuildError(
@@ -180,7 +207,11 @@
try (FileOutputStream foStream = new FileOutputStream(systemImageGZ);
BufferedOutputStream boStream = new BufferedOutputStream(foStream);
GZIPOutputStream out = new GZIPOutputStream(boStream)) {
- StreamUtil.copyStreams(systemImageStream, out);
+ copyStreamsWithDeadline(
+ systemImageStream,
+ out,
+ systemImageStream.size(),
+ imageConversionDeadline);
}
dsuPushSrc = systemImageGZ;
@@ -201,7 +232,11 @@
BufferedOutputStream boStream = new BufferedOutputStream(foStream);
ZipOutputStream out = new ZipOutputStream(boStream)) {
out.putNextEntry(new ZipEntry(SYSTEM_IMAGE_NAME));
- StreamUtil.copyStreams(systemImageStream, out);
+ copyStreamsWithDeadline(
+ systemImageStream,
+ out,
+ systemImageStream.size(),
+ imageConversionDeadline);
out.closeEntry();
// Also look for any system-like partition images.
for (String imageName :
@@ -210,7 +245,8 @@
getUnsparsedImageStream(systemImageZipFile, imageName)) {
if (sis != null) {
out.putNextEntry(new ZipEntry(imageName));
- StreamUtil.copyStreams(sis, out);
+ copyStreamsWithDeadline(
+ sis, out, sis.size(), imageConversionDeadline);
out.closeEntry();
}
}
@@ -266,7 +302,10 @@
} catch (IOException e) {
CLog.e(e);
throw new TargetSetupError(
- "fail to install the DynamicSystemUpdate", e, device.getDeviceDescriptor());
+ "Fail to create image archive.",
+ e,
+ device.getDeviceDescriptor(),
+ InfraErrorIdentifier.FAIL_TO_CREATE_FILE);
} finally {
for (File tempFile : tempFiles) {
FileUtil.deleteFile(tempFile);
diff --git a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
index c22487a..a3ed846 100644
--- a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
@@ -28,6 +28,7 @@
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.observatory.IDiscoverDependencies;
import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.result.error.ErrorIdentifier;
import com.android.tradefed.result.error.InfraErrorIdentifier;
@@ -59,7 +60,7 @@
*/
@OptionClass(alias = "push-file")
public class PushFilePreparer extends BaseTargetPreparer
- implements IAbiReceiver, IInvocationContextReceiver {
+ implements IAbiReceiver, IInvocationContextReceiver, IDiscoverDependencies {
private static final String MEDIA_SCAN_INTENT =
"am broadcast -a android.intent.action.MEDIA_MOUNTED -d file://%s "
+ "--receiver-include-background";
@@ -465,4 +466,19 @@
}
}
}
+
+ @Override
+ public Set<String> reportDependencies() {
+ Set<String> deps = new HashSet<>();
+ try {
+ for (File f : getPushSpecs(null).values()) {
+ if (!f.exists()) {
+ deps.add(f.getName());
+ }
+ }
+ } catch (TargetSetupError e) {
+ CLog.e(e);
+ }
+ return deps;
+ }
}
diff --git a/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java b/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
index e92e3ab..2b4ed98 100644
--- a/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/PythonVirtualenvPreparer.java
@@ -177,7 +177,16 @@
throw new TargetSetupError(
"virtualenv is not installed.", device.getDeviceDescriptor());
}
- String version = stdout.split(" ")[1];
+ CLog.d("Output from virtualenv --version: %s", stdout);
+ String[] split = stdout.split(" ");
+ if (split.length < 2) {
+ throw new TargetSetupError(
+ String.format(
+ "Something is wrong with your installed virtualenv version: %s",
+ stdout),
+ device.getDeviceDescriptor());
+ }
+ String version = split[1];
int majorVersion = Integer.parseInt(version.split("\\.")[0]);
if (majorVersion < 20) {
throw new TargetSetupError(
diff --git a/test_framework/com/android/tradefed/targetprep/WaitForDeviceDatetimePreparer.java b/test_framework/com/android/tradefed/targetprep/WaitForDeviceDatetimePreparer.java
index fb32012..daf9d21 100644
--- a/test_framework/com/android/tradefed/targetprep/WaitForDeviceDatetimePreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/WaitForDeviceDatetimePreparer.java
@@ -21,6 +21,7 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.error.DeviceErrorIdentifier;
import com.android.tradefed.util.IRunUtil;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.TimeUtil;
@@ -62,9 +63,10 @@
if (mForceSetupError) {
throw new TargetSetupError(
String.format(
- "datetime on device is incorrect after " + "wait timeout of '%s'",
+ "datetime on device is incorrect after wait timeout of '%s'",
TimeUtil.formatElapsedTime(mDatetimeWaitTimeout)),
- device.getDeviceDescriptor());
+ device.getDeviceDescriptor(),
+ DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
} else {
CLog.w("datetime on device is incorrect after wait timeout.");
}
diff --git a/test_framework/com/android/tradefed/targetprep/multi/PairingMultiTargetPreparer.java b/test_framework/com/android/tradefed/targetprep/multi/PairingMultiTargetPreparer.java
index 7a58d34..f636534 100644
--- a/test_framework/com/android/tradefed/targetprep/multi/PairingMultiTargetPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/multi/PairingMultiTargetPreparer.java
@@ -22,6 +22,7 @@
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.result.error.DeviceErrorIdentifier;
import com.android.tradefed.result.error.InfraErrorIdentifier;
import com.android.tradefed.targetprep.BuildError;
@@ -89,12 +90,14 @@
throws TargetSetupError, BuildError, DeviceNotAvailableException {
setDeviceInfos(testInformation.getContext().getDeviceBuildMap());
try {
+ CLog.d("Enabling bluetooth on %s", mPrimaryDevice.getDeviceDescriptor());
if (!mUtil.enable(mPrimaryDevice)) {
throw new TargetSetupError(
"Failed to enable Bluetooth",
mPrimaryDevice.getDeviceDescriptor(),
DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
+ CLog.d("Enabling bluetooth on %s", mCompanionDevice.getDeviceDescriptor());
if (!mUtil.enable(mCompanionDevice)) {
throw new TargetSetupError(
"Failed to enable Bluetooth",
@@ -102,6 +105,7 @@
DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
mUtil.setBtPairTimeout(mPairingTimeout);
+ CLog.d("Starting pairing bluetooth on %s", mPrimaryDevice.getDeviceDescriptor());
if (!mUtil.pair(mPrimaryDevice, mCompanionDevice)) {
throw new TargetSetupError(
"Bluetooth pairing failed.",
@@ -111,6 +115,7 @@
// Always enable PBAP between primary and companion devices in case it's not enabled
// For now, assume PBAP client profile is always on primary device, and enable PBAP on
// companion device.
+ CLog.d("Enabling PBAP on %s", mCompanionDevice.getDeviceDescriptor());
if (!mUtil.changeProfileAccessPermission(
mCompanionDevice,
mPrimaryDevice,
@@ -121,6 +126,7 @@
mCompanionDevice.getDeviceDescriptor(),
DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
+ CLog.d("Enabling PBAP_CLIENT on %s", mPrimaryDevice.getDeviceDescriptor());
if (!mUtil.setProfilePriority(
mPrimaryDevice,
mCompanionDevice,
@@ -131,6 +137,7 @@
mPrimaryDevice.getDeviceDescriptor(),
DeviceErrorIdentifier.DEVICE_UNEXPECTED_RESPONSE);
}
+ CLog.d("Connecting to profiles");
if (mConnectDevices && mProfiles.size() > 0) {
if (!mUtil.connect(mPrimaryDevice, mCompanionDevice, mProfiles)) {
throw new TargetSetupError(
diff --git a/test_framework/com/android/tradefed/testtype/ArtRunTest.java b/test_framework/com/android/tradefed/testtype/ArtRunTest.java
index bff97f9..c0a47de 100644
--- a/test_framework/com/android/tradefed/testtype/ArtRunTest.java
+++ b/test_framework/com/android/tradefed/testtype/ArtRunTest.java
@@ -20,6 +20,8 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.FileInputStreamSource;
@@ -557,9 +559,15 @@
* @return An optional error message, empty if the Checker invocation was successful
*/
protected Optional<String> runChecker(String[] checkerCommandLine) {
- CLog.d("About to run Checker command: %s", String.join(" ", checkerCommandLine));
+ String checkerCommandLineString = String.join(" ", checkerCommandLine);
+ CLog.d("About to run Checker command: %s", checkerCommandLineString);
+ long startTime = System.currentTimeMillis();
CommandResult result =
RunUtil.getDefault().runTimedCmd(CHECKER_TIMEOUT_MS, checkerCommandLine);
+ long duration = System.currentTimeMillis() - startTime;
+ CLog.i("Checker command `%s` executed in %s ms", checkerCommandLineString, duration);
+ InvocationMetricLogger.addInvocationMetrics(
+ InvocationMetricKey.ART_RUN_TEST_CHECKER_COMMAND_TIME_MS, duration);
if (result.getStatus() != CommandStatus.SUCCESS) {
String errorMessage;
if (result.getStatus() == CommandStatus.TIMED_OUT) {
diff --git a/test_framework/com/android/tradefed/testtype/HostGTest.java b/test_framework/com/android/tradefed/testtype/HostGTest.java
index be79fed..1ccb70f 100644
--- a/test_framework/com/android/tradefed/testtype/HostGTest.java
+++ b/test_framework/com/android/tradefed/testtype/HostGTest.java
@@ -24,12 +24,16 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.error.HarnessRuntimeException;
import com.android.tradefed.invoker.TestInformation;
+import com.android.tradefed.invoker.TestInvocation;
import com.android.tradefed.log.ITestLogger;
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.LogDataType;
import com.android.tradefed.result.error.TestErrorIdentifier;
+import com.android.tradefed.result.proto.TestRecordProto.FailureStatus;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
@@ -44,6 +48,7 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
@@ -262,11 +267,14 @@
File gTestFile = null;
try {
gTestFile = FileUtil.findFile(moduleName, getAbi(), scanDirs.toArray(new File[] {}));
+ if (gTestFile != null && gTestFile.isDirectory()) {
+ // Search the exact file in subdir
+ gTestFile = FileUtil.findFile(moduleName, getAbi(), gTestFile);
+ }
} catch (IOException e) {
throw new RuntimeException(e);
}
-
- if (gTestFile == null || gTestFile.isDirectory()) {
+ if (gTestFile == null) {
// If we ended up here we most likely failed to find the proper file as is, so we
// search for it with a potential suffix (which is allowed).
try {
@@ -288,8 +296,12 @@
}
if (!gTestFile.canExecute()) {
- throw new RuntimeException(
- String.format("%s is not executable!", gTestFile.getAbsolutePath()));
+ reportFailure(
+ listener,
+ gTestFile.getName(),
+ new RuntimeException(
+ String.format("%s is not executable!", gTestFile.getAbsolutePath())));
+ return;
}
listener = getGTestListener(listener);
@@ -299,4 +311,15 @@
CLog.i("Running gtest %s %s", gTestFile.getName(), flags);
runTest(resultParser, gTestFile, flags, listener);
}
+
+ private void reportFailure(
+ ITestInvocationListener listener, String runName, RuntimeException exception) {
+ listener.testRunStarted(runName, 0);
+ listener.testRunFailed(createFailure(exception));
+ listener.testRunEnded(0L, new HashMap<String, Metric>());
+ }
+
+ private FailureDescription createFailure(Exception e) {
+ return TestInvocation.createFailureFromException(e, FailureStatus.TEST_FAILURE);
+ }
}
diff --git a/test_framework/com/android/tradefed/testtype/HostTest.java b/test_framework/com/android/tradefed/testtype/HostTest.java
index 32aff60..72e8a18 100644
--- a/test_framework/com/android/tradefed/testtype/HostTest.java
+++ b/test_framework/com/android/tradefed/testtype/HostTest.java
@@ -131,13 +131,13 @@
public static final String SET_OPTION_NAME = "set-option";
public static final String SET_OPTION_DESC =
- "Options to be passed down to the class under test, key and value should be "
- + "separated by colon \":\"; for example, if class under test supports "
- + "\"--iteration 1\" from a command line, it should be passed in as"
- + " \"--set-option iteration:1\" or \"--set-option iteration:key=value\" for "
- + "passing options to map; escaping of \"=\" is currently not supported."
- + "A particular class can be targetted by specifying it. "
- + "\" --set-option <fully qualified class>:<option name>:<option value>\"";
+ "Options to be passed down to the class under test, key and value should be separated"
+ + " by colon \":\"; for example, if class under test supports \"--iteration 1\""
+ + " from a command line, it should be passed in as \"--set-option iteration:1\" or"
+ + " \"--set-option iteration:key=value\" for passing options to map. Values that"
+ + " contain \":\" or \"=\" can be escaped with a backslash. A particular class can"
+ + " be targeted by specifying it. \" --set-option <fully qualified class>:<option"
+ + " name>:<option value>\"";
@Option(name = SET_OPTION_NAME, description = SET_OPTION_DESC)
private List<String> mKeyValueOptions = new ArrayList<>();
@@ -1154,18 +1154,24 @@
private static void injectOption(OptionSetter setter, String origItem, String key, String value)
throws ConfigurationException {
- if (value.contains("=")) {
- String[] values = value.split("=");
- if (values.length != 2) {
- throw new RuntimeException(
- String.format(
- "set-option provided '%s' format is invalid. Only one "
- + "'=' is allowed",
- origItem));
- }
- setter.setOptionValue(key, values[0], values[1]);
+ String esc = "\\";
+ String delim = "=";
+ String regex = "(?<!" + Pattern.quote(esc) + ")" + Pattern.quote(delim);
+ String escDelim = Pattern.quote(esc) + Pattern.quote(delim);
+ String[] values = value.split(regex);
+ if (values.length == 1) {
+ setter.setOptionValue(key, values[0].replaceAll(escDelim, delim));
+ } else if (values.length == 2) {
+ setter.setOptionValue(
+ key,
+ values[0].replaceAll(escDelim, delim),
+ values[1].replaceAll(escDelim, delim));
} else {
- setter.setOptionValue(key, value);
+ throw new RuntimeException(
+ String.format(
+ "set-option provided '%s' format is invalid. Only one "
+ + "'=' is allowed",
+ origItem));
}
}
diff --git a/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java b/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java
index b7effd7..f525a1c 100644
--- a/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java
+++ b/test_framework/com/android/tradefed/testtype/IsolatedHostTest.java
@@ -179,6 +179,8 @@
public void run(TestInformation testInfo, ITestInvocationListener listener)
throws DeviceNotAvailableException {
mReportedFailure = false;
+ Process isolationRunner = null;
+
try {
mServer = new ServerSocket(0);
mServer.setSoTimeout(mSocketTimeout);
@@ -204,8 +206,7 @@
mSubprocessLog = FileUtil.createTempFile("subprocess-logs", "");
runner.setRedirectStderrToStdout(true);
- Process isolationRunner =
- runner.runCmdInBackground(Redirect.to(mSubprocessLog), cmdArgs);
+ isolationRunner = runner.runCmdInBackground(Redirect.to(mSubprocessLog), cmdArgs);
CLog.v("Started subprocess.");
Socket socket = mServer.accept();
@@ -238,9 +239,7 @@
.setCommand(RunnerOp.RUNNER_OP_STOP)
.build()
.writeDelimitedTo(socket.getOutputStream());
- // Ensure the subprocess finishes
- isolationRunner.waitFor(1, TimeUnit.MINUTES);
- } catch (IOException | InterruptedException e) {
+ } catch (IOException e) {
if (!mReportedFailure) {
// Avoid overriding the failure
FailureDescription failure =
@@ -250,6 +249,40 @@
listener.testRunEnded(0L, new HashMap<String, Metric>());
}
} finally {
+ try {
+ // Ensure the subprocess finishes
+ if (isolationRunner != null) {
+ if (isolationRunner.isAlive()) {
+ CLog.v(
+ "Subprocess is still alive after test phase - waiting for it to"
+ + " terminate.");
+ isolationRunner.waitFor(10, TimeUnit.SECONDS);
+ if (isolationRunner.isAlive()) {
+ CLog.v(
+ "Subprocess is still alive after test phase - requesting"
+ + " termination.");
+ // Isolation runner still alive for some reason, try to kill it
+ isolationRunner.destroy();
+ isolationRunner.waitFor(10, TimeUnit.SECONDS);
+
+ // If the process is still alive after trying to kill it nicely
+ // then end it forcibly.
+ if (isolationRunner.isAlive()) {
+ CLog.v(
+ "Subprocess is still alive after test phase - forcibly"
+ + " terminating it.");
+ isolationRunner.destroyForcibly();
+ }
+ }
+ }
+ }
+ } catch (InterruptedException e) {
+ throw new HarnessRuntimeException(
+ "Interrupted while stopping subprocess",
+ e,
+ InfraErrorIdentifier.INTERRUPTED_DURING_SUBPROCESS_SHUTDOWN);
+ }
+
FileUtil.deleteFile(mIsolationJar);
}
}
diff --git a/test_framework/com/android/tradefed/testtype/pandora/PtsBotTest.java b/test_framework/com/android/tradefed/testtype/pandora/PtsBotTest.java
index 4ec8d01..ce431ba 100644
--- a/test_framework/com/android/tradefed/testtype/pandora/PtsBotTest.java
+++ b/test_framework/com/android/tradefed/testtype/pandora/PtsBotTest.java
@@ -28,7 +28,9 @@
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.ITestFilterReceiver;
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;
@@ -60,6 +62,8 @@
private static final int HCI_ROOTCANAL_PORT = 6211;
private static final int HCI_PROXY_PORT = 1234;
+ private IRunUtil mRunUtil = new RunUtil();
+
@Option(name = "pts-bot-path", description = "pts-bot binary path.")
private File ptsBotPath = new File("pts-bot");
@@ -134,6 +138,28 @@
return physical ? HCI_PROXY_PORT : HCI_ROOTCANAL_PORT;
}
+ private void displayPtsBotVersion() {
+ CommandResult c;
+ c = mRunUtil.runTimedCmd(5000, "which", ptsBotPath.getPath());
+ if (c.getStatus() != CommandStatus.SUCCESS) {
+ CLog.e("Failed to get pts-bot path");
+ CLog.e(
+ "Status: %s\nStdout: %s\nStderr: %s",
+ c.getStatus(), c.getStdout(), c.getStderr());
+ throw new RuntimeException("Failed to get pts-bot path. Error:\n" + c.getStderr());
+ }
+ String ptsBotAbsolutePath = c.getStdout().trim();
+ c = mRunUtil.runTimedCmd(5000, ptsBotAbsolutePath, "--version");
+ if (c.getStatus() != CommandStatus.SUCCESS) {
+ CLog.e("Failed to get pts-bot version");
+ CLog.e(
+ "Status: %s\nStdout: %s\nStderr: %s",
+ c.getStatus(), c.getStdout(), c.getStderr());
+ throw new RuntimeException("Failed to get pts-bot version. Error:\n" + c.getStderr());
+ }
+ CLog.d("pts-bot version: %s", c.getStdout().trim());
+ }
+
@Override
public void run(TestInformation testInfo, ITestInvocationListener listener)
throws DeviceNotAvailableException {
@@ -163,6 +189,11 @@
}
}
+ // Test ressources files are not executable
+ ptsBotPath.setExecutable(true);
+
+ displayPtsBotVersion();
+
CLog.i("Tests config file: %s", testsConfigFile.getPath());
CLog.i("Profiles to be tested: %s", profiles);
@@ -354,9 +385,7 @@
}
private ProcessBuilder ptsBot(TestInformation testInfo, String... args) {
- List<String> command = new ArrayList();
-
- ptsBotPath.setExecutable(true);
+ List<String> command = new ArrayList<>();
command.add(ptsBotPath.getPath());
command.add("-c");
@@ -381,7 +410,7 @@
if (venvDir != null) {
String packagePath =
PythonVirtualenvHelper.getPackageInstallLocation(
- new RunUtil(), venvDir.getAbsolutePath());
+ mRunUtil, venvDir.getAbsolutePath());
pythonPath += ":" + packagePath;
}
diff --git a/test_observatory/com/android/tradefed/observatory/TestDiscoveryExecutor.java b/test_observatory/com/android/tradefed/observatory/TestDiscoveryExecutor.java
index bc76f28..84323f4 100644
--- a/test_observatory/com/android/tradefed/observatory/TestDiscoveryExecutor.java
+++ b/test_observatory/com/android/tradefed/observatory/TestDiscoveryExecutor.java
@@ -25,8 +25,9 @@
import com.android.tradefed.testtype.suite.BaseTestSuite;
import com.android.tradefed.testtype.suite.SuiteTestFilter;
-import org.json.JSONArray;
-import org.json.JSONObject;
+import com.google.gson.Gson;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
import java.util.ArrayList;
import java.util.Collections;
@@ -77,39 +78,9 @@
* Discover test dependencies base on command line args.
*
* @param args the command line args of the test.
- * @return A JSON string with test module names.
- */
- @Deprecated(forRemoval = true)
- public String discoverDependencies(String[] args) throws Exception {
- // Create IConfiguration base on command line args.
- IConfiguration config = getConfiguration(args);
- List<IRemoteTest> tests = config.getTests();
-
- // Tests could be empty if input args are corrupted.
- if (tests == null || tests.isEmpty()) {
- throw new IllegalStateException(
- "Tradefed Observatory discovered no tests from the IConfiguration created from"
- + " command line args.");
- }
- Set<String> allDependencies = new HashSet<>(discoverTestModulesFromTests(tests));
- allDependencies.addAll(discoverDependencies(config));
- List<String> allDependenciesList = new ArrayList<>(allDependencies);
-
- // Sort it so that it's always in the same order
- Collections.sort(allDependenciesList);
- JSONObject jsonObject = new JSONObject();
- JSONArray jsonArray = new JSONArray(allDependenciesList);
- jsonObject.put(TestDiscoveryInvoker.TEST_DEPENDENCIES_LIST_KEY, jsonArray);
- return jsonObject.toString();
- }
-
- /**
- * Discover test dependencies base on command line args.
- *
- * @param args the command line args of the test.
* @return A JSON string with one test module names array and one other test dependency array.
*/
- public String discoverDependenciesV2(String[] args) throws Exception {
+ public String discoverDependencies(String[] args) throws Exception {
// Create IConfiguration base on command line args.
IConfiguration config = getConfiguration(args);
List<IRemoteTest> tests = config.getTests();
@@ -122,15 +93,22 @@
}
List<String> testModules = new ArrayList<>(discoverTestModulesFromTests(tests));
+
+ if (testModules == null || testModules.isEmpty()) {
+ throw new TestDiscoveryException(
+ "Tradefed Observatory discovered no test modules from the test config, it"
+ + " might be component-based.");
+ }
List<String> testDependencies = new ArrayList<>(discoverDependencies(config));
Collections.sort(testModules);
Collections.sort(testDependencies);
- JSONObject jsonObject = new JSONObject();
- JSONArray testModulesArray = new JSONArray(testModules);
- JSONArray testDependenciesArray = new JSONArray(testDependencies);
- jsonObject.put(TestDiscoveryInvoker.TEST_MODULES_LIST_KEY, testModulesArray);
- jsonObject.put(TestDiscoveryInvoker.TEST_DEPENDENCIES_LIST_KEY, testDependenciesArray);
+ JsonObject jsonObject = new JsonObject();
+ Gson gson = new Gson();
+ JsonArray testModulesArray = gson.toJsonTree(testModules).getAsJsonArray();
+ JsonArray testDependenciesArray = gson.toJsonTree(testDependencies).getAsJsonArray();
+ jsonObject.add(TestDiscoveryInvoker.TEST_MODULES_LIST_KEY, testModulesArray);
+ jsonObject.add(TestDiscoveryInvoker.TEST_DEPENDENCIES_LIST_KEY, testDependenciesArray);
return jsonObject.toString();
}
diff --git a/test_observatory/com/android/tradefed/observatory/TestDiscoveryInvoker.java b/test_observatory/com/android/tradefed/observatory/TestDiscoveryInvoker.java
index ed2de02..9eb88b1 100644
--- a/test_observatory/com/android/tradefed/observatory/TestDiscoveryInvoker.java
+++ b/test_observatory/com/android/tradefed/observatory/TestDiscoveryInvoker.java
@@ -57,6 +57,7 @@
public class TestDiscoveryInvoker {
private final IConfiguration mConfiguration;
+ private final String mDefaultConfigName;
private final File mRootDir;
public static final String TRADEFED_OBSERVATORY_ENTRY_PATH =
TestDiscoveryExecutor.class.getName();
@@ -68,40 +69,21 @@
return RunUtil.getDefault();
}
+ /** Creates an {@link TestDiscoveryInvoker} with a {@link IConfiguration} and root directory. */
public TestDiscoveryInvoker(IConfiguration config, File rootDir) {
mConfiguration = config;
+ mDefaultConfigName = null;
mRootDir = rootDir;
}
/**
- * Retrieve a list of test module names by using Tradefed Observatory.
- *
- * @return A list of test module names.
- * @throws IOException
- * @throws JSONException
- * @throws ConfigurationException
+ * Creates an {@link TestDiscoveryInvoker} with a {@link IConfiguration}, test launcher's
+ * default config name and root directory.
*/
- @Deprecated(forRemoval = true)
- public List<String> discoverTestModuleNames()
- throws IOException, JSONException, ConfigurationException {
- List<String> testModuleNames = new ArrayList<>();
- // Build the classpath base on test root directory which should contain all the jars
- String classPath = buildClasspath(mRootDir);
- // Build command line args to query the tradefed.jar in the root directory
- List<String> args = buildJavaCmdForXtsDiscovery(classPath);
- String[] subprocessArgs = args.toArray(new String[args.size()]);
- CommandResult res = getRunUtil().runTimedCmd(20000, subprocessArgs);
- if (res.getExitCode() != 0 || !res.getStatus().equals(CommandStatus.SUCCESS)) {
- CLog.e(
- "Tradefed observatory error, unable to discover test module names. command"
- + " used: %s error: %s",
- Joiner.on(" ").join(subprocessArgs), res.getStderr());
- return testModuleNames;
- }
- String stdout = res.getStdout();
- CLog.i(String.format("Tradefed Observatory returned in stdout: %s", stdout));
- testModuleNames.addAll(parseTestModules(stdout));
- return testModuleNames;
+ public TestDiscoveryInvoker(IConfiguration config, String defaultConfigName, File rootDir) {
+ mConfiguration = config;
+ mDefaultConfigName = defaultConfigName;
+ mRootDir = rootDir;
}
/**
@@ -172,12 +154,21 @@
String configName = ctsParserSettings.mConfigName;
if (configName == null) {
- throw new ConfigurationException(
- String.format(
- "Failed to extract config-name from parent test command options,"
- + " unable to build args to invoke tradefed observatory. Parent"
- + " test command options is: %s",
- fullCommandLineArgs));
+ if (mDefaultConfigName == null) {
+ throw new ConfigurationException(
+ String.format(
+ "Failed to extract config-name from parent test command options,"
+ + " unable to build args to invoke tradefed observatory."
+ + " Parent test command options is: %s",
+ fullCommandLineArgs));
+ } else {
+ CLog.i(
+ String.format(
+ "No config name provided in the command args, use default config"
+ + " name %s",
+ mDefaultConfigName));
+ configName = mDefaultConfigName;
+ }
}
List<String> args = new ArrayList<>();
args.add(SystemUtil.getRunningJavaBinaryPath().getAbsolutePath());