Merge "Change sl4a request/response log message to verbose"
diff --git a/atest/atest_arg_parser.py b/atest/atest_arg_parser.py
index e4dee5f..0a6e615 100644
--- a/atest/atest_arg_parser.py
+++ b/atest/atest_arg_parser.py
@@ -312,9 +312,6 @@
--collect-tests-only
{COLLECT_TESTS_ONLY}
- --dry-run
- {DRY_RUN}
-
--info
{INFO}
@@ -329,6 +326,9 @@
[ Dry-Run and Caching ]
+ --dry-run
+ {DRY_RUN}
+
-c, --clear-cache
{CLEAR_CACHE}
diff --git a/device_build_interfaces/com/android/tradefed/device/INativeDevice.java b/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
index 9ec2270..66f3f3b 100644
--- a/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
+++ b/device_build_interfaces/com/android/tradefed/device/INativeDevice.java
@@ -125,6 +125,16 @@
public boolean setProperty(String propKey, String propValue) throws DeviceNotAvailableException;
/**
+ * Retrieve the given fastboot variable value from the device.
+ *
+ * @param variableName the variable name
+ * @return the property value or <code>null</code> if it does not exist
+ * @throws DeviceNotAvailableException, UnsupportedOperationException
+ */
+ public String getFastbootVariable(String variableName)
+ throws DeviceNotAvailableException, UnsupportedOperationException;
+
+ /**
* Convenience method to get the bootloader version of this device.
* <p/>
* Will attempt to retrieve bootloader version from the device's current state. (ie if device
@@ -569,6 +579,13 @@
public boolean isRuntimePermissionSupported() throws DeviceNotAvailableException;
/**
+ * Check whether platform on device supports app enumeration
+ * @return True if app enumeration is supported, false otherwise
+ * @throws DeviceNotAvailableException
+ */
+ public boolean isAppEnumerationSupported() throws DeviceNotAvailableException;
+
+ /**
* Retrieves a file off device.
*
* @param remoteFilePath the absolute path to file on device.
@@ -925,6 +942,16 @@
public void reboot(@Nullable String reason) throws DeviceNotAvailableException;
/**
+ * Reboots the device into fastbootd mode.
+ *
+ * <p>Blocks until device is in fastbootd mode.
+ *
+ * @throws DeviceNotAvailableException if connection with device is lost and cannot be
+ * recovered.
+ */
+ public void rebootIntoFastbootd() throws DeviceNotAvailableException;
+
+ /**
* Reboots only userspace part of device.
*
* <p>Blocks until device becomes available.
diff --git a/device_build_interfaces/com/android/tradefed/device/StubLocalAndroidVirtualDevice.java b/device_build_interfaces/com/android/tradefed/device/StubLocalAndroidVirtualDevice.java
index 5066999..5ce00ec 100644
--- a/device_build_interfaces/com/android/tradefed/device/StubLocalAndroidVirtualDevice.java
+++ b/device_build_interfaces/com/android/tradefed/device/StubLocalAndroidVirtualDevice.java
@@ -21,9 +21,9 @@
* A placeholder {@link IDevice} used by {@link DeviceManager} to allocate when {@link
* DeviceSelectionOptions#localVirtualDeviceRequested()} is <code>true</code>
*/
-public class StubLocalAndroidVirtualDevice extends StubDevice {
+public class StubLocalAndroidVirtualDevice extends TcpDevice {
public StubLocalAndroidVirtualDevice(String serial) {
- super(serial, false);
+ super(serial);
}
}
diff --git a/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java b/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
index 055cc73..3e37f20 100644
--- a/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
+++ b/device_build_interfaces/com/android/tradefed/device/contentprovider/ContentProviderHandler.java
@@ -276,9 +276,11 @@
public static String createEscapedContentUri(String deviceFilePath) {
String escapedFilePath = deviceFilePath;
try {
- // Escape the path then encode it.
- String escaped = UrlEscapers.urlPathSegmentEscaper().escape(deviceFilePath);
- escapedFilePath = URLEncoder.encode(escaped, "UTF-8");
+ // Encode the path then escape it. This logic must invert the logic in
+ // ManagedFileContentProvider.getFileForUri. That calls to Uri.getPath() and then
+ // URLDecoder.decode(), so this must invert each of those two steps and switch the order
+ String encoded = URLEncoder.encode(deviceFilePath, "UTF-8");
+ escapedFilePath = UrlEscapers.urlPathSegmentEscaper().escape(encoded);
} catch (UnsupportedEncodingException e) {
CLog.e(e);
}
diff --git a/invocation_interfaces/com/android/tradefed/invoker/IInvocationContext.java b/invocation_interfaces/com/android/tradefed/invoker/IInvocationContext.java
index 537daf4..5603712 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/IInvocationContext.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/IInvocationContext.java
@@ -23,6 +23,7 @@
import com.android.tradefed.util.UniqueMultiMap;
import java.io.Serializable;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -36,6 +37,7 @@
/** Key used for storing associated invocation ID. */
public static final String INVOCATION_ID = "invocation-id";
+ @Deprecated
public enum TimingEvent {
FETCH_BUILD,
SETUP;
@@ -145,10 +147,16 @@
public MultiMap<String, String> getAttributes();
/** Add a invocation timing metric. */
- public void addInvocationTimingMetric(TimingEvent timingEvent, Long durationMillis);
+ @Deprecated
+ public default void addInvocationTimingMetric(TimingEvent timingEvent, Long durationMillis) {
+ // Do nothing
+ }
/** Returns the map containing the invocation timing metrics. */
- public Map<TimingEvent, Long> getInvocationTimingMetrics();
+ @Deprecated
+ public default Map<TimingEvent, Long> getInvocationTimingMetrics() {
+ return new HashMap<>();
+ }
/** Sets the descriptor associated with the test configuration that launched the invocation */
public void setConfigurationDescriptor(ConfigurationDescriptor configurationDescriptor);
diff --git a/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java b/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java
index 42eec08..4427fe5 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/logger/CurrentInvocation.java
@@ -87,7 +87,9 @@
/** Clear the invocation info for an invocation. */
public static void clearInvocationInfos() {
ThreadGroup group = Thread.currentThread().getThreadGroup();
- mPerGroupInfo.remove(group);
+ synchronized (mPerGroupInfo) {
+ mPerGroupInfo.remove(group);
+ }
}
/**
diff --git a/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java b/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
index f9718cb..8b8b802 100644
--- a/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
+++ b/invocation_interfaces/com/android/tradefed/invoker/logger/InvocationMetricLogger.java
@@ -137,6 +137,8 @@
/** Clear the invocation metrics for an invocation. */
public static void clearInvocationMetrics() {
ThreadGroup group = Thread.currentThread().getThreadGroup();
- mPerGroupMetrics.remove(group);
+ synchronized (mPerGroupMetrics) {
+ mPerGroupMetrics.remove(group);
+ }
}
}
diff --git a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
index f9d558a..fa09182 100644
--- a/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
+++ b/src/com/android/tradefed/cluster/ClusterCommandScheduler.java
@@ -17,6 +17,7 @@
import com.android.ddmlib.testrunner.TestResult.TestStatus;
import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.cluster.ClusterHostEvent.HostEventType;
import com.android.tradefed.command.CommandScheduler;
import com.android.tradefed.command.ICommandScheduler;
import com.android.tradefed.command.remote.DeviceDescriptor;
@@ -33,6 +34,7 @@
import com.android.tradefed.device.battery.IBatteryInfo.BatteryState;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.CollectingTestListener;
import com.android.tradefed.result.ITestSummaryListener;
@@ -42,7 +44,6 @@
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.QuotationAwareTokenizer;
-import com.android.tradefed.cluster.ClusterHostEvent.HostEventType;
import com.google.common.primitives.Ints;
import org.json.JSONException;
@@ -56,7 +57,6 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
-import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.RejectedExecutionHandler;
@@ -289,20 +289,17 @@
mError = "build not found";
}
- long fetchBuildTimeMillis = -1;
- long setupTimeMillis = -1;
- if (metadata != null && metadata.getInvocationTimingMetrics() != null) {
- for (Entry<IInvocationContext.TimingEvent, Long> entry :
- metadata.getInvocationTimingMetrics().entrySet()) {
- switch (entry.getKey()) {
- case FETCH_BUILD:
- fetchBuildTimeMillis = entry.getValue();
- break;
- case SETUP:
- setupTimeMillis = entry.getValue();
- break;
- }
- }
+ String fetchBuildTimeMillis = "-1";
+ String setupTimeMillis = "-1";
+ if (metadata != null) {
+ fetchBuildTimeMillis =
+ metadata.getAttributes()
+ .getUniqueMap()
+ .get(InvocationMetricKey.FETCH_BUILD.toString());
+ setupTimeMillis =
+ metadata.getAttributes()
+ .getUniqueMap()
+ .get(InvocationMetricKey.SETUP.toString());
}
// Stop heartbeat thread before sending InvocationCompleted event.
@@ -318,10 +315,9 @@
.setData(ClusterCommandEvent.DATA_KEY_SUMMARY, mSummary)
.setData(
ClusterCommandEvent.DATA_KEY_FETCH_BUILD_TIME_MILLIS,
- Long.toString(fetchBuildTimeMillis))
+ fetchBuildTimeMillis)
.setData(
- ClusterCommandEvent.DATA_KEY_SETUP_TIME_MILLIS,
- Long.toString(setupTimeMillis))
+ ClusterCommandEvent.DATA_KEY_SETUP_TIME_MILLIS, setupTimeMillis)
.setData(
ClusterCommandEvent.DATA_KEY_TOTAL_TEST_COUNT,
Integer.toString(getNumTotalTests()))
diff --git a/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java b/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
index cce0bbf..66a92fb 100644
--- a/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
+++ b/src/com/android/tradefed/device/LocalAndroidVirtualDevice.java
@@ -20,6 +20,7 @@
import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.build.IDeviceBuildInfo;
+import com.android.tradefed.device.cloud.GceAvdInfo;
import com.android.tradefed.log.ITestLogger;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.result.FileInputStreamSource;
@@ -36,6 +37,7 @@
import com.android.tradefed.util.ZipUtil;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
+import com.google.common.net.HostAndPort;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -43,7 +45,9 @@
import java.util.List;
/** The class for local virtual devices running on TradeFed host. */
-public class LocalAndroidVirtualDevice extends TestDevice implements ITestLoggerReceiver {
+public class LocalAndroidVirtualDevice extends RemoteAndroidDevice implements ITestLoggerReceiver {
+
+ private static final int INVALID_PORT = 0;
// Environment variables.
private static final String ANDROID_HOST_OUT = "ANDROID_HOST_OUT";
@@ -53,13 +57,8 @@
// The name of the GZIP file containing launch_cvd and stop_cvd.
private static final String CVD_HOST_PACKAGE_NAME = "cvd-host_package.tar.gz";
- // The port of cuttlefish instance 1.
- private static final int CUTTLEFISH_FIRST_HOST_PORT = 6520;
-
private static final String ACLOUD_CVD_TEMP_DIR_NAME = "acloud_cvd_temp";
- private static final String INSTANCE_DIR_NAME_PREFIX = "instance_home_";
private static final String CUTTLEFISH_RUNTIME_DIR_NAME = "cuttlefish_runtime";
- private static final String INSTANCE_NAME_PREFIX = "local-instance-";
private ITestLogger mTestLogger = null;
@@ -68,11 +67,7 @@
private boolean mShouldDeleteHostPackageDir = false;
private File mImageDir = null;
- // The data for restoring the stub device at tear-down.
- private String mOriginalSerialNumber = null;
-
- // A positive integer for acloud to identify this device.
- private int mInstanceId = -1;
+ private GceAvdInfo mGceAvdInfo = null;
public LocalAndroidVirtualDevice(
IDevice device, IDeviceStateMonitor stateMonitor, IDeviceMonitor allocationMonitor) {
@@ -86,27 +81,58 @@
// The setup method in super class does not require the device to be online.
super.preInvocationSetup(info, testResourceBuildInfos);
- // TODO(b/133211308): multiple instances
- mInstanceId = 1;
- replaceStubDevice("127.0.0.1:" + (CUTTLEFISH_FIRST_HOST_PORT + mInstanceId - 1));
-
createTempDirs((IDeviceBuildInfo) info);
- CommandResult result = acloudCreate(info.getBuildFlavor(), getOptions());
+ CommandResult result = null;
+ File report = null;
+ try {
+ report = FileUtil.createTempFile("report", ".json");
+ result = acloudCreate(info.getBuildFlavor(), report, getOptions());
+ loadAvdInfo(report);
+ } catch (IOException ex) {
+ throw new TargetSetupError(
+ "Cannot create acloud report file.", ex, getDeviceDescriptor());
+ } finally {
+ FileUtil.deleteFile(report);
+ }
if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
throw new TargetSetupError(
- String.format("Cannot launch virtual device. stderr:\n%s", result.getStderr()),
+ String.format("Cannot execute acloud command. stderr:\n%s", result.getStderr()),
getDeviceDescriptor());
}
+
+ HostAndPort hostAndPort = mGceAvdInfo.hostAndPort();
+ replaceStubDevice(hostAndPort.toString());
+
+ RecoveryMode previousMode = getRecoveryMode();
+ try {
+ setRecoveryMode(RecoveryMode.NONE);
+ if (!adbTcpConnect(hostAndPort.getHost(), Integer.toString(hostAndPort.getPort()))) {
+ throw new TargetSetupError(
+ String.format("Cannot connect to %s.", hostAndPort), getDeviceDescriptor());
+ }
+ waitForDeviceAvailable();
+ } finally {
+ setRecoveryMode(previousMode);
+ }
}
/** Execute common tear-down procedure and stop the virtual device. */
@Override
public void postInvocationTearDown(Throwable exception) {
TestDeviceOptions options = getOptions();
+ HostAndPort hostAndPort = getHostAndPortFromAvdInfo();
+ String instanceName = (mGceAvdInfo != null ? mGceAvdInfo.instanceName() : null);
try {
- if (!options.shouldSkipTearDown() && mHostPackageDir != null) {
- CommandResult result = acloudDelete(options);
+ if (!options.shouldSkipTearDown() && hostAndPort != null) {
+ if (!adbTcpDisconnect(
+ hostAndPort.getHost(), Integer.toString(hostAndPort.getPort()))) {
+ CLog.e("Cannot disconnect from %s", hostAndPort.toString());
+ }
+ }
+
+ if (!options.shouldSkipTearDown() && instanceName != null) {
+ CommandResult result = acloudDelete(instanceName, options);
if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
CLog.e("Cannot stop the virtual device.");
}
@@ -114,7 +140,9 @@
CLog.i("Skip stopping the virtual device.");
}
- reportInstanceLogs();
+ if (instanceName != null) {
+ reportInstanceLogs(instanceName);
+ }
} finally {
restoreStubDevice();
@@ -122,10 +150,15 @@
deleteTempDirs();
} else {
CLog.i(
- "Skip deleting the temporary directories.\nHost package: %s\nImage: %s\n",
- mHostPackageDir, mImageDir);
+ "Skip deleting the temporary directories.\n"
+ + "Address: %s\nName: %s\nHost package: %s\nImage: %s",
+ hostAndPort, instanceName, mHostPackageDir, mImageDir);
+ mHostPackageDir = null;
+ mImageDir = null;
}
+ mGceAvdInfo = null;
+
super.postInvocationTearDown(exception);
}
}
@@ -202,38 +235,14 @@
throw new TargetSetupError(
"Unexpected device type: " + device.getClass(), getDeviceDescriptor());
}
- mOriginalSerialNumber = device.getSerialNumber();
setIDevice(new StubLocalAndroidVirtualDevice(newSerialNumber));
setFastbootEnabled(false);
}
- /**
- * Set this device to be offline and associate it with a {@link StubLocalAndroidVirtualDevice}.
- */
+ /** Restore the {@link StubLocalAndroidVirtualDevice} with the initial serial number. */
private void restoreStubDevice() {
- if (mOriginalSerialNumber == null) {
- CLog.w("Skip restoring the stub device.");
- return;
- }
- setIDevice(new StubLocalAndroidVirtualDevice(mOriginalSerialNumber));
+ setIDevice(new StubLocalAndroidVirtualDevice(getInitialSerial()));
setFastbootEnabled(false);
- mOriginalSerialNumber = null;
- }
-
- private String getInstanceDirName() {
- return INSTANCE_DIR_NAME_PREFIX + mInstanceId;
- }
-
- private File getInstanceDir() {
- return FileUtil.getFileForPath(
- getTmpDir(),
- ACLOUD_CVD_TEMP_DIR_NAME,
- getInstanceDirName(),
- CUTTLEFISH_RUNTIME_DIR_NAME);
- }
-
- private String getInstanceName() {
- return INSTANCE_NAME_PREFIX + mInstanceId;
}
private static void addLogLevelToAcloudCommand(List<String> command, LogLevel logLevel) {
@@ -244,7 +253,7 @@
}
}
- private CommandResult acloudCreate(String buildFlavor, TestDeviceOptions options) {
+ private CommandResult acloudCreate(String buildFlavor, File report, TestDeviceOptions options) {
CommandResult result = null;
File acloud = options.getAvdDriverBinary();
@@ -262,6 +271,7 @@
options.getGceCmdTimeout(),
acloud,
buildFlavor,
+ report,
options.getGceDriverLogLevel(),
options.getGceDriverParams());
if (CommandStatus.SUCCESS.equals(result.getStatus())) {
@@ -275,18 +285,16 @@
}
private CommandResult acloudCreate(
- long timeout, File acloud, String buildFlavor, LogLevel logLevel, List<String> args) {
+ long timeout,
+ File acloud,
+ String buildFlavor,
+ File report,
+ LogLevel logLevel,
+ List<String> args) {
IRunUtil runUtil = createRunUtil();
// The command creates the instance directory under TMPDIR.
runUtil.setEnvVariable(TMPDIR, getTmpDir().getAbsolutePath());
- // The command finds bin/launch_cvd in ANDROID_HOST_OUT.
- runUtil.setEnvVariable(ANDROID_HOST_OUT, mHostPackageDir.getAbsolutePath());
runUtil.setEnvVariable(TARGET_PRODUCT, buildFlavor);
- // TODO(b/141349771): Size of sockaddr_un->sun_path is 108, which may be too small for this
- // path.
- if (new File(getInstanceDir(), "launcher_monitor.sock").getAbsolutePath().length() > 108) {
- CLog.w("Length of instance path is too long for launch_cvd.");
- }
List<String> command =
new ArrayList<String>(
@@ -294,9 +302,13 @@
acloud.getAbsolutePath(),
"create",
"--local-instance",
- Integer.toString(mInstanceId),
"--local-image",
mImageDir.getAbsolutePath(),
+ "--local-tool",
+ mHostPackageDir.getAbsolutePath(),
+ "--report_file",
+ report.getAbsolutePath(),
+ "--no-autoconnect",
"--yes",
"--skip-pre-run-check"));
addLogLevelToAcloudCommand(command, logLevel);
@@ -308,7 +320,47 @@
return result;
}
- private CommandResult acloudDelete(TestDeviceOptions options) {
+ /**
+ * Get valid host and port from mGceAvdInfo.
+ *
+ * @return {@link HostAndPort} if the port is valid; null otherwise.
+ */
+ private HostAndPort getHostAndPortFromAvdInfo() {
+ if (mGceAvdInfo == null) {
+ return null;
+ }
+ HostAndPort hostAndPort = mGceAvdInfo.hostAndPort();
+ if (hostAndPort == null
+ || !hostAndPort.hasPort()
+ || hostAndPort.getPort() == INVALID_PORT) {
+ return null;
+ }
+ return hostAndPort;
+ }
+
+ /** Initialize instance name, host address, and port from an acloud report file. */
+ private void loadAvdInfo(File report) throws TargetSetupError {
+ mGceAvdInfo = GceAvdInfo.parseGceInfoFromFile(report, getDeviceDescriptor(), INVALID_PORT);
+ if (mGceAvdInfo == null) {
+ throw new TargetSetupError("Cannot read acloud report file.", getDeviceDescriptor());
+ }
+
+ if (Strings.isNullOrEmpty(mGceAvdInfo.instanceName())) {
+ throw new TargetSetupError("No instance name in acloud report.", getDeviceDescriptor());
+ }
+
+ if (getHostAndPortFromAvdInfo() == null) {
+ throw new TargetSetupError("No port in acloud report.", getDeviceDescriptor());
+ }
+
+ if (!GceAvdInfo.GceStatus.SUCCESS.equals(mGceAvdInfo.getStatus())) {
+ throw new TargetSetupError(
+ "Cannot launch virtual device: " + mGceAvdInfo.getErrors(),
+ getDeviceDescriptor());
+ }
+ }
+
+ private CommandResult acloudDelete(String instanceName, TestDeviceOptions options) {
File acloud = options.getAvdDriverBinary();
if (acloud == null || !acloud.isFile()) {
CLog.e("Specified AVD driver binary is not a file.");
@@ -324,8 +376,9 @@
Arrays.asList(
acloud.getAbsolutePath(),
"delete",
+ "--local-only",
"--instance-names",
- getInstanceName()));
+ instanceName));
addLogLevelToAcloudCommand(command, options.getGceDriverLogLevel());
CommandResult result =
@@ -335,24 +388,29 @@
return result;
}
- private void reportInstanceLogs() {
+ private void reportInstanceLogs(String instanceName) {
if (mTestLogger == null) {
return;
}
- reportInstanceLog("kernel.log", LogDataType.KERNEL_LOG);
- reportInstanceLog("logcat", LogDataType.LOGCAT);
- reportInstanceLog("launcher.log", LogDataType.TEXT);
- reportInstanceLog("cuttlefish_config.json", LogDataType.TEXT);
+ File instanceDir =
+ FileUtil.getFileForPath(
+ getTmpDir(),
+ ACLOUD_CVD_TEMP_DIR_NAME,
+ instanceName,
+ CUTTLEFISH_RUNTIME_DIR_NAME);
+ reportInstanceLog(new File(instanceDir, "kernel.log"), LogDataType.KERNEL_LOG);
+ reportInstanceLog(new File(instanceDir, "logcat"), LogDataType.LOGCAT);
+ reportInstanceLog(new File(instanceDir, "launcher.log"), LogDataType.TEXT);
+ reportInstanceLog(new File(instanceDir, "cuttlefish_config.json"), LogDataType.TEXT);
}
- private void reportInstanceLog(String fileName, LogDataType type) {
- File file = new File(getInstanceDir(), fileName);
+ private void reportInstanceLog(File file, LogDataType type) {
if (file.exists()) {
try (InputStreamSource source = new FileInputStreamSource(file)) {
- mTestLogger.testLog(fileName, type, source);
+ mTestLogger.testLog(file.getName(), type, source);
}
} else {
- CLog.w("%s doesn't exist.", fileName);
+ CLog.w("%s doesn't exist.", file.getAbsolutePath());
}
}
diff --git a/src/com/android/tradefed/device/NativeDevice.java b/src/com/android/tradefed/device/NativeDevice.java
index ee6c71d..1432b1b 100644
--- a/src/com/android/tradefed/device/NativeDevice.java
+++ b/src/com/android/tradefed/device/NativeDevice.java
@@ -632,7 +632,9 @@
return prop;
}
- private String getFastbootVariable(String variableName)
+ /** {@inheritDoc} */
+ @Override
+ public String getFastbootVariable(String variableName)
throws DeviceNotAvailableException, UnsupportedOperationException {
CommandResult result = executeFastbootCommand("getvar", variableName);
if (result.getStatus() == CommandStatus.SUCCESS) {
@@ -993,6 +995,14 @@
}
/**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean isAppEnumerationSupported() throws DeviceNotAvailableException {
+ return getApiLevel() > 29;
+ }
+
+ /**
* helper method to throw exception if runtime permission isn't supported
* @throws DeviceNotAvailableException
*/
@@ -2895,21 +2905,43 @@
@Override
public void rebootIntoBootloader()
throws DeviceNotAvailableException, UnsupportedOperationException {
+ rebootIntoFastbootInternal(true);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public void rebootIntoFastbootd()
+ throws DeviceNotAvailableException, UnsupportedOperationException {
+ rebootIntoFastbootInternal(false);
+ }
+
+ /**
+ * Reboots the device into bootloader or fastbootd mode.
+ *
+ * @param isBootloader: true to boot the device into bootloader mode, false to boot the device
+ * into fastbootd mode.
+ * @throws DeviceNotAvailableException if connection with device is lost and cannot be
+ * recovered.
+ */
+ private void rebootIntoFastbootInternal(boolean isBootloader)
+ throws DeviceNotAvailableException {
+ final RebootMode mode =
+ isBootloader ? RebootMode.REBOOT_INTO_BOOTLOADER : RebootMode.REBOOT_INTO_FASTBOOT;
if (!mFastbootEnabled) {
throw new UnsupportedOperationException(
- "Fastboot is not available and cannot reboot into bootloader");
+ String.format("Fastboot is not available and cannot reboot into %s", mode));
}
// If we go to bootloader, it's probably for flashing so ensure we re-check the provider
mShouldSkipContentProviderSetup = false;
CLog.i(
- "Rebooting device %s in state %s into bootloader",
- getSerialNumber(), getDeviceState());
+ "Rebooting device %s in state %s into %s",
+ getSerialNumber(), getDeviceState(), mode);
if (TestDeviceState.FASTBOOT.equals(getDeviceState())) {
CLog.i("device %s already in fastboot. Rebooting anyway", getSerialNumber());
- executeFastbootCommand("reboot-bootloader");
+ executeFastbootCommand(String.format("reboot-%s", mode));
} else {
- CLog.i("Booting device %s into bootloader", getSerialNumber());
- doAdbRebootBootloader();
+ CLog.i("Booting device %s into %s", getSerialNumber(), mode);
+ doAdbReboot(mode, null);
}
if (!mStateMonitor.waitForDeviceBootloader(mOptions.getFastbootTimeout())) {
recoverDeviceFromBootloader();
@@ -3083,6 +3115,11 @@
return Strings.isNullOrEmpty(reason) ? mRebootTarget : mRebootTarget + "," + reason;
}
}
+
+ @Override
+ public String toString() {
+ return mRebootTarget;
+ }
}
/**
diff --git a/src/com/android/tradefed/invoker/InvocationContext.java b/src/com/android/tradefed/invoker/InvocationContext.java
index d4cdeda..0dcd3fb 100644
--- a/src/com/android/tradefed/invoker/InvocationContext.java
+++ b/src/com/android/tradefed/invoker/InvocationContext.java
@@ -51,7 +51,6 @@
private Map<String, IBuildInfo> mNameAndBuildinfoMap;
private final UniqueMultiMap<String, String> mInvocationAttributes =
new UniqueMultiMap<String, String>();
- private Map<IInvocationContext.TimingEvent, Long> mInvocationTimingMetrics;
/** Invocation test-tag **/
private String mTestTag;
/** configuration descriptor */
@@ -70,7 +69,6 @@
* Creates a {@link BuildInfo} using default attribute values.
*/
public InvocationContext() {
- mInvocationTimingMetrics = new LinkedHashMap<>();
mAllocatedDeviceAndBuildMap = new LinkedHashMap<ITestDevice, IBuildInfo>();
// Use LinkedHashMap to ensure key ordering by insertion order
mNameAndDeviceMap = new LinkedHashMap<String, ITestDevice>();
@@ -229,22 +227,6 @@
return copy;
}
-
- /** {@inheritDoc} */
- @Override
- public Map<IInvocationContext.TimingEvent, Long> getInvocationTimingMetrics() {
- return mInvocationTimingMetrics;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void addInvocationTimingMetric(IInvocationContext.TimingEvent timingEvent,
- Long durationMillis) {
- mInvocationTimingMetrics.put(timingEvent, durationMillis);
- }
-
/**
* {@inheritDoc}
*/
@@ -375,10 +357,6 @@
// now we are a "live" object again, so let's init the transient field
mAllocatedDeviceAndBuildMap = new LinkedHashMap<ITestDevice, IBuildInfo>();
mNameAndDeviceMap = new LinkedHashMap<String, ITestDevice>();
- // For compatibility, when parent TF does not have the invocation timing yet.
- if (mInvocationTimingMetrics == null) {
- mInvocationTimingMetrics = new LinkedHashMap<>();
- }
}
/** {@inheritDoc} */
diff --git a/src/com/android/tradefed/invoker/InvocationExecution.java b/src/com/android/tradefed/invoker/InvocationExecution.java
index 233b75e..5fb9acd 100644
--- a/src/com/android/tradefed/invoker/InvocationExecution.java
+++ b/src/com/android/tradefed/invoker/InvocationExecution.java
@@ -255,8 +255,6 @@
// 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 setupDuration = System.currentTimeMillis() - start;
- testInfo.getContext()
- .addInvocationTimingMetric(IInvocationContext.TimingEvent.SETUP, setupDuration);
InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.SETUP, setupDuration);
CLog.d("Setup duration: %s'", TimeUtil.formatElapsedTime(setupDuration));
// Upload the setup logcat after setup is complete.
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index b84a2ed..61754c9 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -38,6 +38,7 @@
import com.android.tradefed.invoker.logger.CurrentInvocation.InvocationInfo;
import com.android.tradefed.invoker.logger.InvocationMetricLogger;
import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
+import com.android.tradefed.invoker.logger.TfObjectTracker;
import com.android.tradefed.invoker.sandbox.ParentSandboxInvocationExecution;
import com.android.tradefed.invoker.sandbox.SandboxedInvocationExecution;
import com.android.tradefed.invoker.shard.LastShardDetector;
@@ -402,6 +403,7 @@
}
}
} finally {
+ TfObjectTracker.clearTracking();
CurrentInvocation.clearInvocationInfos();
invocationPath.cleanUpBuilds(context, config);
}
@@ -830,8 +832,6 @@
boolean providerSuccess =
invokeFetchBuild(info, config, rescheduler, listener, invocationPath);
long fetchBuildDuration = System.currentTimeMillis() - start;
- context.addInvocationTimingMetric(IInvocationContext.TimingEvent.FETCH_BUILD,
- fetchBuildDuration);
InvocationMetricLogger.addInvocationMetrics(
InvocationMetricKey.FETCH_BUILD, fetchBuildDuration);
CLog.d("Fetch build duration: %s", TimeUtil.formatElapsedTime(fetchBuildDuration));
diff --git a/src/com/android/tradefed/invoker/logger/TfObjectTracker.java b/src/com/android/tradefed/invoker/logger/TfObjectTracker.java
new file mode 100644
index 0000000..cdb79ef
--- /dev/null
+++ b/src/com/android/tradefed/invoker/logger/TfObjectTracker.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.invoker.logger;
+
+import com.android.tradefed.build.IBuildProvider;
+import com.android.tradefed.device.metric.IMetricCollector;
+import com.android.tradefed.postprocessor.IPostProcessor;
+import com.android.tradefed.suite.checker.ISystemStatusChecker;
+import com.android.tradefed.targetprep.ITargetPreparer;
+import com.android.tradefed.targetprep.multi.IMultiTargetPreparer;
+import com.android.tradefed.testtype.IRemoteTest;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/** A utility to track the usage of the different Trade Fedederation objects. */
+public class TfObjectTracker {
+
+ public static final String TF_OBJECTS_TRACKING_KEY = "tf_objects_tracking";
+ private static final Set<Class<?>> TRACKED_CLASSES =
+ ImmutableSet.of(
+ IBuildProvider.class,
+ IMetricCollector.class,
+ IMultiTargetPreparer.class,
+ IPostProcessor.class,
+ IRemoteTest.class,
+ ISystemStatusChecker.class,
+ ITargetPreparer.class);
+
+ private TfObjectTracker() {}
+
+ private static final Map<ThreadGroup, Map<String, Long>> mPerGroupUsage =
+ new ConcurrentHashMap<ThreadGroup, Map<String, Long>>();
+
+ /** Count the occurrence of a give class and its super classes until the Tradefed interface. */
+ public static void countWithParents(Class<?> object) {
+ if (!count(object)) {
+ return;
+ }
+ // Track all the super class until not a TF interface to get a full picture.
+ countWithParents(object.getSuperclass());
+ }
+
+ /**
+ * Count the current occurrence only if it's part of the tracked objects.
+ *
+ * @param object The object to track
+ * @return True if the object was tracked, false otherwise.
+ */
+ public static boolean count(Class<?> object) {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ String qualifiedName = object.getName();
+
+ boolean tracked = false;
+ for (Class<?> classTracked : TRACKED_CLASSES) {
+ if (classTracked.isAssignableFrom(object)) {
+ tracked = true;
+ break;
+ }
+ }
+ if (!tracked) {
+ return false;
+ }
+ if (mPerGroupUsage.get(group) == null) {
+ mPerGroupUsage.put(group, new ConcurrentHashMap<>());
+ }
+ Map<String, Long> countMap = mPerGroupUsage.get(group);
+ long count = 0;
+ if (countMap.get(qualifiedName) != null) {
+ count = countMap.get(qualifiedName);
+ }
+ count++;
+ countMap.put(qualifiedName, count);
+ return true;
+ }
+
+ /** Returns the usage of the tracked objects. */
+ public static Map<String, Long> getUsage() {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ if (mPerGroupUsage.get(group) == null) {
+ mPerGroupUsage.put(group, new ConcurrentHashMap<>());
+ }
+ return new HashMap<>(mPerGroupUsage.get(group));
+ }
+
+ /** Stop tracking the current invocation. This is called automatically by the harness. */
+ public static void clearTracking() {
+ ThreadGroup group = Thread.currentThread().getThreadGroup();
+ mPerGroupUsage.remove(group);
+ }
+}
diff --git a/src/com/android/tradefed/postprocessor/BasePostProcessor.java b/src/com/android/tradefed/postprocessor/BasePostProcessor.java
index 89a1d18..983d435 100644
--- a/src/com/android/tradefed/postprocessor/BasePostProcessor.java
+++ b/src/com/android/tradefed/postprocessor/BasePostProcessor.java
@@ -242,7 +242,10 @@
continue;
}
// Force the metric to 'processed' since generated in a post-processor.
- Metric newMetric = newEntry.getValue().setType(DataType.PROCESSED).build();
+ Metric newMetric =
+ newEntry.getValue()
+ .setType(getMetricType())
+ .build();
testMetrics.put(newKey, newMetric);
}
} catch (RuntimeException e) {
@@ -325,8 +328,19 @@
continue;
}
// Force the metric to 'processed' since generated in a post-processor.
- Metric newMetric = newEntry.getValue().setType(DataType.PROCESSED).build();
+ Metric newMetric =
+ newEntry.getValue()
+ .setType(getMetricType())
+ .build();
existing.put(newKey, newMetric);
}
}
+
+ /**
+ * Override this method to change the metric type if needed. By default metric is set to
+ * processed type.
+ */
+ protected DataType getMetricType() {
+ return DataType.PROCESSED;
+ }
}
diff --git a/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java b/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java
index ab0033f..5628636 100644
--- a/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java
+++ b/src/com/android/tradefed/sandbox/SandboxInvocationRunner.java
@@ -57,6 +57,9 @@
try {
CommandResult result = sandbox.run(config, listener);
if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
+ CLog.e(
+ "Sandbox finished with status: %s and exit code: %s",
+ result.getStatus(), result.getExitCode());
handleStderrException(result.getStderr());
}
} finally {
diff --git a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
index f557b4b..5273668 100644
--- a/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
+++ b/src/com/android/tradefed/targetprep/TestAppInstallSetup.java
@@ -99,6 +99,10 @@
+ "including leading dash, e.g. \"-d\"")
private Collection<String> mInstallArgs = new ArrayList<>();
+ @Option(name = "force-queryable",
+ description = "Whether apks should be installed as force queryable.")
+ private boolean mForceQueryable = true;
+
@Option(
name = "cleanup-apks",
description =
@@ -265,6 +269,10 @@
}
}
+ if (mForceQueryable && getDevice().isAppEnumerationSupported()) {
+ mInstallArgs.add("--force-queryable");
+ }
+
for (String testAppName : mTestFileNames) {
installer(testInfo, Arrays.asList(new String[] {testAppName}));
}
diff --git a/test_framework/Android.bp b/test_framework/Android.bp
index 62c5875..31d31a7 100644
--- a/test_framework/Android.bp
+++ b/test_framework/Android.bp
@@ -20,6 +20,7 @@
],
static_libs: [
"longevity-host-lib",
+ "perfetto_metrics-full",
"test-composers",
"truth-prebuilt",
],
diff --git a/test_framework/com/android/tradefed/device/metric/HostStatsdMetricCollector.java b/test_framework/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
index 4f2e029..d86de45 100644
--- a/test_framework/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
+++ b/test_framework/com/android/tradefed/device/metric/HostStatsdMetricCollector.java
@@ -43,48 +43,48 @@
@Option(name = "binary-stats-config", description = "Path to the binary Statsd config file")
private File mBinaryConfig;
+ @Option(name = "per-run", description = "Collect Metrics at per-test level or per-run level")
+ private boolean mPerRun = true;
+
/** Map from device serial number to config ID on that device */
private Map<String, Long> mDeviceConfigIds = new HashMap<>();
+ /**
+ * Counting the test in the same run. It is used to distinguish the statsd result file from
+ * multiple tests
+ */
+ private int mTestCount = 1;
+
@Override
public void onTestRunStart(DeviceMetricData runData) {
- mDeviceConfigIds.clear();
- for (ITestDevice device : getDevices()) {
- String serialNumber = device.getSerialNumber();
- try {
- long configId = pushBinaryStatsConfig(device, mBinaryConfig);
- CLog.d(
- "Pushed binary stats config to device %s with config id: %d",
- serialNumber, configId);
- mDeviceConfigIds.put(serialNumber, configId);
- } catch (IOException | DeviceNotAvailableException e) {
- CLog.e("Failed to push stats config to device %s, error: %s", serialNumber, e);
- }
+ if (mPerRun) {
+ mDeviceConfigIds.clear();
+ startCollection();
+ }
+ }
+
+ @Override
+ public void onTestStart(DeviceMetricData testData) {
+ if (!mPerRun) {
+ mDeviceConfigIds.clear();
+ startCollection();
+ }
+ }
+
+ @Override
+ public void onTestEnd(
+ DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics) {
+ if (!mPerRun) {
+ stopCollection(testData);
+ mTestCount++;
}
}
@Override
public void onTestRunEnd(
DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) {
- for (ITestDevice device : getDevices()) {
- String serialNumber = device.getSerialNumber();
- if (mDeviceConfigIds.containsKey(serialNumber)) {
- long configId = mDeviceConfigIds.get(serialNumber);
- try {
- InputStreamSource dataStream = getReportByteStream(device, configId);
- CLog.d(
- "Retrieved stats report from device %s for config %d",
- serialNumber, configId);
- processStatsReport(device, dataStream, runData);
- testLog(
- String.format("device_%s_stats_report", serialNumber),
- LogDataType.PB,
- dataStream);
- removeConfig(device, configId);
- } catch (DeviceNotAvailableException e) {
- CLog.e("Device %s not available: %s", serialNumber, e);
- }
- }
+ if (mPerRun) {
+ stopCollection(runData);
}
}
@@ -114,5 +114,47 @@
* @param runData The destination where the processed metrics will be stored
*/
protected void processStatsReport(
- ITestDevice device, InputStreamSource dataStream, DeviceMetricData runData) {}
+ ITestDevice device, InputStreamSource dataStream, DeviceMetricData runData) {
+ // Empty method by default
+ }
+
+ private void startCollection() {
+ for (ITestDevice device : getDevices()) {
+ String serialNumber = device.getSerialNumber();
+ try {
+ long configId = pushBinaryStatsConfig(device, mBinaryConfig);
+ CLog.d(
+ "Pushed binary stats config to device %s with config id: %d",
+ serialNumber, configId);
+ mDeviceConfigIds.put(serialNumber, configId);
+ } catch (IOException | DeviceNotAvailableException e) {
+ CLog.e("Failed to push stats config to device %s, error: %s", serialNumber, e);
+ }
+ }
+ }
+
+ private void stopCollection(DeviceMetricData metricData) {
+ for (ITestDevice device : getDevices()) {
+ String serialNumber = device.getSerialNumber();
+ if (mDeviceConfigIds.containsKey(serialNumber)) {
+ long configId = mDeviceConfigIds.get(serialNumber);
+ try (InputStreamSource dataStream = getReportByteStream(device, configId)) {
+ CLog.d(
+ "Retrieved stats report from device %s for config %d",
+ serialNumber, configId);
+ removeConfig(device, configId);
+ String reportName =
+ mPerRun
+ ? String.format("device_%s_stats_report", serialNumber)
+ : String.format(
+ "device_%s_stats_report_test_%d",
+ serialNumber, mTestCount);
+ testLog(reportName, LogDataType.PB, dataStream);
+ processStatsReport(device, dataStream, metricData);
+ } catch (DeviceNotAvailableException e) {
+ CLog.e("Device %s not available: %s", serialNumber, e);
+ }
+ }
+ }
+ }
}
diff --git a/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
new file mode 100644
index 0000000..5e0beb1
--- /dev/null
+++ b/test_framework/com/android/tradefed/postprocessor/PerfettoGenericPostProcessor.java
@@ -0,0 +1,410 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.postprocessor;
+
+import com.android.tradefed.config.Option;
+import com.android.tradefed.config.OptionClass;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.result.LogFile;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.ZipUtil;
+import com.android.tradefed.util.ZipUtil2;
+import com.android.tradefed.util.proto.TfMetricProtoUtil;
+
+import com.google.protobuf.Descriptors.FieldDescriptor;
+import com.google.protobuf.Message;
+import com.google.protobuf.TextFormat;
+import com.google.protobuf.TextFormat.ParseException;
+
+import org.apache.commons.compress.archivers.zip.ZipFile;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.regex.Pattern;
+
+import perfetto.protos.PerfettoMergedMetrics.TraceMetrics;
+
+/**
+ * A post processor that processes text/binary metric perfetto proto file into key-value pairs by
+ * recursively expanding the proto messages and fields with string values until the field with
+ * numeric value is encountered. Treats enum and boolean as string values while constructing the
+ * keys.
+ *
+ * <p>It optionally supports indexing list fields when there are duplicates while constructing the
+ * keys. For example
+ *
+ * <p>"perfetto-indexed-list-field" - perfetto.protos.AndroidStartupMetric.Startup
+ *
+ * <p>android_startup-startup#1-package_name-com.calculator-to_first_frame-dur_ns: 300620342
+ * android_startup-startup#2-package_name-com.nexuslauncher-to_first_frame-dur_ns: 49257713
+ * android_startup-startup#3-package_name-com.calculator-to_first_frame-dur_ns: 261382005
+ */
+@OptionClass(alias = "perfetto-generic-processor")
+public class PerfettoGenericPostProcessor extends BasePostProcessor {
+
+ private static final String METRIC_SEP = "-";
+
+ public enum METRIC_FILE_FORMAT {
+ text,
+ binary,
+ json,
+ }
+
+ @Option(
+ name = "perfetto-proto-file-prefix",
+ description = "Prefix for identifying a perfetto metric file name.")
+ private Set<String> mPerfettoProtoMetricFilePrefix = new HashSet<>();
+
+ @Option(
+ name = "perfetto-indexed-list-field",
+ description = "List fields in perfetto proto metric file that has to be indexed.")
+ private Set<String> mPerfettoIndexedListFields = new HashSet<>();
+
+ @Option(
+ name = "perfetto-include-all-metrics",
+ description =
+ "If this flag is turned on, all the metrics parsed from the perfetto file will"
+ + " be included in the final result map and ignores the regex passed"
+ + " in the filters.")
+ private boolean mPerfettoIncludeAllMetrics = false;
+
+ @Option(
+ name = "perfetto-metric-filter-regex",
+ description =
+ "Regular expression that will be used for filtering the metrics parsed"
+ + " from the perfetto proto metric file.")
+ private Set<String> mPerfettoMetricFilterRegEx = new HashSet<>();
+
+ @Option(
+ name = "trace-processor-output-format",
+ description = "Trace processor output format. One of [binary|text|json]")
+ private METRIC_FILE_FORMAT mTraceProcessorOutputFormat = METRIC_FILE_FORMAT.text;
+
+ @Option(
+ name = "decompress-perfetto-timeout",
+ description = "Timeout to decompress perfetto compressed file.",
+ isTimeVal = true)
+ private long mDecompressTimeoutMs = TimeUnit.MINUTES.toMillis(20);
+
+ @Option(
+ name = "processed-metric",
+ description =
+ "True if the metric is final and shouldn't be processed any more,"
+ + " false if the metric can be handled by another post-processor.")
+ private boolean mProcessedMetric = true;
+
+ // Matches 1.73, 1.73E+2
+ private Pattern mNumberWithExponentPattern =
+ Pattern.compile("[-+]?[0-9]*[\\.]?[0-9]+([eE][-+]?[0-9]+)?");
+
+ // Matches numbers without exponent format.
+ private Pattern mNumberPattern = Pattern.compile("[-+]?[0-9]*[\\.]?[0-9]+");
+
+ private List<Pattern> mMetricPatterns = new ArrayList<>();
+
+
+ @Override
+ public Map<String, Metric.Builder> processTestMetricsAndLogs(
+ TestDescription testDescription,
+ HashMap<String, Metric> testMetrics,
+ Map<String, LogFile> testLogs) {
+ buildMetricFilterPatterns();
+ return processPerfettoMetrics(filterPerfeticMetricFiles(testLogs));
+ }
+
+ @Override
+ public Map<String, Metric.Builder> processRunMetricsAndLogs(
+ HashMap<String, Metric> rawMetrics, Map<String, LogFile> runLogs) {
+ buildMetricFilterPatterns();
+ return processPerfettoMetrics(filterPerfeticMetricFiles(runLogs));
+ }
+
+ /**
+ * Filter the perfetto metric file based on the prefix.
+ *
+ * @param logs
+ * @return files matched the prefix.
+ */
+ private List<File> filterPerfeticMetricFiles(Map<String, LogFile> logs) {
+ List<File> perfettoMetricFiles = new ArrayList<>();
+ for (String key : logs.keySet()) {
+ Optional<String> reportPrefix =
+ mPerfettoProtoMetricFilePrefix
+ .stream()
+ .filter(prefix -> key.startsWith(prefix))
+ .findAny();
+
+ if (!reportPrefix.isPresent()) {
+ continue;
+ }
+ perfettoMetricFiles.add(new File(logs.get(key).getPath()));
+ }
+ return perfettoMetricFiles;
+ }
+
+ /**
+ * Process perfetto metric files into key, value pairs.
+ *
+ * @param perfettoMetricFiles perfetto metric files to be processed.
+ * @return key, value pairs processed from the metrics.
+ */
+ private Map<String, Metric.Builder> processPerfettoMetrics(List<File> perfettoMetricFiles) {
+ Map<String, Metric.Builder> parsedMetrics = new HashMap<>();
+ File uncompressedDir = null;
+ for (File perfettoMetricFile : perfettoMetricFiles) {
+ // Text files by default are compressed before uploading. Decompress the text proto
+ // file before post processing.
+ try {
+ if (!(mTraceProcessorOutputFormat == METRIC_FILE_FORMAT.binary) &&
+ ZipUtil.isZipFileValid(perfettoMetricFile, true)) {
+ ZipFile perfettoZippedFile = new ZipFile(perfettoMetricFile);
+ uncompressedDir = FileUtil.createTempDir("uncompressed_perfetto_metric");
+ ZipUtil2.extractZip(perfettoZippedFile, uncompressedDir);
+ perfettoMetricFile = uncompressedDir.listFiles()[0];
+ perfettoZippedFile.close();
+ }
+ } catch (IOException e) {
+ CLog.e(
+ "IOException happened when unzipping the perfetto metric proto"
+ + " file."
+ + e.getMessage());
+ }
+
+ // Parse the perfetto proto file.
+ try (BufferedReader bufferedReader = new BufferedReader(
+ new FileReader(perfettoMetricFile))) {
+ switch (mTraceProcessorOutputFormat) {
+ case text:
+ TraceMetrics.Builder builder = TraceMetrics.newBuilder();
+ TextFormat.merge(bufferedReader, builder);
+ parsedMetrics.putAll(
+ filterMetrics(convertPerfettoProtoMessage(builder.build())));
+ break;
+ case binary:
+ TraceMetrics metricProto = null;
+ metricProto = TraceMetrics
+ .parseFrom(new FileInputStream(perfettoMetricFile));
+ parsedMetrics
+ .putAll(filterMetrics(convertPerfettoProtoMessage(metricProto)));
+ break;
+ case json:
+ CLog.w("JSON perfetto metric file processing not supported.");
+ }
+ } catch (ParseException e) {
+ CLog.e("Failed to merge the perfetto metric file. " + e.getMessage());
+ } catch (IOException ioe) {
+ CLog.e(
+ "IOException happened when reading the perfetto metric file. "
+ + ioe.getMessage());
+ } finally {
+ // Delete the uncompressed perfetto metric proto file directory.
+ FileUtil.recursiveDelete(uncompressedDir);
+ }
+ }
+ return parsedMetrics;
+ }
+
+ /**
+ * Expands the metric proto file as tree structure and converts it into key, value pairs by
+ * recursively constructing the key using the message name, proto fields with string values
+ * until the numeric proto field is encountered.
+ *
+ * <p>android_startup-startup-package_name-com.calculator-to_first_frame-dur_ns: 300620342
+ * android_startup-startup-package_name-com.nexuslauncher-to_first_frame-dur_ns: 49257713
+ *
+ * <p>It also supports indexing the list proto fields optionally. This will be used if the list
+ * generates duplicate key's when recursively expanding the messages to prevent overriding the
+ * results.
+ *
+ * <p>"perfetto-indexed-list-field" - perfetto.protos.AndroidStartupMetric.Startup
+ *
+ * <p><android_startup-startup#1-package_name-com.calculator-to_first_frame-dur_ns: 300620342
+ * android_startup-startup#2-package_name-com.nexuslauncher-to_first_frame-dur_ns: 49257713
+ * android_startup-startup#3-package_name-com.calculator-to_first_frame-dur_ns: 261382005
+ */
+ private Map<String, Metric.Builder> convertPerfettoProtoMessage(Message reportMessage) {
+ Map<FieldDescriptor, Object> fields = reportMessage.getAllFields();
+ Map<String, Metric.Builder> convertedMetrics = new HashMap<String, Metric.Builder>();
+ List<String> keyPrefixes = new ArrayList<String>();
+
+ for (Entry<FieldDescriptor, Object> entry : fields.entrySet()) {
+ if (!(entry.getValue() instanceof Message) && !(entry.getValue() instanceof List)) {
+ if (isNumeric(entry.getValue().toString())) {
+ // Construct the metric if it is numeric value.
+ if (mNumberPattern.matcher(entry.getValue().toString()).matches()) {
+ convertedMetrics.put(
+ entry.getKey().getName(),
+ TfMetricProtoUtil.stringToMetric(entry.getValue().toString())
+ .toBuilder());
+ } else {
+ // Parse the exponent notation of string before adding it to metric.
+ convertedMetrics.put(
+ entry.getKey().getName(),
+ TfMetricProtoUtil.stringToMetric(
+ Long.toString(
+ Double.valueOf(entry.getValue().toString())
+ .longValue()))
+ .toBuilder());
+ }
+ } else {
+ // Add to prefix list if string value is encountered.
+ keyPrefixes.add(
+ String.join(
+ METRIC_SEP,
+ entry.getKey().getName().toString(),
+ entry.getValue().toString()));
+ }
+ }
+ }
+
+ // Recursively expand the proto messages and repeated fields(i.e list).
+ // Recursion when there are no messages or list with in the current message.
+ for (Entry<FieldDescriptor, Object> entry : fields.entrySet()) {
+ if (entry.getValue() instanceof Message) {
+ Map<String, Metric.Builder> messageMetrics =
+ convertPerfettoProtoMessage((Message) entry.getValue());
+ for (Entry<String, Metric.Builder> metricEntry : messageMetrics.entrySet()) {
+ // Add prefix to the metrics parsed from this message.
+ for (String prefix : keyPrefixes) {
+ convertedMetrics.put(
+ String.join(
+ METRIC_SEP,
+ prefix,
+ entry.getKey().getName(),
+ metricEntry.getKey()),
+ metricEntry.getValue());
+ }
+ if (keyPrefixes.isEmpty()) {
+ convertedMetrics.put(
+ String.join(
+ METRIC_SEP, entry.getKey().getName(), metricEntry.getKey()),
+ metricEntry.getValue());
+ }
+ }
+ } else if (entry.getValue() instanceof List) {
+ List<? extends Object> listMetrics = (List) entry.getValue();
+ for (int i = 0; i < listMetrics.size(); i++) {
+ String metricKeyRoot;
+ // Use indexing if the current field is chosen for indexing.
+ // Use it if metrics keys generated has duplicates to prevent overriding.
+ if (mPerfettoIndexedListFields.contains(entry.getKey().toString())) {
+ metricKeyRoot =
+ String.join(
+ METRIC_SEP,
+ entry.getKey().getName(),
+ String.valueOf(i + 1));
+ } else {
+ metricKeyRoot = String.join(METRIC_SEP, entry.getKey().getName());
+ }
+ if (listMetrics.get(i) instanceof Message) {
+ Map<String, Metric.Builder> messageMetrics =
+ convertPerfettoProtoMessage((Message) listMetrics.get(i));
+ for (Entry<String, Metric.Builder> metricEntry :
+ messageMetrics.entrySet()) {
+ for (String prefix : keyPrefixes) {
+ convertedMetrics.put(
+ String.join(
+ METRIC_SEP,
+ prefix,
+ metricKeyRoot,
+ metricEntry.getKey()),
+ metricEntry.getValue());
+ }
+ if (keyPrefixes.isEmpty()) {
+ convertedMetrics.put(
+ String.join(
+ METRIC_SEP, metricKeyRoot, metricEntry.getKey()),
+ metricEntry.getValue());
+ }
+ }
+ } else {
+ convertedMetrics.put(
+ metricKeyRoot,
+ TfMetricProtoUtil.stringToMetric(listMetrics.get(i).toString())
+ .toBuilder());
+ }
+ }
+ }
+ }
+ return convertedMetrics;
+ }
+
+ /**
+ * Check if the given string is number. It matches the string with exponent notation as well.
+ *
+ * <p>For example returns true for Return true for 1.73, 1.73E+2
+ */
+ private boolean isNumeric(String strNum) {
+ if (strNum == null) {
+ return false;
+ }
+ return mNumberWithExponentPattern.matcher(strNum).matches();
+ }
+
+ /** Build regular expression patterns to filter the metrics. */
+ private void buildMetricFilterPatterns() {
+ if (!mPerfettoMetricFilterRegEx.isEmpty() && mMetricPatterns.isEmpty()) {
+ for (String regEx : mPerfettoMetricFilterRegEx) {
+ mMetricPatterns.add(Pattern.compile(regEx));
+ }
+ }
+ }
+
+ /**
+ * Filter parsed metrics from the proto metric files based on the regular expression. If
+ * "mPerfettoIncludeAllMetrics" is enabled then filters will be ignored and returns all the
+ * parsed metrics.
+ */
+ private Map<String, Metric.Builder> filterMetrics(Map<String, Metric.Builder> parsedMetrics) {
+ if (mPerfettoIncludeAllMetrics) {
+ return parsedMetrics;
+ }
+ Map<String, Metric.Builder> filteredMetrics = new HashMap<>();
+ for (Entry<String, Metric.Builder> metricEntry : parsedMetrics.entrySet()) {
+ for (Pattern pattern : mMetricPatterns) {
+ if (pattern.matcher(metricEntry.getKey()).matches()) {
+ filteredMetrics.put(metricEntry.getKey(), metricEntry.getValue());
+ break;
+ }
+ }
+ }
+ return filteredMetrics;
+ }
+
+ /**
+ * Set the metric type based on flag.
+ */
+ @Override
+ protected DataType getMetricType() {
+ return mProcessedMetric ? DataType.PROCESSED : DataType.RAW;
+ }
+}
diff --git a/test_framework/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java b/test_framework/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java
index ca5ce91..1e4429c 100644
--- a/test_framework/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java
+++ b/test_framework/com/android/tradefed/targetprep/AllTestAppsInstallSetup.java
@@ -51,6 +51,10 @@
+ "including leading dash, e.g. \"-d\"")
private Collection<String> mInstallArgs = new ArrayList<>();
+ @Option(name = "force-queryable",
+ description = "Whether apks should be installed as force queryable.")
+ private boolean mForceQueryable = true;
+
@Option(name = "cleanup-apks",
description = "Whether apks installed should be uninstalled after test. Note that the "
+ "preparer does not verify if the apks are successfully removed.")
@@ -81,6 +85,9 @@
throw new TargetSetupError("Failed to find a valid test zip directory.",
device.getDeviceDescriptor());
}
+ if (mForceQueryable && device.isAppEnumerationSupported()) {
+ mInstallArgs.add("--force-queryable");
+ }
resolveAbi(device);
installApksRecursively(testsDir, device);
}
diff --git a/test_framework/com/android/tradefed/targetprep/AppSetup.java b/test_framework/com/android/tradefed/targetprep/AppSetup.java
index 62a8464..8a7a573 100644
--- a/test_framework/com/android/tradefed/targetprep/AppSetup.java
+++ b/test_framework/com/android/tradefed/targetprep/AppSetup.java
@@ -63,6 +63,10 @@
@Option(name = "install-arg", description = "optional flag(s) to provide when installing apks.")
private ArrayList<String> mInstallArgs = new ArrayList<>();
+ @Option(name = "force-queryable",
+ description = "Whether apks should be installed as force queryable.")
+ private boolean mForceQueryable = true;
+
@Option(name = "post-install-cmd", description =
"optional post-install adb shell commands; can be repeated.")
private List<String> mPostInstallCmds = new ArrayList<>();
@@ -102,6 +106,10 @@
}
if (mInstall) {
+ if (mForceQueryable && device.isAppEnumerationSupported()) {
+ mInstallArgs.add("--force-queryable");
+ }
+
for (VersionedFile apkFile : apps) {
if (mCheckMinSdk) {
AaptParser aaptParser = doAaptParse(apkFile.getFile());
diff --git a/test_framework/com/android/tradefed/targetprep/FastbootCommandPreparer.java b/test_framework/com/android/tradefed/targetprep/FastbootCommandPreparer.java
index 62df086..51baa0f 100644
--- a/test_framework/com/android/tradefed/targetprep/FastbootCommandPreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/FastbootCommandPreparer.java
@@ -30,13 +30,33 @@
*/
@OptionClass(alias = "fastboot-command-preparer")
public class FastbootCommandPreparer extends BaseTargetPreparer {
+
+ private enum FastbootMode {
+ BOOTLOADER,
+ FASTBOOTD,
+ }
+
+ @Option(
+ name = "fastboot-mode",
+ description = "True to boot the device into bootloader mode, false for fastbootd mode.")
+ private FastbootMode mFastbootMode = FastbootMode.BOOTLOADER;
+
+ @Option(
+ name = "stay-fastboot",
+ description = "True to keep the device in bootloader or fastbootd mode.")
+ private boolean mStayFastboot = false;
+
@Option(name = "command", description = "Fastboot commands to run.")
private List<String> mFastbootCommands = new ArrayList<String>();
@Override
public void setUp(TestInformation testInformation)
throws TargetSetupError, BuildError, DeviceNotAvailableException {
- testInformation.getDevice().rebootIntoBootloader();
+ if (mFastbootMode == FastbootMode.BOOTLOADER) {
+ testInformation.getDevice().rebootIntoBootloader();
+ } else {
+ testInformation.getDevice().rebootIntoFastbootd();
+ }
for (String cmd : mFastbootCommands) {
// Ignore reboots since we'll reboot in the end.
@@ -47,7 +67,9 @@
testInformation.getDevice().executeFastbootCommand(cmd.split("\\s+"));
}
- testInformation.getDevice().reboot();
+ if (!mStayFastboot) {
+ testInformation.getDevice().reboot();
+ }
}
/** {@inheritDoc} */
diff --git a/test_framework/com/android/tradefed/targetprep/InstallAllTestZipAppsSetup.java b/test_framework/com/android/tradefed/targetprep/InstallAllTestZipAppsSetup.java
index 0dd9b97..4ad51e2 100644
--- a/test_framework/com/android/tradefed/targetprep/InstallAllTestZipAppsSetup.java
+++ b/test_framework/com/android/tradefed/targetprep/InstallAllTestZipAppsSetup.java
@@ -48,6 +48,10 @@
)
private Collection<String> mInstallArgs = new ArrayList<>();
+ @Option(name = "force-queryable",
+ description = "Whether apks should be installed as force queryable.")
+ private boolean mForceQueryable = true;
+
@Option(
name = "cleanup-apks",
description =
@@ -103,6 +107,10 @@
"Failed to extract test zip.", e, device.getDeviceDescriptor());
}
+ if (mForceQueryable && device.isAppEnumerationSupported()) {
+ mInstallArgs.add("--force-queryable");
+ }
+
try {
installApksRecursively(testsDir, device);
} finally {
diff --git a/test_framework/com/android/tradefed/targetprep/InstallApkSetup.java b/test_framework/com/android/tradefed/targetprep/InstallApkSetup.java
index 36f4053..651a657 100644
--- a/test_framework/com/android/tradefed/targetprep/InstallApkSetup.java
+++ b/test_framework/com/android/tradefed/targetprep/InstallApkSetup.java
@@ -63,6 +63,10 @@
+ "including leading dash, e.g. \"-d\"")
private Collection<String> mInstallArgs = new ArrayList<>();
+ @Option(name = "force-queryable",
+ description = "Whether apks should be installed as force queryable.")
+ private boolean mForceQueryable = true;
+
@Option(name = "post-install-cmd", description =
"optional post-install adb shell commands; can be repeated.")
private List<String> mPostInstallCmds = new ArrayList<>();
@@ -95,6 +99,10 @@
mInstallArgs.add(String.format("--abi %s", abi));
}
}
+ if (mForceQueryable && device.isAppEnumerationSupported()
+ && !mInstallArgs.contains("--force-queryable")) {
+ mInstallArgs.add("--force-queryable");
+ }
String result = device.installPackage(apk, true, mInstallArgs.toArray(new String[]{}));
if (result != null) {
if (mThrowIfInstallFail) {
diff --git a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
index 6190f3c..3afabc6 100644
--- a/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
+++ b/test_framework/com/android/tradefed/targetprep/PushFilePreparer.java
@@ -26,6 +26,7 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.IAbi;
import com.android.tradefed.testtype.IAbiReceiver;
@@ -257,13 +258,12 @@
return src;
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
@Override
- public void setUp(ITestDevice device, IBuildInfo buildInfo) throws TargetSetupError, BuildError,
- DeviceNotAvailableException {
+ public void setUp(TestInformation testInfo)
+ throws TargetSetupError, BuildError, DeviceNotAvailableException {
mFilesPushed = new HashSet<>();
+ ITestDevice device = testInfo.getDevice();
if (mRemountSystem) {
device.remountSystemWritable();
}
@@ -292,7 +292,7 @@
String.format(
"Trying to push local '%s' to remote '%s'",
local.getPath(), remotePath));
- evaluatePushingPair(device, buildInfo, local, remotePath);
+ evaluatePushingPair(device, testInfo.getBuildInfo(), local, remotePath);
}
for (String command : mPostPushCommands) {
@@ -305,12 +305,10 @@
}
}
- /**
- * {@inheritDoc}
- */
+ /** {@inheritDoc} */
@Override
- public void tearDown(ITestDevice device, IBuildInfo buildInfo, Throwable e)
- throws DeviceNotAvailableException {
+ public void tearDown(TestInformation testInfo, Throwable e) throws DeviceNotAvailableException {
+ ITestDevice device = testInfo.getDevice();
if (!(e instanceof DeviceNotAvailableException) && mCleanup && mFilesPushed != null) {
if (mRemountSystem) {
device.remountSystemWritable();
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index c299b28..19dc753 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -135,6 +135,7 @@
import com.android.tradefed.invoker.TestInvocationTest;
import com.android.tradefed.invoker.UnexecutedTestReporterThreadTest;
import com.android.tradefed.invoker.logger.InvocationMetricLoggerTest;
+import com.android.tradefed.invoker.logger.TfObjectTrackerTest;
import com.android.tradefed.invoker.sandbox.ParentSandboxInvocationExecutionTest;
import com.android.tradefed.invoker.shard.ShardHelperTest;
import com.android.tradefed.invoker.shard.StrictShardHelperTest;
@@ -151,6 +152,7 @@
import com.android.tradefed.postprocessor.AggregatePostProcessorTest;
import com.android.tradefed.postprocessor.AveragePostProcessorTest;
import com.android.tradefed.postprocessor.BasePostProcessorTest;
+import com.android.tradefed.postprocessor.PerfettoGenericPostProcessorTest;
import com.android.tradefed.postprocessor.StatsdEventMetricPostProcessorTest;
import com.android.tradefed.postprocessor.StatsdGenericPostProcessorTest;
import com.android.tradefed.result.ATestFileSystemLogSaverTest;
@@ -552,6 +554,7 @@
// invoker.logger
InvocationMetricLoggerTest.class,
+ TfObjectTrackerTest.class,
// invoker.shard
ShardHelperTest.class,
@@ -580,6 +583,7 @@
AggregatePostProcessorTest.class,
AveragePostProcessorTest.class,
BasePostProcessorTest.class,
+ PerfettoGenericPostProcessorTest.class,
StatsdEventMetricPostProcessorTest.class,
StatsdGenericPostProcessorTest.class,
diff --git a/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java b/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
index cfb080d..b129c37 100644
--- a/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
+++ b/tests/src/com/android/tradefed/cluster/ClusterCommandSchedulerTest.java
@@ -26,6 +26,8 @@
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.cluster.ClusterCommand.RequestType;
+import com.android.tradefed.cluster.ClusterCommandScheduler.InvocationEventHandler;
import com.android.tradefed.command.CommandScheduler;
import com.android.tradefed.command.remote.DeviceDescriptor;
import com.android.tradefed.config.Configuration;
@@ -45,6 +47,7 @@
import com.android.tradefed.device.StubDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ConsoleResultReporter;
import com.android.tradefed.result.FileInputStreamSource;
@@ -56,15 +59,13 @@
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner.TestLogData;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.IRestApiHelper;
import com.android.tradefed.util.MultiMap;
import com.android.tradefed.util.RunUtil;
import com.android.tradefed.util.ZipUtil;
import com.android.tradefed.util.keystore.IKeyStoreClient;
import com.android.tradefed.util.keystore.StubKeyStoreClient;
-import com.android.tradefed.cluster.ClusterCommand.RequestType;
-import com.android.tradefed.cluster.ClusterCommandScheduler.InvocationEventHandler;
-import com.android.tradefed.util.IRestApiHelper;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
@@ -87,7 +88,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
-import org.junit.Test;
import org.mockito.Mockito;
import java.io.ByteArrayInputStream;
@@ -99,11 +99,11 @@
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
-import java.util.UUID;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import java.util.TreeMap;
+import java.util.UUID;
import java.util.concurrent.TimeUnit;
/** Unit tests for {@link ClusterCommandScheduler}. */
@@ -138,7 +138,7 @@
// Explicitly define this, so we can mock it
private static interface ICommandEventUploader
- extends IClusterEventUploader<ClusterCommandEvent> {};
+ extends IClusterEventUploader<ClusterCommandEvent> {}
@Before
public void setUp() throws Exception {
@@ -651,8 +651,8 @@
handler.testRunEnded(10L, new HashMap<String, Metric>());
handler.invocationEnded(100L);
context.addAllocatedDevice(DEVICE_SERIAL, mockTestDevice);
- context.addInvocationTimingMetric(IInvocationContext.TimingEvent.FETCH_BUILD, 100L);
- context.addInvocationTimingMetric(IInvocationContext.TimingEvent.SETUP, 200L);
+ context.addInvocationAttribute(InvocationMetricKey.FETCH_BUILD.toString(), "100");
+ context.addInvocationAttribute(InvocationMetricKey.SETUP.toString(), "200");
Map<ITestDevice, FreeDeviceState> releaseMap = new HashMap<>();
releaseMap.put(mockTestDevice, FreeDeviceState.AVAILABLE);
handler.invocationComplete(context, releaseMap);
diff --git a/tests/src/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java b/tests/src/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
index 28335ef..c123833 100644
--- a/tests/src/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/LocalAndroidVirtualDeviceTest.java
@@ -41,6 +41,7 @@
import org.apache.commons.compress.archivers.tar.TarArchiveOutputStream;
import org.easymock.Capture;
import org.easymock.EasyMock;
+import org.easymock.IAnswer;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
@@ -61,22 +62,27 @@
super(device, stateMonitor, allocationMonitor);
}
- IDevice currentDevice = null;
- IRunUtil currentRunUtil = null;
+ IRunUtil currentRunUtil;
+ boolean expectToConnect;
@Override
- public IDevice getIDevice() {
- return currentDevice;
+ public boolean adbTcpConnect(String host, String port) {
+ Assert.assertTrue("Unexpected method call to adbTcpConnect.", expectToConnect);
+ Assert.assertEquals(IP_ADDRESS, host);
+ Assert.assertEquals(PORT, port);
+ return true;
}
@Override
- public void setIDevice(IDevice device) {
- currentDevice = device;
+ public boolean adbTcpDisconnect(String host, String port) {
+ Assert.assertEquals(IP_ADDRESS, host);
+ Assert.assertEquals(PORT, port);
+ return true;
}
@Override
- public void setDeviceState(TestDeviceState state) {
- Assert.assertEquals(TestDeviceState.NOT_AVAILABLE, state);
+ public void waitForDeviceAvailable() {
+ Assert.assertTrue("Unexpected method call to waitForDeviceAvailable.", expectToConnect);
}
@Override
@@ -94,9 +100,44 @@
}
private static final String STUB_SERIAL_NUMBER = "local-virtual-device-0";
- private static final String ONLINE_SERIAL_NUMBER = "127.0.0.1:6520";
+ private static final String IP_ADDRESS = "127.0.0.1";
+ private static final String PORT = "6520";
+ private static final String ONLINE_SERIAL_NUMBER = IP_ADDRESS + ":" + PORT;
+ private static final String INSTANCE_NAME = "local-instance-1";
private static final String BUILD_FLAVOR = "cf_x86_phone-userdebug";
private static final long ACLOUD_TIMEOUT = 12345;
+ private static final String SUCCESS_REPORT_STRING =
+ String.format(
+ "{"
+ + " \"command\": \"create\","
+ + " \"data\": {"
+ + " \"devices\": ["
+ + " {"
+ + " \"ip\": \"%s\","
+ + " \"instance_name\": \"%s\""
+ + " }"
+ + " ]"
+ + " },"
+ + " \"errors\": [],"
+ + " \"status\": \"SUCCESS\""
+ + "}",
+ ONLINE_SERIAL_NUMBER, INSTANCE_NAME);
+ private static final String FAILURE_REPORT_STRING =
+ String.format(
+ "{"
+ + " \"command\": \"create\","
+ + " \"data\": {"
+ + " \"devices_failing_boot\": ["
+ + " {"
+ + " \"ip\": \"%s\","
+ + " \"instance_name\": \"%s\""
+ + " }"
+ + " ]"
+ + " },"
+ + " \"errors\": [],"
+ + " \"status\": \"BOOT_FAIL\""
+ + "}",
+ ONLINE_SERIAL_NUMBER, INSTANCE_NAME);
// Temporary files.
private File mAcloud;
@@ -104,12 +145,7 @@
private File mHostPackageTarGzip;
private File mTmpDir;
- // The initial stub device.
- private StubLocalAndroidVirtualDevice mStubLocalAvd;
-
- // Mock objects
- private IDeviceStateMonitor mMockDeviceStateMonitor;
- private IDeviceMonitor mMockDeviceMonitor;
+ // Mock object.
private IDeviceBuildInfo mMockDeviceBuildInfo;
// The object under test.
@@ -123,20 +159,22 @@
createHostPackage(mHostPackageTarGzip);
mTmpDir = FileUtil.createTempDir("LocalAvdTmp");
- mStubLocalAvd = new StubLocalAndroidVirtualDevice(STUB_SERIAL_NUMBER);
-
- mMockDeviceStateMonitor = EasyMock.createMock(IDeviceStateMonitor.class);
- mMockDeviceMonitor = EasyMock.createMock(IDeviceMonitor.class);
mMockDeviceBuildInfo = EasyMock.createMock(IDeviceBuildInfo.class);
EasyMock.expect(mMockDeviceBuildInfo.getDeviceImageFile()).andReturn(mImageZip);
EasyMock.expect(mMockDeviceBuildInfo.getFile(EasyMock.eq("cvd-host_package.tar.gz")))
.andReturn(mHostPackageTarGzip);
EasyMock.expect(mMockDeviceBuildInfo.getBuildFlavor()).andReturn(BUILD_FLAVOR);
+ IDeviceStateMonitor mockDeviceStateMonitor = EasyMock.createMock(IDeviceStateMonitor.class);
+ mockDeviceStateMonitor.setIDevice(EasyMock.anyObject());
+ EasyMock.expectLastCall().anyTimes();
+ IDeviceMonitor mockDeviceMonitor = EasyMock.createMock(IDeviceMonitor.class);
+ EasyMock.replay(mMockDeviceBuildInfo, mockDeviceStateMonitor, mockDeviceMonitor);
mLocalAvd =
new TestableLocalAndroidVirtualDevice(
- mStubLocalAvd, mMockDeviceStateMonitor, mMockDeviceMonitor);
- mLocalAvd.setIDevice(mStubLocalAvd);
+ new StubLocalAndroidVirtualDevice(STUB_SERIAL_NUMBER),
+ mockDeviceStateMonitor,
+ mockDeviceMonitor);
TestDeviceOptions options = mLocalAvd.getOptions();
options.setGceCmdTimeout(ACLOUD_TIMEOUT);
options.setAvdDriverBinary(mAcloud);
@@ -178,35 +216,54 @@
}
}
- private void replayAllMocks(Object... mocks) {
- EasyMock.replay(mocks);
- EasyMock.replay(mMockDeviceStateMonitor, mMockDeviceMonitor, mMockDeviceBuildInfo);
- }
-
private IRunUtil mockAcloudCreate(
- CommandStatus status, Capture<String> hostPackageDir, Capture<String> imageDir) {
+ CommandStatus status,
+ String reportString,
+ Capture<String> reportFile,
+ Capture<String> hostPackageDir,
+ Capture<String> imageDir) {
IRunUtil runUtil = EasyMock.createMock(IRunUtil.class);
runUtil.setEnvVariable(EasyMock.eq("TMPDIR"), EasyMock.eq(mTmpDir.getAbsolutePath()));
- runUtil.setEnvVariable(EasyMock.eq("ANDROID_HOST_OUT"), EasyMock.capture(hostPackageDir));
runUtil.setEnvVariable(EasyMock.eq("TARGET_PRODUCT"), EasyMock.eq(BUILD_FLAVOR));
- CommandResult result = new CommandResult(status);
- result.setStderr("acloud create");
- result.setStdout("acloud create");
+ IAnswer<CommandResult> writeToReportFile =
+ new IAnswer() {
+ @Override
+ public CommandResult answer() throws Throwable {
+ Object[] args = EasyMock.getCurrentArguments();
+ for (int index = 0; index < args.length; index++) {
+ if ("--report_file".equals(args[index])) {
+ index++;
+ File file = new File((String) args[index]);
+ FileUtil.writeToFile(reportString, file);
+ }
+ }
+
+ CommandResult result = new CommandResult(status);
+ result.setStderr("acloud create");
+ result.setStdout("acloud create");
+ return result;
+ }
+ };
+
EasyMock.expect(
runUtil.runTimedCmd(
EasyMock.eq(ACLOUD_TIMEOUT),
- EasyMock.startsWith(mAcloud.getAbsolutePath()),
+ EasyMock.eq(mAcloud.getAbsolutePath()),
EasyMock.eq("create"),
EasyMock.eq("--local-instance"),
- EasyMock.eq("1"),
EasyMock.eq("--local-image"),
EasyMock.capture(imageDir),
+ EasyMock.eq("--local-tool"),
+ EasyMock.capture(hostPackageDir),
+ EasyMock.eq("--report_file"),
+ EasyMock.capture(reportFile),
+ EasyMock.eq("--no-autoconnect"),
EasyMock.eq("--yes"),
EasyMock.eq("--skip-pre-run-check"),
EasyMock.eq("-vv"),
EasyMock.eq("-test")))
- .andReturn(result);
+ .andAnswer(writeToReportFile);
return runUtil;
}
@@ -221,10 +278,11 @@
EasyMock.expect(
runUtil.runTimedCmd(
EasyMock.eq(ACLOUD_TIMEOUT),
- EasyMock.startsWith(mAcloud.getAbsolutePath()),
+ EasyMock.eq(mAcloud.getAbsolutePath()),
EasyMock.eq("delete"),
+ EasyMock.eq("--local-only"),
EasyMock.eq("--instance-names"),
- EasyMock.eq("local-instance-1"),
+ EasyMock.eq(INSTANCE_NAME),
EasyMock.eq("-vv")))
.andReturn(result);
@@ -269,10 +327,16 @@
@Test
public void testPreinvocationSetupSuccess()
throws DeviceNotAvailableException, IOException, TargetSetupError {
+ Capture<String> reportFile = new Capture<String>();
Capture<String> hostPackageDir = new Capture<String>();
Capture<String> imageDir = new Capture<String>();
IRunUtil acloudCreateRunUtil =
- mockAcloudCreate(CommandStatus.SUCCESS, hostPackageDir, imageDir);
+ mockAcloudCreate(
+ CommandStatus.SUCCESS,
+ SUCCESS_REPORT_STRING,
+ reportFile,
+ hostPackageDir,
+ imageDir);
IRunUtil acloudDeleteRunUtil = mockAcloudDelete(CommandStatus.SUCCESS);
@@ -281,11 +345,12 @@
IDevice mockOnlineDevice = EasyMock.createMock(IDevice.class);
EasyMock.expect(mockOnlineDevice.getSerialNumber()).andReturn(ONLINE_SERIAL_NUMBER);
- replayAllMocks(acloudCreateRunUtil, acloudDeleteRunUtil, testLogger, mockOnlineDevice);
+ EasyMock.replay(acloudCreateRunUtil, acloudDeleteRunUtil, testLogger, mockOnlineDevice);
// Test setUp.
mLocalAvd.setTestLogger(testLogger);
mLocalAvd.currentRunUtil = acloudCreateRunUtil;
+ mLocalAvd.expectToConnect = true;
mLocalAvd.preInvocationSetup(mMockDeviceBuildInfo, null);
Assert.assertEquals(ONLINE_SERIAL_NUMBER, mLocalAvd.getIDevice().getSerialNumber());
@@ -300,34 +365,42 @@
// Create the logs and configuration that the local AVD object expects.
File runtimeDir =
FileUtil.getFileForPath(
- mTmpDir, "acloud_cvd_temp", "instance_home_1", "cuttlefish_runtime");
+ mTmpDir, "acloud_cvd_temp", INSTANCE_NAME, "cuttlefish_runtime");
Assert.assertTrue(runtimeDir.mkdirs());
createEmptyFiles(
runtimeDir, "kernel.log", "logcat", "launcher.log", "cuttlefish_config.json");
// Test tearDown.
mLocalAvd.currentRunUtil = acloudDeleteRunUtil;
+ mLocalAvd.expectToConnect = false;
mLocalAvd.postInvocationTearDown(null);
assertFinalDeviceState(mLocalAvd.getIDevice());
+ Assert.assertFalse(new File(reportFile.getValue()).exists());
Assert.assertFalse(capturedHostPackageDir.exists());
Assert.assertFalse(capturedImageDir.exists());
}
- /** Test that the device cannot boot within timeout. */
+ /** Test that the acloud command reports failure. */
@Test
- public void testPreInvocationSetupTimeout() throws DeviceNotAvailableException {
+ public void testPreInvocationSetupBootFailure() throws DeviceNotAvailableException {
+ Capture<String> reportFile = new Capture<String>();
Capture<String> hostPackageDir = new Capture<String>();
Capture<String> imageDir = new Capture<String>();
IRunUtil acloudCreateRunUtil =
- mockAcloudCreate(CommandStatus.TIMED_OUT, hostPackageDir, imageDir);
+ mockAcloudCreate(
+ CommandStatus.SUCCESS,
+ FAILURE_REPORT_STRING,
+ reportFile,
+ hostPackageDir,
+ imageDir);
IRunUtil acloudDeleteRunUtil = mockAcloudDelete(CommandStatus.FAILED);
ITestLogger testLogger = EasyMock.createMock(ITestLogger.class);
- replayAllMocks(acloudCreateRunUtil, acloudDeleteRunUtil, testLogger);
+ EasyMock.replay(acloudCreateRunUtil, acloudDeleteRunUtil, testLogger);
// Test setUp.
TargetSetupError expectedException = null;
@@ -340,7 +413,7 @@
expectedException = e;
}
- Assert.assertEquals(ONLINE_SERIAL_NUMBER, mLocalAvd.getIDevice().getSerialNumber());
+ Assert.assertEquals(STUB_SERIAL_NUMBER, mLocalAvd.getIDevice().getSerialNumber());
File capturedHostPackageDir = new File(hostPackageDir.getValue());
File capturedImageDir = new File(imageDir.getValue());
@@ -353,23 +426,23 @@
assertFinalDeviceState(mLocalAvd.getIDevice());
+ Assert.assertFalse(new File(reportFile.getValue()).exists());
Assert.assertFalse(capturedHostPackageDir.exists());
Assert.assertFalse(capturedImageDir.exists());
}
- /** Test that the device fails to boot. */
+ /** Test that the acloud command fails, and the report is empty. */
@Test
public void testPreInvocationSetupFailure() throws DeviceNotAvailableException {
+ Capture<String> reportFile = new Capture<String>();
Capture<String> hostPackageDir = new Capture<String>();
Capture<String> imageDir = new Capture<String>();
IRunUtil acloudCreateRunUtil =
- mockAcloudCreate(CommandStatus.FAILED, hostPackageDir, imageDir);
-
- IRunUtil acloudDeleteRunUtil = mockAcloudDelete(CommandStatus.FAILED);
+ mockAcloudCreate(CommandStatus.FAILED, "", reportFile, hostPackageDir, imageDir);
ITestLogger testLogger = EasyMock.createMock(ITestLogger.class);
- replayAllMocks(acloudCreateRunUtil, acloudDeleteRunUtil, testLogger);
+ EasyMock.replay(acloudCreateRunUtil, testLogger);
// Test setUp.
TargetSetupError expectedException = null;
@@ -382,7 +455,7 @@
expectedException = e;
}
- Assert.assertEquals(ONLINE_SERIAL_NUMBER, mLocalAvd.getIDevice().getSerialNumber());
+ Assert.assertEquals(STUB_SERIAL_NUMBER, mLocalAvd.getIDevice().getSerialNumber());
File capturedHostPackageDir = new File(hostPackageDir.getValue());
File capturedImageDir = new File(imageDir.getValue());
@@ -390,11 +463,12 @@
Assert.assertTrue(capturedImageDir.isDirectory());
// Test tearDown.
- mLocalAvd.currentRunUtil = acloudDeleteRunUtil;
+ mLocalAvd.currentRunUtil = null;
mLocalAvd.postInvocationTearDown(expectedException);
assertFinalDeviceState(mLocalAvd.getIDevice());
+ Assert.assertFalse(new File(reportFile.getValue()).exists());
Assert.assertFalse(capturedHostPackageDir.exists());
Assert.assertFalse(capturedImageDir.exists());
}
diff --git a/tests/src/com/android/tradefed/device/NativeDeviceTest.java b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
index c45e163..60e316b 100644
--- a/tests/src/com/android/tradefed/device/NativeDeviceTest.java
+++ b/tests/src/com/android/tradefed/device/NativeDeviceTest.java
@@ -1666,6 +1666,106 @@
EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
}
+ /** Unit test for {@link NativeDevice#rebootIntoBootloader()}}. */
+ @Test
+ public void testRebootIntoBootloader() throws Exception {
+ NativeDevice testDevice =
+ new NativeDevice(mMockIDevice, mMockStateMonitor, mMockDvcMonitor) {
+ @Override
+ public TestDeviceState getDeviceState() {
+ return TestDeviceState.ONLINE;
+ }
+ };
+ String into = "bootloader";
+ mMockIDevice.reboot(into);
+ EasyMock.expectLastCall();
+ EasyMock.expect(mMockStateMonitor.waitForDeviceBootloader(EasyMock.anyLong()))
+ .andReturn(true);
+ EasyMock.replay(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+ testDevice.rebootIntoBootloader();
+ EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+ }
+
+ /**
+ * Unit test for {@link NativeDevice#rebootIntoBootloader()}} when device is already in fastboot
+ * mode.
+ */
+ @Test
+ public void testRebootIntoBootloader_forceFastboot() throws Exception {
+ TestableAndroidNativeDevice testDevice =
+ new TestableAndroidNativeDevice() {
+ @Override
+ public TestDeviceState getDeviceState() {
+ return TestDeviceState.FASTBOOT;
+ }
+
+ @Override
+ public CommandResult executeFastbootCommand(String... cmdArgs)
+ throws DeviceNotAvailableException, UnsupportedOperationException {
+ if (cmdArgs[0].equals("reboot-bootloader")) {
+ wasCalled = true;
+ }
+ return new CommandResult();
+ }
+ };
+ EasyMock.expect(mMockStateMonitor.waitForDeviceBootloader(EasyMock.anyLong()))
+ .andReturn(true);
+ EasyMock.replay(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+ testDevice.rebootIntoBootloader();
+ assertTrue(testDevice.wasCalled);
+ EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+ }
+
+ /** Unit test for {@link NativeDevice#rebootIntoFastbootd()}}. */
+ @Test
+ public void testRebootIntoFastbootd() throws Exception {
+ NativeDevice testDevice =
+ new NativeDevice(mMockIDevice, mMockStateMonitor, mMockDvcMonitor) {
+ @Override
+ public TestDeviceState getDeviceState() {
+ return TestDeviceState.ONLINE;
+ }
+ };
+ String into = "fastboot";
+ mMockIDevice.reboot(into);
+ EasyMock.expectLastCall();
+ EasyMock.expect(mMockStateMonitor.waitForDeviceBootloader(EasyMock.anyLong()))
+ .andReturn(true);
+ EasyMock.replay(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+ testDevice.rebootIntoFastbootd();
+ EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+ }
+
+ /**
+ * Unit test for {@link NativeDevice#rebootIntoFastbootd()}} when device is already in fastboot
+ * mode.
+ */
+ @Test
+ public void testRebootIntoFastbootd_forceFastboot() throws Exception {
+ TestableAndroidNativeDevice testDevice =
+ new TestableAndroidNativeDevice() {
+ @Override
+ public TestDeviceState getDeviceState() {
+ return TestDeviceState.FASTBOOT;
+ }
+
+ @Override
+ public CommandResult executeFastbootCommand(String... cmdArgs)
+ throws DeviceNotAvailableException, UnsupportedOperationException {
+ if (cmdArgs[0].equals("reboot-fastboot")) {
+ wasCalled = true;
+ }
+ return new CommandResult();
+ }
+ };
+ EasyMock.expect(mMockStateMonitor.waitForDeviceBootloader(EasyMock.anyLong()))
+ .andReturn(true);
+ EasyMock.replay(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+ testDevice.rebootIntoFastbootd();
+ assertTrue(testDevice.wasCalled);
+ EasyMock.verify(mMockIDevice, mMockStateMonitor, mMockDvcMonitor);
+ }
+
/** Unit test for {@link NativeDevice#unlockDevice()} already decrypted. */
@Test
public void testUnlockDevice_skipping() throws Exception {
diff --git a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
index 7f56715..b0da7d4 100644
--- a/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
+++ b/tests/src/com/android/tradefed/device/contentprovider/ContentProviderHandlerTest.java
@@ -364,8 +364,8 @@
ContentProviderHandler.createEscapedContentUri("filepath/file name spaced (data)");
// We expect the full url to be quoted to avoid space issues and the URL to be encoded.
assertEquals(
- "\"content://android.tradefed.contentprovider/filepath%252Ffile%2520name"
- + "%2520spaced%2520%28data%29\"",
+ "\"content://android.tradefed.contentprovider/filepath%252Ffile+name+spaced+"
+ + "%2528data%2529\"",
espacedUrl);
}
diff --git a/tests/src/com/android/tradefed/device/metric/HostStatsdMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/HostStatsdMetricCollectorTest.java
index 8056036..8f13ba9 100644
--- a/tests/src/com/android/tradefed/device/metric/HostStatsdMetricCollectorTest.java
+++ b/tests/src/com/android/tradefed/device/metric/HostStatsdMetricCollectorTest.java
@@ -20,6 +20,7 @@
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;
@@ -31,6 +32,7 @@
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.TestDescription;
import org.junit.Before;
import org.junit.Rule;
@@ -43,15 +45,15 @@
import java.io.File;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
/** Unit Tests for {@link HostStatsdMetricCollector}. */
@RunWith(JUnit4.class)
public class HostStatsdMetricCollectorTest {
private static final String STATSD_CONFIG = "statsd.config";
- private static final String[] DEVICE_SERIALS = new String[] {"device_1", "device_2"};
private static final long CONFIG_ID = 54321L;
@Mock private IInvocationContext mContext;
@@ -59,34 +61,58 @@
@Spy private HostStatsdMetricCollector mCollector;
@Rule public TemporaryFolder mFolder = new TemporaryFolder();
- @Before
- public void setUp() throws IOException {
- initMocks(this);
- }
+ private TestDescription mTest = new TestDescription("Foo", "Bar");
+ private List<ITestDevice> mDevices =
+ Stream.of("device_1", "device_2").map(this::mockDevice).collect(Collectors.toList());
+ private HashMap<String, Metric> mMetrics = new HashMap<>();
- /** Test that a binary config is pushed and report is dumped from multiple devices. */
- @Test
- public void testMetricCollection_binaryConfig_multiDevice()
- throws IOException, ConfigurationException, DeviceNotAvailableException {
+ @Before
+ public void setUp() throws IOException, ConfigurationException, DeviceNotAvailableException {
+ initMocks(this);
OptionSetter options = new OptionSetter(mCollector);
options.setOptionValue(
"binary-stats-config", mFolder.newFile(STATSD_CONFIG).getAbsolutePath());
- List<ITestDevice> devices = new ArrayList<>();
- for (String serial : DEVICE_SERIALS) {
- devices.add(mockDevice(serial));
- }
- when(mContext.getDevices()).thenReturn(devices);
+ when(mContext.getDevices()).thenReturn(mDevices);
doReturn(CONFIG_ID)
.when(mCollector)
.pushBinaryStatsConfig(any(ITestDevice.class), any(File.class));
+ }
- HashMap<String, Metric> runMetrics = new HashMap<>();
+ /** Test at per-test level that a binary config is pushed and report is dumped */
+ @Test
+ public void testCollect_perTest()
+ throws IOException, DeviceNotAvailableException, ConfigurationException {
+ OptionSetter options = new OptionSetter(mCollector);
+ options.setOptionValue("per-run", "false");
+
mCollector.init(mContext, mListener);
- mCollector.testRunStarted("collect-metrics", 1);
- mCollector.testRunEnded(0L, runMetrics);
+ mCollector.testRunStarted("collect-metrics", 2);
+ mCollector.testStarted(mTest);
+ mCollector.testEnded(mTest, mMetrics);
+ mCollector.testStarted(mTest);
+ mCollector.testEnded(mTest, mMetrics);
+ mCollector.testRunEnded(0L, mMetrics);
- for (ITestDevice device : devices) {
+ for (ITestDevice device : mDevices) {
+ verify(mCollector, times(2)).pushBinaryStatsConfig(eq(device), any(File.class));
+ verify(mCollector, times(2)).getReportByteStream(eq(device), anyLong());
+ verify(mCollector, times(2)).removeConfig(eq(device), anyLong());
+ }
+ }
+
+ /** Test at per-run level that a binary config is pushed and report is dumped at run level. */
+ @Test
+ public void testCollect_perRun() throws IOException, DeviceNotAvailableException {
+ mCollector.init(mContext, mListener);
+ mCollector.testRunStarted("collect-metrics", 2);
+ mCollector.testStarted(mTest);
+ mCollector.testEnded(mTest, mMetrics);
+ mCollector.testStarted(mTest);
+ mCollector.testEnded(mTest, mMetrics);
+ mCollector.testRunEnded(0L, mMetrics);
+
+ for (ITestDevice device : mDevices) {
verify(mCollector).pushBinaryStatsConfig(eq(device), any(File.class));
verify(mCollector).getReportByteStream(eq(device), anyLong());
verify(mCollector).removeConfig(eq(device), anyLong());
diff --git a/tests/src/com/android/tradefed/invoker/logger/TfObjectTrackerTest.java b/tests/src/com/android/tradefed/invoker/logger/TfObjectTrackerTest.java
new file mode 100644
index 0000000..a170e8e
--- /dev/null
+++ b/tests/src/com/android/tradefed/invoker/logger/TfObjectTrackerTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.invoker.logger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.command.CommandOptions;
+import com.android.tradefed.targetprep.BaseTargetPreparer;
+import com.android.tradefed.targetprep.TestAppInstallSetup;
+import com.android.tradefed.targetprep.suite.SuiteApkInstaller;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Map;
+import java.util.UUID;
+
+/** Unit tests for {@link TfObjectTracker}. */
+@RunWith(JUnit4.class)
+public class TfObjectTrackerTest {
+
+ @Test
+ public void testNotTracking() throws Exception {
+ String uuid = UUID.randomUUID().toString();
+ String group = "unit-test-group-" + uuid;
+ ThreadGroup testGroup = new ThreadGroup(group);
+ Map<String, Long> metrics = logMetric(testGroup, true, CommandOptions.class);
+ assertTrue(metrics.isEmpty());
+ }
+
+ @Test
+ public void testTrackingWithParents() throws Exception {
+ String uuid = UUID.randomUUID().toString();
+ String group = "unit-test-group-" + uuid;
+ ThreadGroup testGroup = new ThreadGroup(group);
+ Map<String, Long> metrics = logMetric(testGroup, true, SuiteApkInstaller.class);
+ assertEquals(3, metrics.size());
+ assertEquals(1, metrics.get(SuiteApkInstaller.class.getName()).longValue());
+ assertEquals(1, metrics.get(TestAppInstallSetup.class.getName()).longValue());
+ assertEquals(1, metrics.get(BaseTargetPreparer.class.getName()).longValue());
+ // Add a second time
+ metrics = logMetric(testGroup, true, SuiteApkInstaller.class);
+ assertEquals(3, metrics.size());
+ assertEquals(2, metrics.get(SuiteApkInstaller.class.getName()).longValue());
+ assertEquals(2, metrics.get(TestAppInstallSetup.class.getName()).longValue());
+ assertEquals(2, metrics.get(BaseTargetPreparer.class.getName()).longValue());
+ }
+
+ @Test
+ public void testTracking() throws Exception {
+ String uuid = UUID.randomUUID().toString();
+ String group = "unit-test-group-" + uuid;
+ ThreadGroup testGroup = new ThreadGroup(group);
+ Map<String, Long> metrics = logMetric(testGroup, false, SuiteApkInstaller.class);
+ assertEquals(1, metrics.size());
+ assertEquals(1, metrics.get(SuiteApkInstaller.class.getName()).longValue());
+ assertNull(metrics.get(TestAppInstallSetup.class.getName()));
+ assertNull(metrics.get(BaseTargetPreparer.class.getName()));
+ // Add a second time
+ metrics = logMetric(testGroup, false, SuiteApkInstaller.class);
+ assertEquals(1, metrics.size());
+ assertEquals(2, metrics.get(SuiteApkInstaller.class.getName()).longValue());
+ assertNull(metrics.get(TestAppInstallSetup.class.getName()));
+ assertNull(metrics.get(BaseTargetPreparer.class.getName()));
+ }
+
+ private Map<String, Long> logMetric(
+ ThreadGroup testGroup, boolean trackWithParents, Class<?> toTrack) throws Exception {
+ TestRunnable runnable = new TestRunnable(toTrack, trackWithParents);
+ Thread testThread = new Thread(testGroup, runnable);
+ testThread.setName("TfObjectTrackerTest-test-thread");
+ testThread.setDaemon(true);
+ testThread.start();
+ testThread.join(10000);
+ return runnable.getUsageMap();
+ }
+
+ private class TestRunnable implements Runnable {
+
+ private Class<?> mToTrack;
+ private Map<String, Long> mResultMap;
+ private boolean mTrackWithParents;
+
+ public TestRunnable(Class<?> toTrack, boolean trackWithParents) {
+ mToTrack = toTrack;
+ mTrackWithParents = trackWithParents;
+ }
+
+ @Override
+ public void run() {
+ if (mTrackWithParents) {
+ TfObjectTracker.countWithParents(mToTrack);
+ } else {
+ TfObjectTracker.count(mToTrack);
+ }
+ mResultMap = TfObjectTracker.getUsage();
+ }
+
+ public Map<String, Long> getUsageMap() {
+ return mResultMap;
+ }
+ }
+}
diff --git a/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java b/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
new file mode 100644
index 0000000..8656f66
--- /dev/null
+++ b/tests/src/com/android/tradefed/postprocessor/PerfettoGenericPostProcessorTest.java
@@ -0,0 +1,483 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.tradefed.postprocessor;
+
+import static org.junit.Assert.assertTrue;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import com.android.tradefed.config.ConfigurationException;
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
+import com.android.tradefed.postprocessor.PerfettoGenericPostProcessor.METRIC_FILE_FORMAT;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.LogFile;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.ZipUtil;
+
+import com.google.protobuf.TextFormat;
+import com.google.protobuf.TextFormat.ParseException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import perfetto.protos.PerfettoMergedMetrics.TraceMetrics;
+
+/** Unit tests for {@link PerfettoGenericPostProcessor}. */
+@RunWith(JUnit4.class)
+public class PerfettoGenericPostProcessorTest {
+
+ @Mock private ITestInvocationListener mListener;
+ private PerfettoGenericPostProcessor mProcessor;
+ private OptionSetter mOptionSetter;
+
+ private static final String PREFIX_OPTION = "perfetto-proto-file-prefix";
+ private static final String PREFIX_OPTION_VALUE = "metric-perfetto";
+ private static final String INDEX_OPTION = "perfetto-indexed-list-field";
+ private static final String REGEX_OPTION_VALUE = "perfetto-metric-filter-regex";
+ private static final String ALL_METRICS_OPTION = "perfetto-include-all-metrics";
+ private static final String FILE_FORMAT_OPTION = "trace-processor-output-format";
+
+ File perfettoMetricProtoFile = null;
+
+ @Before
+ public void setUp() throws ConfigurationException {
+ initMocks(this);
+ mProcessor = new PerfettoGenericPostProcessor();
+ mProcessor.init(mListener);
+ mOptionSetter = new OptionSetter(mProcessor);
+ }
+
+ /**
+ * Test metrics count should be zero if "perfetto-include-all-metrics" is not set or set to
+ * false;
+ */
+ @Test
+ public void testNoMetricsByDefault() throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(INDEX_OPTION, "perfetto.protos.AndroidStartupMetric.startup");
+ 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);
+
+ assertTrue(
+ "Number of metrics parsed without indexing is incorrect.",
+ parsedMetrics.size() == 0);
+ }
+
+ /**
+ * Test metrics are filtered correctly when filter regex are passed and
+ * "perfetto-include-all-metrics" is set to false (Note: by default false)
+ */
+ @Test
+ public void testMetricsFilterWithRegEx() throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(INDEX_OPTION, "perfetto.protos.AndroidStartupMetric.startup");
+ mOptionSetter.setOptionValue(REGEX_OPTION_VALUE, "android_startup-startup-1.*");
+ 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);
+
+ assertTrue(
+ "Number of metrics parsed filter regex match is incorrect.",
+ parsedMetrics.size() == 32);
+ assertMetricsContain(parsedMetrics, "android_startup-startup-1-startup_id", 1);
+ assertMetricsContain(
+ parsedMetrics,
+ "android_startup-startup-1-package_name-com.google."
+ + "android.apps.nexuslauncher-to_first_frame-dur_ns",
+ 36175473);
+ }
+
+ /**
+ * Test all metrics are included when "perfetto-include-all-metrics" is set to true and ignores
+ * any of the filter regex set.
+ */
+ @Test
+ public void testAllMetricsOptionIgnoresFilter() throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(INDEX_OPTION, "perfetto.protos.AndroidStartupMetric.startup");
+ 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);
+
+ assertTrue(
+ "Number of metrics parsed without indexing is incorrect.",
+ parsedMetrics.size() == 76);
+ }
+
+ /** Test that the post processor can parse reports from test metrics. */
+ @Test
+ public void testParsingTestMetrics() throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ 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.processTestMetricsAndLogs(
+ new TestDescription("class", "test"), new HashMap<>(), testLogs);
+ assertMetricsContain(
+ parsedMetrics,
+ "android_mem-process_metrics-process_name-"
+ + ".dataservices-total_counters-anon_rss-min",
+ 27938816);
+ }
+
+ /** Test the post processor can parse reports from run metrics. */
+ @Test
+ public void testParsingRunMetrics() throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ 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,
+ "android_mem-process_metrics-process_name-"
+ + ".dataservices-total_counters-anon_rss-min",
+ 27938816);
+ }
+
+ /**
+ * Test metrics count and metrics without indexing. In case of app startup metrics startup
+ * messages for same package name will be overridden without indexing.
+ */
+ @Test
+ public void testParsingWithoutIndexing() throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ 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);
+
+ assertTrue(
+ "Number of metrics parsed without indexing is incorrect.",
+ parsedMetrics.size() == 44);
+ assertMetricsContain(parsedMetrics, "android_startup-startup-startup_id", 2);
+ assertMetricsContain(
+ parsedMetrics,
+ "android_startup-startup-package_name-com.google."
+ + "android.apps.nexuslauncher-to_first_frame-dur_ns",
+ 53102401);
+ }
+
+ /**
+ * Test metrics count and metrics with indexing. In case of app startup metrics, startup
+ * messages for same package name will not be overridden with indexing.
+ */
+ @Test
+ public void testParsingWithIndexing() throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(INDEX_OPTION, "perfetto.protos.AndroidStartupMetric.startup");
+ 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);
+
+ assertTrue(
+ "Number of metrics parsed with indexing is incorrect.", parsedMetrics.size() == 76);
+ assertMetricsContain(parsedMetrics, "android_startup-startup-1-startup_id", 1);
+ assertMetricsContain(
+ parsedMetrics,
+ "android_startup-startup-1-package_name-com.google."
+ + "android.apps.nexuslauncher-to_first_frame-dur_ns",
+ 36175473);
+ assertMetricsContain(parsedMetrics, "android_startup-startup-2-startup_id", 2);
+ assertMetricsContain(
+ parsedMetrics,
+ "android_startup-startup-2-package_name-com.google."
+ + "android.apps.nexuslauncher-to_first_frame-dur_ns",
+ 53102401);
+ }
+
+ /** Test the post processor can parse binary perfetto metric proto format. */
+ @Test
+ public void testParsingBinaryProto() throws ConfigurationException, IOException {
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.binary, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(ALL_METRICS_OPTION, "true");
+ mOptionSetter.setOptionValue(FILE_FORMAT_OPTION, "binary");
+ Map<String, LogFile> testLogs = new HashMap<>();
+ testLogs.put(
+ PREFIX_OPTION_VALUE,
+ new LogFile(perfettoMetricProtoFile.getAbsolutePath(), "some.url", LogDataType.PB));
+ Map<String, Metric.Builder> parsedMetrics =
+ mProcessor.processRunMetricsAndLogs(new HashMap<>(), testLogs);
+ assertMetricsContain(
+ parsedMetrics,
+ "android_mem-process_metrics-process_name-"
+ + ".dataservices-total_counters-anon_rss-min",
+ 27938816);
+ }
+
+ /** Test the post processor can parse binary perfetto metric proto format. */
+ @Test
+ public void testNoSupportForJsonParsing() throws ConfigurationException, IOException {
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ mOptionSetter.setOptionValue(ALL_METRICS_OPTION, "true");
+ mOptionSetter.setOptionValue(FILE_FORMAT_OPTION, "json");
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, 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);
+ assertTrue("Should not have any metrics if json format is set", parsedMetrics.size() == 0);
+ }
+
+ /**
+ * Test the post processor can parse reports from run metrics when the text proto file is
+ * compressed format.
+ */
+ @Test
+ public void testParsingRunMetricsWithCompressedFile()
+ throws ConfigurationException, IOException {
+ // Setup compressed text proto metric file.
+ setupPerfettoMetricFile(METRIC_FILE_FORMAT.text, true);
+ mOptionSetter.setOptionValue(PREFIX_OPTION, PREFIX_OPTION_VALUE);
+ 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,
+ "android_mem-process_metrics-process_name-"
+ + ".dataservices-total_counters-anon_rss-min",
+ 27938816);
+ }
+
+ /** Creates sample perfetto metric proto file used for testing. */
+ private File setupPerfettoMetricFile(METRIC_FILE_FORMAT format, boolean isCompressed)
+ throws IOException {
+ String perfettoTextContent =
+ "android_mem {\n"
+ + " process_metrics {\n"
+ + " process_name: \".dataservices\"\n"
+ + " total_counters {\n"
+ + " anon_rss {\n"
+ + " min: 27938816\n"
+ + " max: 27938816\n"
+ + " avg: 27938816\n"
+ + " }\n"
+ + " file_rss {\n"
+ + " min: 62390272\n"
+ + " max: 62390272\n"
+ + " avg: 62390272\n"
+ + " }\n"
+ + " swap {\n"
+ + " min: 0\n"
+ + " max: 0\n"
+ + " avg: 0\n"
+ + " }\n"
+ + " anon_and_swap {\n"
+ + " min: 27938816\n"
+ + " max: 27938816\n"
+ + " avg: 27938816\n"
+ + " }\n"
+ + " }\n"
+ + "}}"
+ + "android_startup {\n"
+ + " startup {\n"
+ + " startup_id: 1\n"
+ + " package_name: \"com.google.android.apps.nexuslauncher\"\n"
+ + " process_name: \"com.google.android.apps.nexuslauncher\"\n"
+ + " zygote_new_process: false\n"
+ + " to_first_frame {\n"
+ + " dur_ns: 36175473\n"
+ + " main_thread_by_task_state {\n"
+ + " running_dur_ns: 11496200\n"
+ + " runnable_dur_ns: 487290\n"
+ + " uninterruptible_sleep_dur_ns: 0\n"
+ + " interruptible_sleep_dur_ns: 23645107\n"
+ + " }\n"
+ + " other_processes_spawned_count: 0\n"
+ + " time_activity_manager {\n"
+ + " dur_ns: 4135001\n"
+ + " }\n"
+ + " time_activity_resume {\n"
+ + " dur_ns: 345105\n"
+ + " }\n"
+ + " time_choreographer {\n"
+ + " dur_ns: 15314324\n"
+ + " }\n"
+ + " other_process_to_activity_cpu_ratio: 6.9345600857535672\n"
+ + " }\n"
+ + " activity_hosting_process_count: 1\n"
+ + " }\n"
+ + " startup {\n"
+ + " startup_id: 2\n"
+ + " package_name: \"com.google.android.apps.nexuslauncher\"\n"
+ + " process_name: \"com.google.android.apps.nexuslauncher\"\n"
+ + " zygote_new_process: false\n"
+ + " to_first_frame {\n"
+ + " dur_ns: 53102401\n"
+ + " main_thread_by_task_state {\n"
+ + " running_dur_ns: 9766774\n"
+ + " runnable_dur_ns: 320103\n"
+ + " uninterruptible_sleep_dur_ns: 0\n"
+ + " interruptible_sleep_dur_ns: 42358858\n"
+ + " }\n"
+ + " other_processes_spawned_count: 0\n"
+ + " time_activity_manager {\n"
+ + " dur_ns: 4742396\n"
+ + " }\n"
+ + " time_activity_resume {\n"
+ + " dur_ns: 280208\n"
+ + " }\n"
+ + " time_choreographer {\n"
+ + " dur_ns: 13705366\n"
+ + " }\n"
+ + " other_process_to_activity_cpu_ratio: 12.956123015968883\n"
+ + " }\n"
+ + " activity_hosting_process_count: 1\n"
+ + " }\n"
+ + "}";
+ FileWriter fileWriter = null;
+ try {
+ perfettoMetricProtoFile = FileUtil.createTempFile("metric_perfetto", "");
+ fileWriter = new FileWriter(perfettoMetricProtoFile);
+ fileWriter.write(perfettoTextContent);
+ } finally {
+ if (fileWriter != null) {
+ fileWriter.close();
+ }
+ }
+
+ if (format.equals(METRIC_FILE_FORMAT.binary)) {
+ File perfettoBinaryFile = FileUtil.createTempFile("metric_perfetto_binary", ".pb");
+ try (BufferedReader bufferedReader =
+ new BufferedReader(new FileReader(perfettoMetricProtoFile))) {
+ TraceMetrics.Builder builder = TraceMetrics.newBuilder();
+ TextFormat.merge(bufferedReader, builder);
+ builder.build().writeTo(new FileOutputStream(perfettoBinaryFile));
+ } catch (ParseException e) {
+ CLog.e("Failed to merge the perfetto metric file." + e.getMessage());
+ } catch (IOException ioe) {
+ CLog.e(
+ "IOException happened when reading the perfetto metric file."
+ + ioe.getMessage());
+ } finally {
+ perfettoMetricProtoFile.delete();
+ perfettoMetricProtoFile = perfettoBinaryFile;
+ }
+ return perfettoMetricProtoFile;
+ }
+
+ if (isCompressed) {
+ perfettoMetricProtoFile = compressFile(perfettoMetricProtoFile);
+ }
+ return perfettoMetricProtoFile;
+ }
+
+ /** Create a zip file with perfetto metric proto file */
+ private File compressFile(File decompressedFile) throws IOException {
+ File compressedFile = FileUtil.createTempFile("compressed_temp", ".zip");
+ try {
+ ZipUtil.createZip(decompressedFile, compressedFile);
+ } catch (IOException ioe) {
+ CLog.e("Unable to gzip the file.");
+ } finally {
+ decompressedFile.delete();
+ }
+ return compressedFile;
+ }
+
+ @After
+ public void teardown() {
+ if (perfettoMetricProtoFile != null) {
+ perfettoMetricProtoFile.delete();
+ }
+ }
+
+ /** Assert that metrics contain a key and a corresponding value. */
+ private void assertMetricsContain(
+ Map<String, Metric.Builder> metrics, String key, Object value) {
+ assertTrue(
+ String.format(
+ "Metric with key containing %s and value %s was expected but not found.",
+ key, value),
+ metrics.entrySet()
+ .stream()
+ .anyMatch(
+ e ->
+ e.getKey().contains(key)
+ && (String.valueOf(value)
+ .equals(
+ e.getValue()
+ .build()
+ .getMeasurements()
+ .getSingleString()))));
+ }
+}
diff --git a/tests/src/com/android/tradefed/targetprep/AllTestAppsInstallSetupTest.java b/tests/src/com/android/tradefed/targetprep/AllTestAppsInstallSetupTest.java
index fa9673f..888afd1 100644
--- a/tests/src/com/android/tradefed/targetprep/AllTestAppsInstallSetupTest.java
+++ b/tests/src/com/android/tradefed/targetprep/AllTestAppsInstallSetupTest.java
@@ -33,6 +33,7 @@
mMockTestDevice = EasyMock.createMock(ITestDevice.class);
EasyMock.expect(mMockTestDevice.getSerialNumber()).andStubReturn(SERIAL);
EasyMock.expect(mMockTestDevice.getDeviceDescriptor()).andStubReturn(null);
+ EasyMock.expect(mMockTestDevice.isAppEnumerationSupported()).andStubReturn(false);
}
public void testNotIDeviceBuildInfo() throws DeviceNotAvailableException {
@@ -93,6 +94,22 @@
FileUtil.recursiveDelete(testDir);
}
}
+ public void testSetupForceQueryable() throws Exception {
+ EasyMock.expect(mMockTestDevice.isAppEnumerationSupported()).andReturn(true);
+ File testDir = FileUtil.createTempDir("TestAppSetupForceQueryableTest");
+ // fake hierarchy of directory and files
+ FileUtil.createTempFile("fakeApk", ".apk", testDir);
+ try {
+ EasyMock.expect(mMockBuildInfo.getTestsDir()).andReturn(testDir);
+ EasyMock.expect(mMockTestDevice.installPackage((File)EasyMock.anyObject(),
+ EasyMock.eq(true), EasyMock.eq("--force-queryable"))).andReturn(null);
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
+ mPrep.setUp(mMockTestDevice, mMockBuildInfo);
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
+ } finally {
+ FileUtil.recursiveDelete(testDir);
+ }
+ }
public void testInstallFailure() throws DeviceNotAvailableException {
final String failure = "INSTALL_PARSE_FAILED_MANIFEST_MALFORMED";
diff --git a/tests/src/com/android/tradefed/targetprep/AppSetupTest.java b/tests/src/com/android/tradefed/targetprep/AppSetupTest.java
index 870fadf..921c0cc 100644
--- a/tests/src/com/android/tradefed/targetprep/AppSetupTest.java
+++ b/tests/src/com/android/tradefed/targetprep/AppSetupTest.java
@@ -38,7 +38,6 @@
import org.mockito.Mockito;
import java.io.File;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@@ -59,11 +58,12 @@
private List<VersionedFile> mApps;
@Before
- public void setUp() throws IOException {
+ public void setUp() throws Exception {
mAppSetup = new AppSetup();
mMockDevice = EasyMock.createMock(ITestDevice.class);
EasyMock.expect(mMockDevice.getSerialNumber()).andStubReturn(SERIAL);
EasyMock.expect(mMockDevice.getDeviceDescriptor()).andStubReturn(null);
+ EasyMock.expect(mMockDevice.isAppEnumerationSupported()).andStubReturn(false);
mMockBuildInfo = EasyMock.createMock(IBuildInfo.class);
mMockAaptParser = Mockito.mock(AaptParser.class);
mApps = new ArrayList<>();
diff --git a/tests/src/com/android/tradefed/targetprep/InstallAllTestZipAppsSetupTest.java b/tests/src/com/android/tradefed/targetprep/InstallAllTestZipAppsSetupTest.java
index 46dda76..ff1f7d1 100644
--- a/tests/src/com/android/tradefed/targetprep/InstallAllTestZipAppsSetupTest.java
+++ b/tests/src/com/android/tradefed/targetprep/InstallAllTestZipAppsSetupTest.java
@@ -76,6 +76,7 @@
mMockTestDevice = EasyMock.createMock(ITestDevice.class);
EasyMock.expect(mMockTestDevice.getSerialNumber()).andStubReturn(SERIAL);
EasyMock.expect(mMockTestDevice.getDeviceDescriptor()).andStubReturn(null);
+ EasyMock.expect(mMockTestDevice.isAppEnumerationSupported()).andStubReturn(false);
}
@After
@@ -153,6 +154,28 @@
}
@Test
+ public void testForceQueryableSuccess() throws Exception {
+ EasyMock.expect(mMockTestDevice.isAppEnumerationSupported()).andReturn(true);
+ mPrep.setTestZipName("zip");
+
+ mMockBuildInfo.getFile((String) EasyMock.anyObject());
+ EasyMock.expectLastCall().andReturn(new File("zip"));
+
+ setMockUnzipDir();
+ mMockTestDevice.installPackage(
+ EasyMock.anyObject(), EasyMock.anyBoolean(), EasyMock.eq("--force-queryable"));
+ EasyMock.expectLastCall().andReturn(null).times(3);
+ mMockTestDevice.uninstallPackage((String) EasyMock.anyObject());
+ EasyMock.expectLastCall().andReturn(null).times(3);
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
+
+ mPrep.setUp(mMockTestDevice, mMockBuildInfo);
+ mPrep.tearDown(mMockTestDevice, mMockBuildInfo, null);
+
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
+ }
+
+ @Test
public void testSuccessNoTearDown() throws Exception {
mPrep.setTestZipName("zip");
mPrep.setCleanup(false);
diff --git a/tests/src/com/android/tradefed/targetprep/InstallApkSetupTest.java b/tests/src/com/android/tradefed/targetprep/InstallApkSetupTest.java
index 8d92719..7c9f0f1 100644
--- a/tests/src/com/android/tradefed/targetprep/InstallApkSetupTest.java
+++ b/tests/src/com/android/tradefed/targetprep/InstallApkSetupTest.java
@@ -15,6 +15,9 @@
*/
package com.android.tradefed.targetprep;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
import com.android.tradefed.build.IDeviceBuildInfo;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
@@ -31,8 +34,6 @@
import java.util.ArrayList;
import java.util.Collection;
-import static org.junit.Assert.*;
-
/**
* Unit tests for {@link InstallApkSetup}
*/
@@ -55,6 +56,7 @@
mMockTestDevice = EasyMock.createMock(ITestDevice.class);
EasyMock.expect(mMockTestDevice.getSerialNumber()).andStubReturn(SERIAL);
EasyMock.expect(mMockTestDevice.getDeviceDescriptor()).andStubReturn(null);
+ EasyMock.expect(mMockTestDevice.isAppEnumerationSupported()).andStubReturn(false);
testDir = FileUtil.createTempDir("TestApkDir");
testFile = FileUtil.createTempFile("File", ".apk", testDir);
@@ -82,6 +84,24 @@
}
/**
+ * Test {@link InstallApkSetupTest#setUp()} by successfully installing 2 Apk files
+ */
+ @Test
+ public void testSetupForceQueryable()
+ throws DeviceNotAvailableException, BuildError, TargetSetupError {
+ EasyMock.expect(mMockTestDevice.isAppEnumerationSupported()).andReturn(true);
+
+ testCollectionFiles.add(testFile);
+ testCollectionFiles.add(testFile);
+ mInstallApkSetup.setApkPaths(testCollectionFiles);
+ EasyMock.expect(mMockTestDevice.installPackage((File) EasyMock.anyObject(),
+ EasyMock.eq(true), EasyMock.eq("--force-queryable"))).andReturn(null).times(2);
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
+ mInstallApkSetup.setUp(mMockTestDevice, mMockBuildInfo);
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
+ }
+
+ /**
* Test {@link InstallApkSetupTest#setUp()} by installing a non-existing Apk
*/
@Test
diff --git a/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java b/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
index c8ca2e6..4ab06a7 100644
--- a/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
+++ b/tests/src/com/android/tradefed/targetprep/PushFilePreparerTest.java
@@ -30,6 +30,7 @@
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.invoker.TestInformation;
import com.android.tradefed.testtype.Abi;
import com.android.tradefed.testtype.suite.ModuleDefinition;
import com.android.tradefed.util.FileUtil;
@@ -53,6 +54,7 @@
private PushFilePreparer mPreparer = null;
private ITestDevice mMockDevice = null;
private OptionSetter mOptionSetter = null;
+ private TestInformation mTestInfo;
@Before
public void setUp() throws Exception {
@@ -61,13 +63,16 @@
EasyMock.expect(mMockDevice.getSerialNumber()).andStubReturn("SERIAL");
mPreparer = new PushFilePreparer();
mOptionSetter = new OptionSetter(mPreparer);
+ IInvocationContext context = new InvocationContext();
+ context.addAllocatedDevice("device", mMockDevice);
+ mTestInfo = TestInformation.newBuilder().setInvocationContext(context).build();
}
/** When there's nothing to be done, expect no exception to be thrown */
@Test
public void testNoop() throws Exception {
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, null);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
}
@@ -77,8 +82,9 @@
mOptionSetter.setOptionValue("post-push", "ls /");
EasyMock.replay(mMockDevice);
try {
+ mTestInfo.getContext().addDeviceBuildInfo("device", new BuildInfo());
// Should throw TargetSetupError and _not_ run any post-push command
- mPreparer.setUp(mMockDevice, null);
+ mPreparer.setUp(mTestInfo);
fail("TargetSetupError not thrown");
} catch (TargetSetupError e) {
// expected
@@ -96,8 +102,9 @@
.andReturn(Boolean.FALSE);
EasyMock.replay(mMockDevice);
try {
+ mTestInfo.getContext().addDeviceBuildInfo("device", new BuildInfo());
// Should throw TargetSetupError and _not_ run any post-push command
- mPreparer.setUp(mMockDevice, null);
+ mPreparer.setUp(mTestInfo);
fail("TargetSetupError not thrown");
} catch (TargetSetupError e) {
// expected
@@ -123,8 +130,9 @@
mMockDevice.pushFile(
EasyMock.eq(testFile), EasyMock.eq("/data/local/tmp/")))
.andReturn(Boolean.TRUE);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
} finally {
FileUtil.recursiveDelete(testsDir);
@@ -150,8 +158,9 @@
EasyMock.eq("/data/local/tmp/"),
EasyMock.anyObject()))
.andReturn(Boolean.TRUE);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
} finally {
FileUtil.recursiveDelete(testsDir);
@@ -171,8 +180,9 @@
EasyMock.expect(mMockDevice.doesFileExist("/data/local/tmp/file")).andReturn(true);
EasyMock.expect(mMockDevice.isDirectory("/data/local/tmp/file")).andReturn(false);
EasyMock.replay(mMockDevice);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
try {
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
fail("Should have thrown an exception.");
} catch (TargetSetupError expected) {
// Expected
@@ -207,8 +217,9 @@
EasyMock.eq(testFile2),
EasyMock.eq("/data/local/tmp/perf_test")))
.andReturn(Boolean.TRUE);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
} finally {
FileUtil.recursiveDelete(testsDir);
@@ -240,8 +251,9 @@
EasyMock.eq(testFile2),
EasyMock.eq("/data/local/tmp/perf_test")))
.andReturn(Boolean.TRUE);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
} finally {
FileUtil.recursiveDelete(testsDir);
@@ -261,9 +273,9 @@
// Because we're only warning, the post-push command should be run despite the push failures
EasyMock.expect(mMockDevice.executeShellCommand(EasyMock.eq("ls /"))).andReturn("");
EasyMock.replay(mMockDevice);
-
+ mTestInfo.getContext().addDeviceBuildInfo("device", new BuildInfo());
// Don't expect any exceptions to be thrown
- mPreparer.setUp(mMockDevice, null);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
}
@@ -374,9 +386,9 @@
EasyMock.eq("/data/local/tmp/debugger"),
EasyMock.capture(capture)))
.andReturn(true);
-
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
// The x86 folder was not filtered
Set<String> capValue = capture.getValue();
@@ -411,9 +423,9 @@
EasyMock.eq("/data/local/tmp/folder"),
EasyMock.capture(capture)))
.andReturn(true);
-
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
// The x86 folder was not filtered
Set<String> capValue = capture.getValue();
@@ -446,8 +458,9 @@
EasyMock.eq("/data/local/tmp/debugger"),
EasyMock.capture(capture)))
.andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
// The x86 folder was not filtered
Set<String> capValue = capture.getValue();
@@ -487,8 +500,9 @@
EasyMock.eq("/data/local/tmp/lib"),
EasyMock.capture(capture)))
.andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
// The x86 folder was not filtered
Set<String> capValue = capture.getValue();
@@ -525,8 +539,9 @@
"target/testcases/aaaaa/x86_64/file")),
EasyMock.eq("/data/local/tmp/file")))
.andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
} finally {
FileUtil.recursiveDelete(tmpFolder);
@@ -562,8 +577,9 @@
EasyMock.eq("/data/local/tmp/lib"),
EasyMock.capture(capture)))
.andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
// The x86 folder was not filtered
Set<String> capValue = capture.getValue();
@@ -600,8 +616,9 @@
EasyMock.eq("/data/local/tmp/lib"),
EasyMock.capture(capture)))
.andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
// The x86 folder was not filtered
Set<String> capValue = capture.getValue();
@@ -643,8 +660,9 @@
EasyMock.eq("/data/local/tmp/debugger"),
EasyMock.capture(capture)))
.andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
// The x86 folder was not filtered
Set<String> capValue = capture.getValue();
@@ -682,8 +700,9 @@
EasyMock.eq("/data/local/tmp/lib"),
EasyMock.capture(capture)))
.andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
// The x86 folder was not filtered
Set<String> capValue = capture.getValue();
@@ -756,8 +775,9 @@
EasyMock.eq("/data/local/tmp/propertyinfoserializer_tests"),
EasyMock.capture(capture)))
.andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
// The x86 folder was not filtered
Set<String> capValue = capture.getValue();
@@ -793,8 +813,9 @@
EasyMock.eq("/data/local/tmp/lib"),
EasyMock.capture(capture)))
.andReturn(true);
+ mTestInfo.getContext().addDeviceBuildInfo("device", info);
EasyMock.replay(mMockDevice);
- mPreparer.setUp(mMockDevice, info);
+ mPreparer.setUp(mTestInfo);
EasyMock.verify(mMockDevice);
// The x86 folder was not filtered
Set<String> capValue = capture.getValue();
diff --git a/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java b/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java
index ecd64da..258dd7a 100644
--- a/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java
+++ b/tests/src/com/android/tradefed/targetprep/TestAppInstallSetupTest.java
@@ -100,6 +100,7 @@
mMockTestDevice = EasyMock.createMock(ITestDevice.class);
EasyMock.expect(mMockTestDevice.getSerialNumber()).andStubReturn(SERIAL);
EasyMock.expect(mMockTestDevice.getDeviceDescriptor()).andStubReturn(null);
+ EasyMock.expect(mMockTestDevice.isAppEnumerationSupported()).andStubReturn(false);
IInvocationContext context = new InvocationContext();
context.addAllocatedDevice("device", mMockTestDevice);
context.addDeviceBuildInfo("device", mMockBuildInfo);
@@ -246,6 +247,25 @@
}
@Test
+ public void testSetup_forceQueryable() throws Exception {
+ EasyMock.expect(mMockTestDevice.isAppEnumerationSupported()).andReturn(true);
+
+ OptionSetter setter = new OptionSetter(mPrep);
+
+ EasyMock.expect(mMockTestDevice.installPackage(
+ EasyMock.eq(fakeApk), EasyMock.eq(true), EasyMock.eq("--force-queryable")))
+ .andReturn(null);
+ EasyMock.expect(
+ mMockTestDevice.installPackages(
+ EasyMock.eq(mTestSplitApkFiles), EasyMock.eq(true),
+ EasyMock.eq("--force-queryable")))
+ .andReturn(null);
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
+ mPrep.setUp(mTestInfo);
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
+ }
+
+ @Test
public void testInstallFailure() throws Exception {
final String failure = "INSTALL_PARSE_FAILED_MANIFEST_MALFORMED";
EasyMock.expect(mMockTestDevice.installPackage(EasyMock.eq(fakeApk), EasyMock.eq(true)))
@@ -293,13 +313,14 @@
@Test
public void testMissingApk() throws Exception {
fakeApk = null; // Apk doesn't exist
-
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
try {
mPrep.setUp(mTestInfo);
fail("TestAppInstallSetup#setUp() did not raise TargetSetupError with missing apk.");
} catch (TargetSetupError e) {
assertTrue(e.getMessage().contains("not found"));
}
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
}
/**
@@ -309,13 +330,14 @@
@Test
public void testUnreadableApk() throws Exception {
fakeApk = new File("/not/a/real/path"); // Apk cannot be read
-
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
try {
mPrep.setUp(mTestInfo);
fail("TestAppInstallSetup#setUp() did not raise TargetSetupError with unreadable apk.");
} catch (TargetSetupError e) {
assertTrue(e.getMessage().contains("not read"));
}
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
}
/**
@@ -326,8 +348,9 @@
public void testMissingApk_silent() throws Exception {
fakeApk = null; // Apk doesn't exist
mSetter.setOptionValue("throw-if-not-found", "false");
-
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
mPrep.setUp(mTestInfo);
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
}
/**
@@ -338,8 +361,9 @@
public void testUnreadableApk_silent() throws Exception {
fakeApk = new File("/not/a/real/path"); // Apk cannot be read
mSetter.setOptionValue("throw-if-not-found", "false");
-
+ EasyMock.replay(mMockBuildInfo, mMockTestDevice);
mPrep.setUp(mTestInfo);
+ EasyMock.verify(mMockBuildInfo, mMockTestDevice);
}
/**
diff --git a/tests/src/com/android/tradefed/testtype/junit4/BaseHostJUnit4TestTest.java b/tests/src/com/android/tradefed/testtype/junit4/BaseHostJUnit4TestTest.java
index 244b052..2ed02a2 100644
--- a/tests/src/com/android/tradefed/testtype/junit4/BaseHostJUnit4TestTest.java
+++ b/tests/src/com/android/tradefed/testtype/junit4/BaseHostJUnit4TestTest.java
@@ -134,6 +134,7 @@
mMockListener = EasyMock.createMock(ITestInvocationListener.class);
mMockBuild = EasyMock.createMock(IBuildInfo.class);
mMockDevice = EasyMock.createMock(ITestDevice.class);
+ EasyMock.expect(mMockDevice.isAppEnumerationSupported()).andStubReturn(false);
mMockContext = new InvocationContext();
mMockContext.addAllocatedDevice(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockDevice);
mMockContext.addDeviceBuildInfo(ConfigurationDef.DEFAULT_DEVICE_NAME, mMockBuild);
diff --git a/util-apps/ContentProvider/hostsidetests/src/com/android/tradefed/contentprovider/ContentProviderTest.java b/util-apps/ContentProvider/hostsidetests/src/com/android/tradefed/contentprovider/ContentProviderTest.java
index 0e6a655..5f23f21 100644
--- a/util-apps/ContentProvider/hostsidetests/src/com/android/tradefed/contentprovider/ContentProviderTest.java
+++ b/util-apps/ContentProvider/hostsidetests/src/com/android/tradefed/contentprovider/ContentProviderTest.java
@@ -75,6 +75,23 @@
}
}
+ /**
+ * '+' character is special in URLs as it can be decoded incorrectly as a space. Ensure it works
+ * and our encoding/decoding handles it well.
+ */
+ @Test
+ public void testPushFile_encode_plus() throws Exception {
+ // Name with '+'
+ File tmpFile = FileUtil.createTempFile("tmpFileToPush+(test)", ".txt");
+ try {
+ boolean res = mHandler.pushFile(tmpFile, "/sdcard/" + tmpFile.getName());
+ assertTrue(res);
+ assertTrue(getDevice().doesFileExist(mCurrentUserStoragePath + tmpFile.getName()));
+ } finally {
+ FileUtil.deleteFile(tmpFile);
+ }
+ }
+
@Test
public void testPushFile() throws Exception {
File tmpFile = FileUtil.createTempFile("tmpFileToPush", ".txt");