Merge "Add setAltDirBehavior to TestAppInstallSetup" into oc-mr1-dev
diff --git a/prod-tests/src/com/android/performance/tests/EmmcPerformanceTest.java b/prod-tests/src/com/android/performance/tests/EmmcPerformanceTest.java
index 91473ac..81eebc2 100644
--- a/prod-tests/src/com/android/performance/tests/EmmcPerformanceTest.java
+++ b/prod-tests/src/com/android/performance/tests/EmmcPerformanceTest.java
@@ -376,7 +376,7 @@
if (mTestDevice.enableAdbRoot()) {
String output = mTestDevice.executeShellCommand("vdc dump | grep cache");
CLog.d("Output from shell command 'vdc dump | grep cache': %s", output);
- String[] segments = output.split(" ");
+ String[] segments = output.split("\\s+");
if (segments.length >= 3) {
mCache = segments[2];
} else {
@@ -394,10 +394,10 @@
// Filesystem 1K-blocks Used Available Use% Mounted on
// /dev/block/mmcblk0p34 60400 56 60344 1% /cache
String output = mTestDevice.executeShellCommand("df cache");
- CLog.d(String.format("Output from shell command 'df cache': %s", output));
+ CLog.d(String.format("Output from shell command 'df cache':\n%s", output));
String[] lines = output.split("\r?\n");
if (lines.length >= 2) {
- String[] segments = lines[1].split(" ");
+ String[] segments = lines[1].split("\\s+");
if (segments.length >= 2) {
if (lines[0].toLowerCase().contains("1k-blocks")) {
mCachePartitionSize = Integer.parseInt(segments[1]) / 1024;
diff --git a/src/com/android/tradefed/build/LocalDeviceBuildProvider.java b/src/com/android/tradefed/build/LocalDeviceBuildProvider.java
index 1ac6fe4..0bbfcef 100644
--- a/src/com/android/tradefed/build/LocalDeviceBuildProvider.java
+++ b/src/com/android/tradefed/build/LocalDeviceBuildProvider.java
@@ -282,7 +282,8 @@
this.mBuildDir = buildDir;
}
- File getTestDir() {
+ /** Returns the directory where the tests are located. */
+ public File getTestDir() {
return mTestDir;
}
diff --git a/src/com/android/tradefed/command/CommandScheduler.java b/src/com/android/tradefed/command/CommandScheduler.java
index 3cc7e32..8cdb7e2 100644
--- a/src/com/android/tradefed/command/CommandScheduler.java
+++ b/src/com/android/tradefed/command/CommandScheduler.java
@@ -36,6 +36,7 @@
import com.android.tradefed.config.IDeviceConfiguration;
import com.android.tradefed.config.IGlobalConfiguration;
import com.android.tradefed.config.Option;
+import com.android.tradefed.config.SandboxConfigurationFactory;
import com.android.tradefed.device.DeviceAllocationState;
import com.android.tradefed.device.DeviceManager;
import com.android.tradefed.device.DeviceNotAvailableException;
@@ -60,7 +61,6 @@
import com.android.tradefed.result.ITestInvocationListener;
import com.android.tradefed.result.ResultForwarder;
import com.android.tradefed.sandbox.ISandbox;
-import com.android.tradefed.sandbox.SandboxConfigUtil;
import com.android.tradefed.sandbox.TradefedSandbox;
import com.android.tradefed.util.ArrayUtil;
import com.android.tradefed.util.FileUtil;
@@ -1122,8 +1122,8 @@
if (isCommandSandboxed(args)) {
// Create an sandboxed configuration based on the sandbox of the scheduler.
ISandbox sandbox = createSandbox();
- return SandboxConfigUtil.createSandboxConfiguration(
- args, sandbox, getConfigFactory(), getKeyStoreClient());
+ return SandboxConfigurationFactory.getInstance()
+ .createConfigurationFromArgs(args, getKeyStoreClient(), sandbox, new RunUtil());
}
return getConfigFactory().createConfigurationFromArgs(args, null, getKeyStoreClient());
}
diff --git a/src/com/android/tradefed/config/Configuration.java b/src/com/android/tradefed/config/Configuration.java
index 60aa5e7..9e6b05f 100644
--- a/src/com/android/tradefed/config/Configuration.java
+++ b/src/com/android/tradefed/config/Configuration.java
@@ -24,6 +24,7 @@
import com.android.tradefed.device.IDeviceRecovery;
import com.android.tradefed.device.IDeviceSelection;
import com.android.tradefed.device.TestDeviceOptions;
+import com.android.tradefed.device.metric.IMetricCollector;
import com.android.tradefed.log.ILeveledLogOutput;
import com.android.tradefed.log.StdoutLogger;
import com.android.tradefed.profiler.ITestProfiler;
@@ -87,6 +88,7 @@
public static final String CONFIGURATION_DESCRIPTION_TYPE_NAME = "config_desc";
public static final String DEVICE_NAME = "device";
public static final String TEST_PROFILER_TYPE_NAME = "test_profiler";
+ public static final String DEVICE_METRICS_COLLECTOR_TYPE_NAME = "metrics_collector";
public static final String SANDBOX_TYPE_NAME = "sandbox";
private static Map<String, ObjTypeInfo> sObjTypeMap = null;
@@ -161,6 +163,9 @@
CONFIGURATION_DESCRIPTION_TYPE_NAME,
new ObjTypeInfo(ConfigurationDescriptor.class, false));
sObjTypeMap.put(TEST_PROFILER_TYPE_NAME, new ObjTypeInfo(ITestProfiler.class, false));
+ sObjTypeMap.put(
+ DEVICE_METRICS_COLLECTOR_TYPE_NAME,
+ new ObjTypeInfo(IMetricCollector.class, true));
}
return sObjTypeMap;
}
@@ -211,6 +216,7 @@
setSystemStatusCheckers(new ArrayList<ISystemStatusChecker>());
setConfigurationDescriptor(new ConfigurationDescriptor());
setProfiler(new StubTestProfiler());
+ setDeviceMetricCollectors(new ArrayList<>());
}
/**
@@ -349,6 +355,13 @@
RESULT_REPORTER_TYPE_NAME);
}
+ @SuppressWarnings("unchecked")
+ @Override
+ public List<IMetricCollector> getMetricCollectors() {
+ return (List<IMetricCollector>)
+ getConfigurationObjectList(DEVICE_METRICS_COLLECTOR_TYPE_NAME);
+ }
+
/** {@inheritDoc} */
@SuppressWarnings("unchecked")
@Override
@@ -642,6 +655,11 @@
setConfigurationObjectListNoThrow(RESULT_REPORTER_TYPE_NAME, listeners);
}
+ @Override
+ public void setDeviceMetricCollectors(List<IMetricCollector> collectors) {
+ setConfigurationObjectListNoThrow(DEVICE_METRICS_COLLECTOR_TYPE_NAME, collectors);
+ }
+
/**
* {@inheritDoc}
*/
diff --git a/src/com/android/tradefed/config/ConfigurationFactory.java b/src/com/android/tradefed/config/ConfigurationFactory.java
index d200b1f..9ce9a0e 100644
--- a/src/com/android/tradefed/config/ConfigurationFactory.java
+++ b/src/com/android/tradefed/config/ConfigurationFactory.java
@@ -268,7 +268,7 @@
if (def == null || def.isStale()) {
def = new ConfigurationDef(configName);
- loadConfiguration(configName, def, templateMap);
+ loadConfiguration(configName, def, null, templateMap);
mConfigDefMap.put(configId, def);
} else {
if (templateMap != null) {
@@ -315,15 +315,19 @@
}
}
- @Override
/**
- * Configs that are bundled inside the tradefed.jar can only include
- * other configs also bundled inside tradefed.jar. However, local
- * (external) configs can include both local (external) and bundled
- * configs.
+ * Configs that are bundled inside the tradefed.jar can only include other configs also
+ * bundled inside tradefed.jar. However, local (external) configs can include both local
+ * (external) and bundled configs.
*/
- public void loadIncludedConfiguration(ConfigurationDef def, String parentName, String name,
- Map<String, String> templateMap) throws ConfigurationException {
+ @Override
+ public void loadIncludedConfiguration(
+ ConfigurationDef def,
+ String parentName,
+ String name,
+ String deviceTagObject,
+ Map<String, String> templateMap)
+ throws ConfigurationException {
String config_name = name;
if (!isBundledConfig(name)) {
@@ -361,25 +365,31 @@
"Circular configuration include: config '%s' is already included",
config_name));
}
- loadConfiguration(config_name, def, templateMap);
+ loadConfiguration(config_name, def, deviceTagObject, templateMap);
}
/**
* Loads a configuration.
*
- * @param name the name of a built-in configuration to load or a file
- * path to configuration xml to load
+ * @param name the name of a built-in configuration to load or a file path to configuration
+ * xml to load
* @param def the loaded {@link ConfigurationDef}
- * @param templateMap map from template-include names to their
- * respective concrete configuration files
- * @throws ConfigurationException if a configuration with given
- * name/file path cannot be loaded or parsed
+ * @param deviceTagObject name of the current deviceTag if we are loading from a config
+ * inside an <include>. Null otherwise.
+ * @param templateMap map from template-include names to their respective concrete
+ * configuration files
+ * @throws ConfigurationException if a configuration with given name/file path cannot be
+ * loaded or parsed
*/
- void loadConfiguration(String name, ConfigurationDef def, Map<String, String> templateMap)
+ void loadConfiguration(
+ String name,
+ ConfigurationDef def,
+ String deviceTagObject,
+ Map<String, String> templateMap)
throws ConfigurationException {
//Log.d(LOG_TAG, String.format("Loading configuration '%s'", name));
BufferedInputStream bufStream = getConfigStream(name);
- ConfigurationXmlParser parser = new ConfigurationXmlParser(this);
+ ConfigurationXmlParser parser = new ConfigurationXmlParser(this, deviceTagObject);
parser.parse(def, name, bufStream, templateMap);
// Track local config source files
@@ -414,13 +424,14 @@
/**
* Retrieve the {@link ConfigurationDef} for the given name
*
- * @param name the name of a built-in configuration to load or a file path
- * to configuration xml to load
+ * @param name the name of a built-in configuration to load or a file path to configuration xml
+ * to load
* @return {@link ConfigurationDef}
* @throws ConfigurationException if an error occurred loading the config
*/
- private ConfigurationDef getConfigurationDef(String name, boolean isGlobal,
- Map<String, String> templateMap) throws ConfigurationException {
+ ConfigurationDef getConfigurationDef(
+ String name, boolean isGlobal, Map<String, String> templateMap)
+ throws ConfigurationException {
return new ConfigLoader(isGlobal).getConfigurationDef(name, templateMap);
}
diff --git a/src/com/android/tradefed/config/ConfigurationXmlParser.java b/src/com/android/tradefed/config/ConfigurationXmlParser.java
index c36f530..32e06fd 100644
--- a/src/com/android/tradefed/config/ConfigurationXmlParser.java
+++ b/src/com/android/tradefed/config/ConfigurationXmlParser.java
@@ -62,6 +62,7 @@
private final ConfigurationDef mConfigDef;
private final Map<String, String> mTemplateMap;
private final String mName;
+ private final boolean mInsideParentDeviceTag;
// State-holding members
private String mCurrentConfigObject;
@@ -72,11 +73,17 @@
private Boolean isLocalConfig = null;
- ConfigHandler(ConfigurationDef def, String name, IConfigDefLoader loader,
+ ConfigHandler(
+ ConfigurationDef def,
+ String name,
+ IConfigDefLoader loader,
+ String parentDeviceObject,
Map<String, String> templateMap) {
mName = name;
mConfigDef = def;
mConfigDefLoader = loader;
+ mCurrentDeviceObject = parentDeviceObject;
+ mInsideParentDeviceTag = (parentDeviceObject != null) ? true : false;
if (templateMap == null) {
mTemplateMap = Collections.<String, String>emptyMap();
@@ -141,7 +148,7 @@
// if it turns out we are in multi mode, we will throw an exception.
mOutsideTag.add(localName);
}
- //if we are inside a device object, some tags are not allowed.
+ // if we are inside a device object, some tags are not allowed.
if (mCurrentDeviceObject != null) {
if (!Configuration.doesBuiltInObjSupportMultiDevice(localName)) {
// Prevent some tags to be inside of a device in multi device mode.
@@ -202,13 +209,9 @@
if (includeName == null) {
throwException("Missing 'name' attribute for include");
}
- if (mCurrentDeviceObject != null) {
- // TODO: Add this use case.
- throwException("<include> inside device object currently not supported.");
- }
try {
- mConfigDefLoader.loadIncludedConfiguration(mConfigDef, mName, includeName,
- mTemplateMap);
+ mConfigDefLoader.loadIncludedConfiguration(
+ mConfigDef, mName, includeName, mCurrentDeviceObject, mTemplateMap);
} catch (ConfigurationException e) {
if (e instanceof TemplateResolutionError) {
throwException(String.format(INNER_TEMPLATE_INCLUDE_ERROR,
@@ -236,8 +239,8 @@
// Removing the used template from the map to avoid re-using it.
mTemplateMap.remove(templateName);
try {
- mConfigDefLoader.loadIncludedConfiguration(mConfigDef, mName, includeName,
- mTemplateMap);
+ mConfigDefLoader.loadIncludedConfiguration(
+ mConfigDef, mName, includeName, null, mTemplateMap);
} catch (ConfigurationException e) {
if (e instanceof TemplateResolutionError) {
throwException(String.format(INNER_TEMPLATE_INCLUDE_ERROR,
@@ -257,7 +260,8 @@
|| GlobalConfiguration.isBuiltInObjType(localName)) {
mCurrentConfigObject = null;
}
- if (DEVICE_TAG.equals(localName)) {
+ if (DEVICE_TAG.equals(localName) && !mInsideParentDeviceTag) {
+ // Only unset if it was not the parent device tag.
mCurrentDeviceObject = null;
}
}
@@ -301,9 +305,15 @@
}
private final IConfigDefLoader mConfigDefLoader;
+ /**
+ * If we are loading a config from inside a <device> tag, this will contain the name of the
+ * current device tag to properly load in context.
+ */
+ private final String mParentDeviceObject;
- ConfigurationXmlParser(IConfigDefLoader loader) {
+ ConfigurationXmlParser(IConfigDefLoader loader, String parentDeviceObject) {
mConfigDefLoader = loader;
+ mParentDeviceObject = parentDeviceObject;
}
/**
@@ -323,8 +333,9 @@
SAXParserFactory parserFactory = SAXParserFactory.newInstance();
parserFactory.setNamespaceAware(true);
SAXParser parser = parserFactory.newSAXParser();
- ConfigHandler configHandler = new ConfigHandler(configDef, name, mConfigDefLoader,
- templateMap);
+ ConfigHandler configHandler =
+ new ConfigHandler(
+ configDef, name, mConfigDefLoader, mParentDeviceObject, templateMap);
parser.parse(new InputSource(xmlInput), configHandler);
checkValidMultiConfiguration(configHandler);
} catch (ParserConfigurationException e) {
diff --git a/src/com/android/tradefed/config/GlobalConfiguration.java b/src/com/android/tradefed/config/GlobalConfiguration.java
index 80d26cb..cef3da6 100644
--- a/src/com/android/tradefed/config/GlobalConfiguration.java
+++ b/src/com/android/tradefed/config/GlobalConfiguration.java
@@ -64,13 +64,13 @@
public static final String KEY_STORE_TYPE_NAME = "key_store";
public static final String SHARDING_STRATEGY_TYPE_NAME = "sharding_strategy";
+ public static final String GLOBAL_CONFIG_VARIABLE = "TF_GLOBAL_CONFIG";
+ private static final String GLOBAL_CONFIG_FILENAME = "tf_global_config.xml";
+
private static Map<String, ObjTypeInfo> sObjTypeMap = null;
private static IGlobalConfiguration sInstance = null;
private static final Object sInstanceLock = new Object();
- private static final String GLOBAL_CONFIG_VARIABLE = "TF_GLOBAL_CONFIG";
- private static final String GLOBAL_CONFIG_FILENAME = "tf_global_config.xml";
-
// Empty embedded configuration available by default
private static final String DEFAULT_EMPTY_CONFIG_NAME = "empty";
diff --git a/src/com/android/tradefed/config/IConfigDefLoader.java b/src/com/android/tradefed/config/IConfigDefLoader.java
index b235f76..d6dd9c9 100644
--- a/src/com/android/tradefed/config/IConfigDefLoader.java
+++ b/src/com/android/tradefed/config/IConfigDefLoader.java
@@ -42,8 +42,15 @@
* @param def the {@link ConfigurationDef} to load the data into
* @param parentName the name of the parent config
* @param name the name of config to include
+ * @param deviceTagObject the name of the current deviceTag or null if not inside a device tag.
+ * @param templateMap the current map of template to be loaded.
* @throws ConfigurationException if an error occurred loading the config
*/
- void loadIncludedConfiguration(ConfigurationDef def, String parentName, String name,
- Map<String, String> templateMap) throws ConfigurationException;
+ void loadIncludedConfiguration(
+ ConfigurationDef def,
+ String parentName,
+ String name,
+ String deviceTagObject,
+ Map<String, String> templateMap)
+ throws ConfigurationException;
}
diff --git a/src/com/android/tradefed/config/IConfiguration.java b/src/com/android/tradefed/config/IConfiguration.java
index 88e2b43..4270b4a 100644
--- a/src/com/android/tradefed/config/IConfiguration.java
+++ b/src/com/android/tradefed/config/IConfiguration.java
@@ -22,6 +22,7 @@
import com.android.tradefed.device.IDeviceRecovery;
import com.android.tradefed.device.IDeviceSelection;
import com.android.tradefed.device.TestDeviceOptions;
+import com.android.tradefed.device.metric.IMetricCollector;
import com.android.tradefed.log.ILeveledLogOutput;
import com.android.tradefed.profiler.ITestProfiler;
import com.android.tradefed.result.ILogSaver;
@@ -129,6 +130,9 @@
*/
public ITestProfiler getProfiler();
+ /** Gets the {@link IMetricCollector}s from the configuration. */
+ public List<IMetricCollector> getMetricCollectors();
+
/**
* Gets the {@link ICommandOptions} to use from the configuration.
*
@@ -346,6 +350,9 @@
*/
public void setTestInvocationListener(ITestInvocationListener listener);
+ /** Set the list of {@link IMetricCollector}s, replacing any existing values. */
+ public void setDeviceMetricCollectors(List<IMetricCollector> collectors);
+
/**
* Set the {@link ITestProfiler}, replacing any existing values
*
diff --git a/src/com/android/tradefed/config/SandboxConfigurationFactory.java b/src/com/android/tradefed/config/SandboxConfigurationFactory.java
new file mode 100644
index 0000000..490413d
--- /dev/null
+++ b/src/com/android/tradefed/config/SandboxConfigurationFactory.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.config;
+
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.sandbox.ISandbox;
+import com.android.tradefed.sandbox.SandboxConfigDump.DumpCmd;
+import com.android.tradefed.sandbox.SandboxConfigUtil;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.keystore.IKeyStoreClient;
+
+import java.io.File;
+import java.util.Map;
+
+/** Special Configuration factory to handle creation of configurations for Sandboxing purpose. */
+public class SandboxConfigurationFactory extends ConfigurationFactory {
+
+ private static SandboxConfigurationFactory sInstance = null;
+
+ /** Get the singleton {@link IConfigurationFactory} instance. */
+ public static SandboxConfigurationFactory getInstance() {
+ if (sInstance == null) {
+ sInstance = new SandboxConfigurationFactory();
+ }
+ return sInstance;
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ ConfigurationDef getConfigurationDef(
+ String name, boolean isGlobal, Map<String, String> templateMap)
+ throws ConfigurationException {
+ // TODO: Extend ConfigurationDef to possibly create a different IConfiguration type and
+ // handle more elegantly the parent/subprocess incompatibilities.
+ ConfigurationDef def = new ConfigurationDef(name);
+ new ConfigLoader(isGlobal).loadConfiguration(name, def, null, templateMap);
+ return def;
+ }
+
+ /**
+ * Create a {@link IConfiguration} based on the command line and sandbox provided.
+ *
+ * @param args the command line for the run.
+ * @param keyStoreClient the {@link IKeyStoreClient} where to load the key from.
+ * @param sandbox the {@link ISandbox} used for the run.
+ * @param runUtil the {@link IRunUtil} to run commands.
+ * @return a {@link IConfiguration} valid for the sandbox.
+ * @throws ConfigurationException
+ */
+ public IConfiguration createConfigurationFromArgs(
+ String[] args, IKeyStoreClient keyStoreClient, ISandbox sandbox, IRunUtil runUtil)
+ throws ConfigurationException {
+ IConfiguration config = null;
+ File xmlConfig = null;
+ try {
+ runUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_VARIABLE);
+ File tfDir = sandbox.getTradefedEnvironment(args);
+ // TODO: dump using the keystore too
+ xmlConfig =
+ SandboxConfigUtil.dumpConfigForVersion(
+ tfDir, runUtil, args, DumpCmd.NON_VERSIONED_CONFIG);
+ // Get the non version part of the configuration in order to do proper allocation
+ // of devices and such.
+ config =
+ super.createConfigurationFromArgs(
+ new String[] {xmlConfig.getAbsolutePath()}, null, keyStoreClient);
+ // Reset the command line to the original one.
+ config.setCommandLine(args);
+ config.setConfigurationObject(Configuration.SANDBOX_TYPE_NAME, sandbox);
+ } catch (ConfigurationException e) {
+ CLog.e(e);
+ sandbox.tearDown();
+ throw e;
+ } finally {
+ FileUtil.deleteFile(xmlConfig);
+ }
+ return config;
+ }
+}
diff --git a/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java b/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
new file mode 100644
index 0000000..9f6fbc0
--- /dev/null
+++ b/src/com/android/tradefed/device/metric/BaseDeviceMetricCollector.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.device.metric;
+
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.result.LogDataType;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base implementation of {@link IMetricCollector} that allows to start and stop collection on
+ * {@link #onTestRunStart(DeviceMetricData)} and {@link #onTestRunEnd(DeviceMetricData)}.
+ */
+public class BaseDeviceMetricCollector implements IMetricCollector {
+
+ private IInvocationContext mContext;
+ private ITestInvocationListener mForwarder;
+ private DeviceMetricData mRunData;
+
+ @Override
+ public ITestInvocationListener init(
+ IInvocationContext context, ITestInvocationListener listener) {
+ mContext = context;
+ mForwarder = listener;
+ return this;
+ }
+
+ @Override
+ public List<ITestDevice> getDevices() {
+ return mContext.getDevices();
+ }
+
+ @Override
+ public List<IBuildInfo> getBuildInfos() {
+ return mContext.getBuildInfos();
+ }
+
+ @Override
+ public ITestInvocationListener getInvocationListener() {
+ return mForwarder;
+ }
+
+ @Override
+ public void onTestRunStart(DeviceMetricData runData) {
+ // Does nothing
+ }
+
+ @Override
+ public void onTestRunEnd(DeviceMetricData runData) {
+ // Does nothing
+ }
+
+ /** =================================== */
+ /** Invocation Listeners for forwarding */
+ @Override
+ public final void invocationStarted(IInvocationContext context) {
+ mForwarder.invocationStarted(context);
+ }
+
+ @Override
+ public final void invocationFailed(Throwable cause) {
+ mForwarder.invocationFailed(cause);
+ }
+
+ @Override
+ public final void invocationEnded(long elapsedTime) {
+ mForwarder.invocationEnded(elapsedTime);
+ }
+
+ @Override
+ public final void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {
+ mForwarder.testLog(dataName, dataType, dataStream);
+ }
+
+ /** Test run callbacks */
+ @Override
+ public final void testRunStarted(String runName, int testCount) {
+ mRunData = new DeviceMetricData();
+ onTestRunStart(mRunData);
+ mForwarder.testRunStarted(runName, testCount);
+ }
+
+ @Override
+ public final void testRunFailed(String errorMessage) {
+ mForwarder.testRunFailed(errorMessage);
+ }
+
+ @Override
+ public final void testRunStopped(long elapsedTime) {
+ mForwarder.testRunStopped(elapsedTime);
+ }
+
+ @Override
+ public final void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
+ onTestRunEnd(mRunData);
+ mRunData.addToMetrics(runMetrics);
+ mForwarder.testRunEnded(elapsedTime, runMetrics);
+ }
+
+ /** Test cases callbacks */
+ @Override
+ public final void testStarted(TestIdentifier test) {
+ testStarted(test, System.currentTimeMillis());
+ }
+
+ @Override
+ public final void testStarted(TestIdentifier test, long startTime) {
+ mForwarder.testStarted(test, startTime);
+ }
+
+ @Override
+ public final void testFailed(TestIdentifier test, String trace) {
+ mForwarder.testFailed(test, trace);
+ }
+
+ @Override
+ public final void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
+ testEnded(test, System.currentTimeMillis(), testMetrics);
+ }
+
+ @Override
+ public final void testEnded(
+ TestIdentifier test, long endTime, Map<String, String> testMetrics) {
+ mForwarder.testEnded(test, endTime, testMetrics);
+ }
+
+ @Override
+ public final void testAssumptionFailure(TestIdentifier test, String trace) {
+ mForwarder.testAssumptionFailure(test, trace);
+ }
+
+ @Override
+ public final void testIgnored(TestIdentifier test) {
+ mForwarder.testIgnored(test);
+ }
+}
diff --git a/src/com/android/tradefed/device/metric/DeviceMetricData.java b/src/com/android/tradefed/device/metric/DeviceMetricData.java
new file mode 100644
index 0000000..d31324d
--- /dev/null
+++ b/src/com/android/tradefed/device/metric/DeviceMetricData.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.device.metric;
+
+import java.io.Serializable;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Object to hold all the data collected by metric collectors. TODO: Add the data holding and
+ * receiving of data methods.
+ */
+public class DeviceMetricData implements Serializable {
+ private static final long serialVersionUID = 1;
+
+ // TODO: expend type supports to more complex type: Object, File, etc.
+ private LinkedHashMap<String, String> mCurrentStringMetrics = new LinkedHashMap<>();
+
+ public void addStringMetric(String key, String value) {
+ mCurrentStringMetrics.put(key, value);
+ }
+
+ /**
+ * Push all the data received so far to the map of metrics that will be reported. This should
+ * also clean up the resources after pushing them.
+ *
+ * @param metrics The metrics currently available.
+ */
+ public void addToMetrics(Map<String, String> metrics) {
+ // TODO: dump all the metrics collected to the map of metrics to be reported.
+ metrics.putAll(mCurrentStringMetrics);
+ }
+}
diff --git a/src/com/android/tradefed/device/metric/IMetricCollector.java b/src/com/android/tradefed/device/metric/IMetricCollector.java
new file mode 100644
index 0000000..b6d0ef7
--- /dev/null
+++ b/src/com/android/tradefed/device/metric/IMetricCollector.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.device.metric;
+
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.result.ITestInvocationListener;
+
+import java.util.List;
+
+/**
+ * This interface will be added as a decorator when reporting tests results in order to collect
+ * matching metrics.
+ */
+public interface IMetricCollector extends ITestInvocationListener {
+
+ /**
+ * Initialization of the collector with the current context and where to forward results.
+ *
+ * @param context the {@link IInvocationContext} for the invocation in progress.
+ * @param listener the {@link ITestInvocationListener} where to put results.
+ * @return the new listener wrapping the original one.
+ */
+ public ITestInvocationListener init(
+ IInvocationContext context, ITestInvocationListener listener);
+
+ /** Returns the list of devices available in the invocation. */
+ public List<ITestDevice> getDevices();
+
+ /** Returns the list of build information available in the invocation. */
+ public List<IBuildInfo> getBuildInfos();
+
+ /** Returns the original {@link ITestInvocationListener} where we are forwarding the results. */
+ public ITestInvocationListener getInvocationListener();
+
+ /**
+ * Callback when a test run is started.
+ *
+ * @param runData the {@link DeviceMetricData} holding the data for the run.
+ */
+ public void onTestRunStart(DeviceMetricData runData);
+
+ /**
+ * Callback when a test run is ended. This should be the time for clean up.
+ *
+ * @param runData the {@link DeviceMetricData} holding the data for the run. Will be the same
+ * object as during {@link #onTestRunStart(DeviceMetricData)}.
+ */
+ public void onTestRunEnd(DeviceMetricData runData);
+}
diff --git a/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java b/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java
new file mode 100644
index 0000000..7c1f9a7
--- /dev/null
+++ b/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollector.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.device.metric;
+
+import com.android.tradefed.config.Option;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import java.util.Timer;
+import java.util.TimerTask;
+
+/**
+ * A {@link IMetricCollector} that allows to run a collection task periodically at a set interval.
+ */
+public abstract class ScheduledDeviceMetricCollector extends BaseDeviceMetricCollector {
+
+ @Option(
+ name = "fixed-schedule-rate",
+ description = "Schedule the timetask as a fixed schedule rate"
+ )
+ private boolean mFixedScheduleRate = false;
+
+ @Option(
+ name = "interval",
+ description = "the interval between two tasks being scheduled",
+ isTimeVal = true
+ )
+ private long mIntervalMs = 60 * 1000l;
+
+ private Timer timer;
+
+ @Override
+ public final void onTestRunStart(DeviceMetricData runData) {
+ CLog.d("starting");
+ onStart(runData);
+ timer = new Timer();
+ TimerTask timerTask =
+ new TimerTask() {
+ @Override
+ public void run() {
+ try {
+ collect(runData);
+ } catch (InterruptedException e) {
+ timer.cancel();
+ Thread.currentThread().interrupt();
+ CLog.e("Interrupted exception thrown from task:");
+ CLog.e(e);
+ }
+ }
+ };
+
+ if (mFixedScheduleRate) {
+ timer.scheduleAtFixedRate(timerTask, 0, mIntervalMs);
+ } else {
+ timer.schedule(timerTask, 0, mIntervalMs);
+ }
+ }
+
+ @Override
+ public final void onTestRunEnd(DeviceMetricData runData) {
+ if (timer != null) {
+ timer.cancel();
+ timer.purge();
+ }
+ onEnd(runData);
+ CLog.d("finished");
+ }
+
+ /**
+ * Task periodically & asynchronously run during the test running.
+ *
+ * @param runData the {@link DeviceMetricData} where to put metrics.
+ * @throws InterruptedException
+ */
+ abstract void collect(DeviceMetricData runData) throws InterruptedException;
+
+ /**
+ * Executed when entering this collector.
+ *
+ * @param runData the {@link DeviceMetricData} where to put metrics.
+ */
+ void onStart(DeviceMetricData runData) {
+ // Does nothing.
+ }
+
+ /**
+ * Executed when finishing this collector.
+ *
+ * @param runData the {@link DeviceMetricData} where to put metrics.
+ */
+ void onEnd(DeviceMetricData runData) {
+ // Does nothing.
+ }
+}
diff --git a/src/com/android/tradefed/invoker/ShardListener.java b/src/com/android/tradefed/invoker/ShardListener.java
index ea0f1fb..8aa5625 100644
--- a/src/com/android/tradefed/invoker/ShardListener.java
+++ b/src/com/android/tradefed/invoker/ShardListener.java
@@ -113,7 +113,19 @@
super.invocationEnded(elapsedTime);
synchronized (mMasterListener) {
logShardContent(getRunResults());
+ IInvocationContext moduleContext = null;
for (TestRunResult runResult : getRunResults()) {
+ // Stop or start the module
+ if (moduleContext != null
+ && !getModuleContextForRunResult(runResult).equals(moduleContext)) {
+ mMasterListener.testModuleEnded();
+ moduleContext = null;
+ }
+ if (moduleContext == null && getModuleContextForRunResult(runResult) != null) {
+ moduleContext = getModuleContextForRunResult(runResult);
+ mMasterListener.testModuleStarted(moduleContext);
+ }
+
mMasterListener.testRunStarted(runResult.getName(), runResult.getNumTests());
forwardTestResults(runResult.getTestResults());
if (runResult.isRunFailure()) {
@@ -121,6 +133,11 @@
}
mMasterListener.testRunEnded(runResult.getElapsedTime(), runResult.getRunMetrics());
}
+ // Close the last module
+ if (moduleContext != null) {
+ mMasterListener.testModuleEnded();
+ moduleContext = null;
+ }
mMasterListener.invocationEnded(elapsedTime);
}
}
diff --git a/src/com/android/tradefed/invoker/TestInvocation.java b/src/com/android/tradefed/invoker/TestInvocation.java
index dd9a4fb..13cdae0 100644
--- a/src/com/android/tradefed/invoker/TestInvocation.java
+++ b/src/com/android/tradefed/invoker/TestInvocation.java
@@ -32,6 +32,7 @@
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.device.StubDevice;
import com.android.tradefed.device.TestDeviceState;
+import com.android.tradefed.device.metric.IMetricCollector;
import com.android.tradefed.invoker.shard.IShardHelper;
import com.android.tradefed.invoker.shard.ShardBuildCloner;
import com.android.tradefed.log.ILeveledLogOutput;
@@ -762,8 +763,15 @@
* @param listener the {@link ITestInvocationListener} of test results
* @throws DeviceNotAvailableException
*/
- private void runTests(IInvocationContext context, IConfiguration config,
- ITestInvocationListener listener) throws DeviceNotAvailableException {
+ @VisibleForTesting
+ void runTests(
+ IInvocationContext context, IConfiguration config, ITestInvocationListener listener)
+ throws DeviceNotAvailableException {
+ // Wrap collectors in each other and collection will be sequential
+ for (IMetricCollector collector : config.getMetricCollectors()) {
+ listener = collector.init(context, listener);
+ }
+
for (IRemoteTest test : config.getTests()) {
// For compatibility of those receivers, they are assumed to be single device alloc.
if (test instanceof IDeviceTest) {
diff --git a/src/com/android/tradefed/result/CollectingTestListener.java b/src/com/android/tradefed/result/CollectingTestListener.java
index 80d5987..edecf72 100644
--- a/src/com/android/tradefed/result/CollectingTestListener.java
+++ b/src/com/android/tradefed/result/CollectingTestListener.java
@@ -42,7 +42,10 @@
// Uses a LinkedHashmap to have predictable iteration order
private Map<String, TestRunResult> mRunResultsMap =
Collections.synchronizedMap(new LinkedHashMap<String, TestRunResult>());
+ private Map<TestRunResult, IInvocationContext> mModuleContextMap =
+ Collections.synchronizedMap(new LinkedHashMap<TestRunResult, IInvocationContext>());
private TestRunResult mCurrentResults = new TestRunResult();
+ private IInvocationContext mCurrentModuleContext = null;
/** represents sums of tests in each TestStatus state for all runs.
* Indexed by TestStatus.ordinal() */
@@ -116,6 +119,16 @@
mBuildInfo = buildInfo;
}
+ @Override
+ public void testModuleStarted(IInvocationContext moduleContext) {
+ mCurrentModuleContext = moduleContext;
+ }
+
+ @Override
+ public void testModuleEnded() {
+ mCurrentModuleContext = null;
+ }
+
/**
* {@inheritDoc}
*/
@@ -130,6 +143,10 @@
mCurrentResults.setAggregateMetrics(mIsAggregateMetrics);
mRunResultsMap.put(name, mCurrentResults);
+ // track the module context associated with the results.
+ if (mCurrentModuleContext != null) {
+ mModuleContextMap.put(mCurrentResults, mCurrentModuleContext);
+ }
}
mCurrentResults.testRunStarted(name, numTests);
mIsCountDirty = true;
@@ -232,6 +249,14 @@
return mRunResultsMap.values();
}
+ /**
+ * Returns the {@link IInvocationContext} of the module associated with the results or null if
+ * it was not associated with any module.
+ */
+ public IInvocationContext getModuleContextForRunResult(TestRunResult res) {
+ return mModuleContextMap.get(res);
+ }
+
/** Returns True if the result map already has an entry for the run name. */
public boolean hasResultFor(String runName) {
return mRunResultsMap.containsKey(runName);
diff --git a/src/com/android/tradefed/result/ITestInvocationListener.java b/src/com/android/tradefed/result/ITestInvocationListener.java
index c8fbd7c..5e951d8 100644
--- a/src/com/android/tradefed/result/ITestInvocationListener.java
+++ b/src/com/android/tradefed/result/ITestInvocationListener.java
@@ -20,6 +20,7 @@
import com.android.tradefed.command.ICommandScheduler;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.log.ITestLogger;
+import com.android.tradefed.testtype.suite.ITestSuite;
import java.util.Map;
@@ -95,6 +96,18 @@
}
/**
+ * Reports the beginning of a module running. This callback is associated with {@link
+ * #testModuleEnded()} and is optional in the sequence. It is only used during a run that uses
+ * modules: {@link ITestSuite} based runners.
+ *
+ * @param moduleContext the {@link IInvocationContext} of the module.
+ */
+ public default void testModuleStarted(IInvocationContext moduleContext) {}
+
+ /** Reports the end of a module run. */
+ public default void testModuleEnded() {}
+
+ /**
* {@inheritDoc}
*/
@Override
diff --git a/src/com/android/tradefed/result/ResultForwarder.java b/src/com/android/tradefed/result/ResultForwarder.java
index 494fc7a..41a3e88 100644
--- a/src/com/android/tradefed/result/ResultForwarder.java
+++ b/src/com/android/tradefed/result/ResultForwarder.java
@@ -297,4 +297,18 @@
}
}
}
+
+ @Override
+ public void testModuleStarted(IInvocationContext moduleContext) {
+ for (ITestInvocationListener listener : mListeners) {
+ listener.testModuleStarted(moduleContext);
+ }
+ }
+
+ @Override
+ public void testModuleEnded() {
+ for (ITestInvocationListener listener : mListeners) {
+ listener.testModuleEnded();
+ }
+ }
}
diff --git a/src/com/android/tradefed/sandbox/SandboxConfigUtil.java b/src/com/android/tradefed/sandbox/SandboxConfigUtil.java
index e0c0159..5aaa4e7 100644
--- a/src/com/android/tradefed/sandbox/SandboxConfigUtil.java
+++ b/src/com/android/tradefed/sandbox/SandboxConfigUtil.java
@@ -15,18 +15,13 @@
*/
package com.android.tradefed.sandbox;
-import com.android.tradefed.config.Configuration;
import com.android.tradefed.config.ConfigurationException;
import com.android.tradefed.config.IConfiguration;
-import com.android.tradefed.config.IConfigurationFactory;
-import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.sandbox.SandboxConfigDump.DumpCmd;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
import com.android.tradefed.util.FileUtil;
import com.android.tradefed.util.IRunUtil;
-import com.android.tradefed.util.RunUtil;
-import com.android.tradefed.util.keystore.IKeyStoreClient;
import java.io.File;
import java.io.IOException;
@@ -75,44 +70,4 @@
FileUtil.deleteFile(destination);
throw new ConfigurationException(result.getStderr());
}
-
- /**
- * Create a {@link IConfiguration} based on the command line and sandbox provided.
- *
- * @param args the command line for the run.
- * @param sandbox the {@link ISandbox} used for the run.
- * @param configFactory the {@link IConfigurationFactory} used to load the config on parent side
- * @param keystore the {@link IKeyStoreClient} where to load the key from.
- * @return a {@link IConfiguration} valid for the sandbox.
- * @throws ConfigurationException
- */
- public static IConfiguration createSandboxConfiguration(
- String args[],
- ISandbox sandbox,
- IConfigurationFactory configFactory,
- IKeyStoreClient keystore)
- throws ConfigurationException {
- IConfiguration config = null;
- File xmlConfig = null;
- try {
- File tfDir = sandbox.getTradefedEnvironment(args);
- xmlConfig =
- dumpConfigForVersion(tfDir, new RunUtil(), args, DumpCmd.NON_VERSIONED_CONFIG);
- // Get the non version part of the configuration in order to do proper allocation
- // of devices and such.
- config =
- configFactory.createConfigurationFromArgs(
- new String[] {xmlConfig.getAbsolutePath()}, null, keystore);
- // Reset the command line to the original one.
- config.setCommandLine(args);
- config.setConfigurationObject(Configuration.SANDBOX_TYPE_NAME, sandbox);
- } catch (ConfigurationException e) {
- CLog.e(e);
- sandbox.tearDown();
- throw e;
- } finally {
- FileUtil.deleteFile(xmlConfig);
- }
- return config;
- }
}
diff --git a/src/com/android/tradefed/testtype/StubTest.java b/src/com/android/tradefed/testtype/StubTest.java
index 0809e41..d0d7ff8 100644
--- a/src/com/android/tradefed/testtype/StubTest.java
+++ b/src/com/android/tradefed/testtype/StubTest.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.LinkedHashMap;
import java.util.List;
/**
@@ -91,7 +92,7 @@
TestIdentifier testId = new TestIdentifier("StubTest", "StubMethod");
listener.testStarted(testId);
listener.testEnded(testId, Collections.emptyMap());
- listener.testRunEnded(500, Collections.emptyMap());
+ listener.testRunEnded(500, new LinkedHashMap<>());
}
}
diff --git a/src/com/android/tradefed/testtype/suite/ITestSuite.java b/src/com/android/tradefed/testtype/suite/ITestSuite.java
index cdef583..90ed34c 100644
--- a/src/com/android/tradefed/testtype/suite/ITestSuite.java
+++ b/src/com/android/tradefed/testtype/suite/ITestSuite.java
@@ -233,7 +233,7 @@
}
try {
- mContext.setModuleInvocationContext(module.getModuleInvocationContext());
+ listener.testModuleStarted(module.getModuleInvocationContext());
// Populate the module context with devices and builds
for (String deviceName : mContext.getDeviceConfigNames()) {
module.getModuleInvocationContext()
@@ -245,7 +245,7 @@
} finally {
// clear out module invocation context since we are now done with module
// execution
- mContext.setModuleInvocationContext(null);
+ listener.testModuleEnded();
}
}
} catch (DeviceNotAvailableException e) {
diff --git a/tests/res/testconfigs/multi-device-incorrect-include.xml b/tests/res/testconfigs/multi-device-incorrect-include.xml
new file mode 100644
index 0000000..ac02418
--- /dev/null
+++ b/tests/res/testconfigs/multi-device-incorrect-include.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Multi device parsing, where an incldue also containing device is inside a device">
+ <device name="device1">
+ <!-- This is incorrect because it includes a device inside a device -->
+ <include name="multi-device-empty" />
+ </device>
+</configuration>
diff --git a/tests/res/testconfigs/test-config-multi-include.xml b/tests/res/testconfigs/test-config-multi-include.xml
new file mode 100644
index 0000000..84f6c09
--- /dev/null
+++ b/tests/res/testconfigs/test-config-multi-include.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration
+ description="test for multiple device receiving options, with an include">
+
+ <device name="device1" >
+ <build_provider class="com.android.tradefed.build.StubBuildProvider" />
+ <device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
+ <target_preparer class="com.android.tradefed.targetprep.StubTargetPreparer" />
+ </device>
+ <device name="device2" >
+ <include name="mandatory-config" />
+ <target_preparer class="com.android.tradefed.targetprep.StubTargetPreparer" />
+ </device>
+</configuration>
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 1b34b75..2d35fb7 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -45,6 +45,7 @@
import com.android.tradefed.config.OptionCopierTest;
import com.android.tradefed.config.OptionSetterTest;
import com.android.tradefed.config.OptionUpdateRuleTest;
+import com.android.tradefed.config.SandboxConfigurationFactoryTest;
import com.android.tradefed.device.BackgroundDeviceActionTest;
import com.android.tradefed.device.CpuStatsCollectorTest;
import com.android.tradefed.device.DeviceManagerTest;
@@ -62,7 +63,10 @@
import com.android.tradefed.device.TopHelperTest;
import com.android.tradefed.device.WaitDeviceRecoveryTest;
import com.android.tradefed.device.WifiHelperTest;
+import com.android.tradefed.device.metric.ScheduledDeviceMetricCollectorTest;
+import com.android.tradefed.device.metric.BaseDeviceMetricCollectorTest;
import com.android.tradefed.invoker.InvocationContextTest;
+import com.android.tradefed.invoker.ShardListenerTest;
import com.android.tradefed.invoker.TestInvocationMultiTest;
import com.android.tradefed.invoker.TestInvocationTest;
import com.android.tradefed.invoker.shard.ShardHelperTest;
@@ -274,6 +278,7 @@
OptionCopierTest.class,
OptionSetterTest.class,
OptionUpdateRuleTest.class,
+ SandboxConfigurationFactoryTest.class,
// device
BackgroundDeviceActionTest.class,
@@ -295,8 +300,13 @@
WaitDeviceRecoveryTest.class,
WifiHelperTest.class,
+ // device.metric
+ ScheduledDeviceMetricCollectorTest.class,
+ BaseDeviceMetricCollectorTest.class,
+
// invoker
InvocationContextTest.class,
+ ShardListenerTest.class,
TestInvocationMultiTest.class,
TestInvocationTest.class,
diff --git a/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java b/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java
index 7f0d84e..15b643f 100644
--- a/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java
+++ b/tests/src/com/android/tradefed/config/ConfigurationFactoryTest.java
@@ -16,6 +16,7 @@
package com.android.tradefed.config;
import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.build.LocalDeviceBuildProvider;
import com.android.tradefed.config.ConfigurationFactory.ConfigId;
import com.android.tradefed.log.ILeveledLogOutput;
import com.android.tradefed.log.LogUtil.CLog;
@@ -1239,6 +1240,39 @@
assertTrue(deviceSetup2.getTestBooleanOptionFalse());
}
+ /** Test that when an <include> tag is used inside a <device> tag we correctly resolve it. */
+ public void testCreateConfiguration_includeInDevice() throws Exception {
+ IConfiguration config =
+ mFactory.createConfigurationFromArgs(
+ new String[] {"test-config-multi-include", "--test-dir", "faketestdir"});
+ assertEquals(2, config.getDeviceConfig().size());
+ IDeviceConfiguration device1 = config.getDeviceConfigByName("device1");
+ assertTrue(device1.getTargetPreparers().get(0) instanceof StubTargetPreparer);
+ // The included config in device2 loads a different build_provider
+ IDeviceConfiguration device2 = config.getDeviceConfigByName("device2");
+ assertTrue(device2.getBuildProvider() instanceof LocalDeviceBuildProvider);
+ LocalDeviceBuildProvider provider = (LocalDeviceBuildProvider) device2.getBuildProvider();
+ // command line options are properly propagated to the included object in device tag.
+ assertEquals("faketestdir", provider.getTestDir().getName());
+ }
+
+ /**
+ * Test when an <include> tag tries to load a <device> tag inside another <device> tag. This
+ * should throw an exception.
+ */
+ public void testCreateConfiguration_includeInDevice_inDevice() throws Exception {
+ try {
+ mFactory.createConfigurationFromArgs(
+ new String[] {
+ "multi-device-incorrect-include",
+ });
+ fail("Should have thrown an exception.");
+ } catch (ConfigurationException expected) {
+ assertEquals(
+ "<device> tag cannot be included inside another device", expected.getMessage());
+ }
+ }
+
/** Test that {@link ConfigurationFactory#reorderArgs(String[])} is properly reordering args. */
public void testReorderArgs_check_ordering() throws Throwable {
String[] args =
diff --git a/tests/src/com/android/tradefed/config/ConfigurationXmlParserTest.java b/tests/src/com/android/tradefed/config/ConfigurationXmlParserTest.java
index 5f24da7..b1043c7 100644
--- a/tests/src/com/android/tradefed/config/ConfigurationXmlParserTest.java
+++ b/tests/src/com/android/tradefed/config/ConfigurationXmlParserTest.java
@@ -36,7 +36,7 @@
protected void setUp() throws Exception {
super.setUp();
mMockLoader = EasyMock.createMock(IConfigDefLoader.class);
- xmlParser = new ConfigurationXmlParser(mMockLoader);
+ xmlParser = new ConfigurationXmlParser(mMockLoader, null);
}
/**
@@ -162,12 +162,15 @@
/**
* Test parsing a include tag.
*/
- @SuppressWarnings("unchecked")
public void testParse_include() throws ConfigurationException {
String includedName = "includeme";
ConfigurationDef configDef = new ConfigurationDef("foo");
- mMockLoader.loadIncludedConfiguration(EasyMock.eq(configDef), EasyMock.eq("foo"),
- EasyMock.eq(includedName), (Map<String, String>) EasyMock.anyObject());
+ mMockLoader.loadIncludedConfiguration(
+ EasyMock.eq(configDef),
+ EasyMock.eq("foo"),
+ EasyMock.eq(includedName),
+ EasyMock.anyObject(),
+ EasyMock.anyObject());
EasyMock.replay(mMockLoader);
final String config = "<include name=\"includeme\" />";
xmlParser.parse(configDef, "foo", getStringAsStream(config), null);
@@ -180,8 +183,8 @@
String includedName = "non-existent";
ConfigurationDef parent = new ConfigurationDef("name");
ConfigurationException exception = new ConfigurationException("I don't exist");
- mMockLoader.loadIncludedConfiguration(parent, "name", includedName,
- Collections.<String, String>emptyMap());
+ mMockLoader.loadIncludedConfiguration(
+ parent, "name", includedName, null, Collections.<String, String>emptyMap());
EasyMock.expectLastCall().andThrow(exception);
EasyMock.replay(mMockLoader);
final String config = String.format("<include name=\"%s\" />", includedName);
diff --git a/tests/src/com/android/tradefed/config/SandboxConfigurationFactoryTest.java b/tests/src/com/android/tradefed/config/SandboxConfigurationFactoryTest.java
new file mode 100644
index 0000000..c61065c
--- /dev/null
+++ b/tests/src/com/android/tradefed/config/SandboxConfigurationFactoryTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.config;
+
+import static org.easymock.EasyMock.eq;
+import static org.junit.Assert.*;
+
+import com.android.tradefed.sandbox.ISandbox;
+import com.android.tradefed.sandbox.SandboxConfigDump;
+import com.android.tradefed.sandbox.SandboxConfigDump.DumpCmd;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.IRunUtil;
+import com.android.tradefed.util.keystore.StubKeyStoreClient;
+
+import org.easymock.EasyMock;
+import org.easymock.IAnswer;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.File;
+import java.io.IOException;
+
+/** Unit tests for {@link SandboxConfigurationFactory}. */
+@RunWith(JUnit4.class)
+public class SandboxConfigurationFactoryTest {
+
+ private SandboxConfigurationFactory mFactory;
+ private File mConfig;
+ private File mTmpEnvDir;
+ private ISandbox mFakeSandbox;
+ private IRunUtil mMockRunUtil;
+
+ @Before
+ public void setUp() throws IOException {
+ mFactory = SandboxConfigurationFactory.getInstance();
+ mConfig = FileUtil.createTempFile("sandbox-config-test", ".xml");
+ mTmpEnvDir = FileUtil.createTempDir("sandbox-tmp-dir");
+ mFakeSandbox = EasyMock.createMock(ISandbox.class);
+ mMockRunUtil = EasyMock.createMock(IRunUtil.class);
+ }
+
+ @After
+ public void tearDown() {
+ FileUtil.recursiveDelete(mTmpEnvDir);
+ FileUtil.deleteFile(mConfig);
+ }
+
+ private void expectDumpCmd(CommandResult res) {
+ EasyMock.expect(
+ mMockRunUtil.runTimedCmd(
+ EasyMock.anyLong(),
+ eq("java"),
+ eq("-cp"),
+ eq(new File(mTmpEnvDir, "*").getAbsolutePath()),
+ eq(SandboxConfigDump.class.getCanonicalName()),
+ eq(DumpCmd.NON_VERSIONED_CONFIG.toString()),
+ EasyMock.anyObject(),
+ eq(mConfig.getAbsolutePath())))
+ .andAnswer(
+ new IAnswer<CommandResult>() {
+ @Override
+ public CommandResult answer() throws Throwable {
+ String resFile = (String) EasyMock.getCurrentArguments()[6];
+ FileUtil.writeToFile(
+ "<configuration><test class=\"com.android.tradefed.test"
+ + "type.StubTest\" /></configuration>",
+ new File(resFile));
+ return res;
+ }
+ });
+ }
+
+ /**
+ * Test that creating a configuration using a sandbox properly create a {@link IConfiguration}.
+ */
+ @Test
+ public void testCreateConfigurationFromArgs() throws ConfigurationException {
+ String[] args = new String[] {mConfig.getAbsolutePath()};
+ EasyMock.expect(mFakeSandbox.getTradefedEnvironment(EasyMock.anyObject()))
+ .andReturn(mTmpEnvDir);
+ mMockRunUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_VARIABLE);
+ CommandResult results = new CommandResult();
+ results.setStatus(CommandStatus.SUCCESS);
+ expectDumpCmd(results);
+ EasyMock.replay(mFakeSandbox, mMockRunUtil);
+ IConfiguration config =
+ mFactory.createConfigurationFromArgs(
+ args, new StubKeyStoreClient(), mFakeSandbox, mMockRunUtil);
+ EasyMock.verify(mFakeSandbox, mMockRunUtil);
+ assertNotNull(config.getConfigurationObject(Configuration.SANDBOX_TYPE_NAME));
+ assertEquals(mFakeSandbox, config.getConfigurationObject(Configuration.SANDBOX_TYPE_NAME));
+ }
+
+ /** Test that when the dump config failed, we throw a ConfigurationException. */
+ @Test
+ public void testCreateConfigurationFromArgs_fail() throws ConfigurationException {
+ String[] args = new String[] {mConfig.getAbsolutePath()};
+ EasyMock.expect(mFakeSandbox.getTradefedEnvironment(EasyMock.anyObject()))
+ .andReturn(mTmpEnvDir);
+ mMockRunUtil.unsetEnvVariable(GlobalConfiguration.GLOBAL_CONFIG_VARIABLE);
+ CommandResult results = new CommandResult();
+ results.setStatus(CommandStatus.FAILED);
+ results.setStderr("I failed");
+ expectDumpCmd(results);
+ // in case of failure, tearDown is called right away for cleaning up
+ mFakeSandbox.tearDown();
+ EasyMock.replay(mFakeSandbox, mMockRunUtil);
+ try {
+ mFactory.createConfigurationFromArgs(
+ args, new StubKeyStoreClient(), mFakeSandbox, mMockRunUtil);
+ fail("Should have thrown an exception.");
+ } catch (ConfigurationException expected) {
+ // expected
+ }
+ EasyMock.verify(mFakeSandbox, mMockRunUtil);
+ }
+}
diff --git a/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java
new file mode 100644
index 0000000..a1e7678
--- /dev/null
+++ b/tests/src/com/android/tradefed/device/metric/BaseDeviceMetricCollectorTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.device.metric;
+
+import static org.mockito.Mockito.times;
+
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.result.ByteArrayInputStreamSource;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.result.LogDataType;
+
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import java.util.Collections;
+
+/** Unit tests for {@link BaseDeviceMetricCollector}. */
+@RunWith(JUnit4.class)
+public class BaseDeviceMetricCollectorTest {
+
+ private BaseDeviceMetricCollector mBase;
+ private IInvocationContext mContext;
+ private ITestInvocationListener mMockListener;
+
+ @Before
+ public void setUp() {
+ mBase = new BaseDeviceMetricCollector();
+ mContext = new InvocationContext();
+ mMockListener = Mockito.mock(ITestInvocationListener.class);
+ }
+
+ @Test
+ public void testInitAndForwarding() {
+ mBase.init(mContext, mMockListener);
+ mBase.invocationStarted(mContext);
+ mBase.testRunStarted("testRun", 1);
+ TestIdentifier test = new TestIdentifier("class", "method");
+ mBase.testStarted(test);
+ mBase.testLog("dataname", LogDataType.TEXT, new ByteArrayInputStreamSource("".getBytes()));
+ mBase.testFailed(test, "trace");
+ mBase.testAssumptionFailure(test, "trace");
+ mBase.testIgnored(test);
+ mBase.testEnded(test, Collections.emptyMap());
+ mBase.testRunFailed("test run failed");
+ mBase.testRunStopped(0l);
+ mBase.testRunEnded(0l, Collections.emptyMap());
+ mBase.invocationFailed(new Throwable());
+ mBase.invocationEnded(0l);
+
+ Mockito.verify(mMockListener, times(1)).invocationStarted(Mockito.any());
+ Mockito.verify(mMockListener, times(1)).testRunStarted("testRun", 1);
+ Mockito.verify(mMockListener, times(1)).testStarted(Mockito.eq(test), Mockito.anyLong());
+ Mockito.verify(mMockListener, times(1))
+ .testLog(Mockito.eq("dataname"), Mockito.eq(LogDataType.TEXT), Mockito.any());
+ Mockito.verify(mMockListener, times(1)).testFailed(test, "trace");
+ Mockito.verify(mMockListener, times(1)).testAssumptionFailure(test, "trace");
+ Mockito.verify(mMockListener, times(1)).testIgnored(test);
+ Mockito.verify(mMockListener, times(1))
+ .testEnded(Mockito.eq(test), Mockito.anyLong(), Mockito.eq(Collections.emptyMap()));
+ Mockito.verify(mMockListener, times(1)).testRunFailed("test run failed");
+ Mockito.verify(mMockListener, times(1)).testRunStopped(0l);
+ Mockito.verify(mMockListener, times(1)).testRunEnded(0l, Collections.emptyMap());
+ Mockito.verify(mMockListener, times(1)).invocationFailed(Mockito.any());
+ Mockito.verify(mMockListener, times(1)).invocationEnded(0l);
+
+ Assert.assertSame(mMockListener, mBase.getInvocationListener());
+ Assert.assertEquals(0, mBase.getDevices().size());
+ Assert.assertEquals(0, mBase.getBuildInfos().size());
+ }
+}
diff --git a/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java b/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java
new file mode 100644
index 0000000..32a5ad6
--- /dev/null
+++ b/tests/src/com/android/tradefed/device/metric/ScheduledDeviceMetricCollectorTest.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.device.metric;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.config.OptionSetter;
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.util.RunUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mockito;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/** Unit tests for {@link ScheduledDeviceMetricCollector}. */
+@RunWith(JUnit4.class)
+public class ScheduledDeviceMetricCollectorTest {
+
+ public static class TestableAsyncTimer extends ScheduledDeviceMetricCollector {
+ private int mInternalCounter = 0;
+
+ @Override
+ void collect(DeviceMetricData runData) throws InterruptedException {
+ mInternalCounter++;
+ runData.addStringMetric("key" + mInternalCounter, "value" + mInternalCounter);
+ }
+ }
+
+ private TestableAsyncTimer mBase;
+ private IInvocationContext mContext;
+ private ITestInvocationListener mMockListener;
+
+ @Before
+ public void setUp() {
+ mBase = new TestableAsyncTimer();
+ mContext = new InvocationContext();
+ mMockListener = Mockito.mock(ITestInvocationListener.class);
+ }
+
+ /** Test the periodic run of the collector once testRunStarted has been called. */
+ @Test
+ public void testSetupAndPeriodicRun() throws Exception {
+ OptionSetter setter = new OptionSetter(mBase);
+ // 100 ms interval
+ setter.setOptionValue("interval", "100");
+ Map<String, String> metrics = new HashMap<>();
+ mBase.init(mContext, mMockListener);
+ try {
+ mBase.testRunStarted("testRun", 1);
+ RunUtil.getDefault().sleep(500);
+ } finally {
+ mBase.testRunEnded(0l, metrics);
+ }
+ // We give it 500msec to run and 100msec interval we should easily have at least three
+ // iterations
+ assertTrue(metrics.containsKey("key1"));
+ assertTrue(metrics.containsKey("key2"));
+ assertTrue(metrics.containsKey("key3"));
+ }
+}
diff --git a/tests/src/com/android/tradefed/invoker/ShardListenerTest.java b/tests/src/com/android/tradefed/invoker/ShardListenerTest.java
new file mode 100644
index 0000000..705b55c
--- /dev/null
+++ b/tests/src/com/android/tradefed/invoker/ShardListenerTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.invoker;
+
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.build.BuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.ITestInvocationListener;
+
+import org.easymock.EasyMock;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.Collections;
+
+/** Unit tests for {@link ShardListener}. */
+@RunWith(JUnit4.class)
+public class ShardListenerTest {
+ private ShardListener mShardListener;
+ private ITestInvocationListener mMockListener;
+ private IInvocationContext mContext;
+ private ITestDevice mMockDevice;
+
+ @Before
+ public void setUp() {
+ mMockListener = EasyMock.createMock(ITestInvocationListener.class);
+ mShardListener = new ShardListener(mMockListener);
+ mMockDevice = EasyMock.createMock(ITestDevice.class);
+ EasyMock.expect(mMockDevice.getSerialNumber()).andStubReturn("serial");
+ mContext = new InvocationContext();
+ mContext.addDeviceBuildInfo("default", new BuildInfo());
+ mContext.addAllocatedDevice("default", mMockDevice);
+ }
+
+ /** Ensure that all the events given to the shardlistener are replayed on invocationEnded. */
+ @Test
+ public void testBufferAndReplay() {
+ mMockListener.invocationStarted(mContext);
+ mMockListener.testRunStarted("run1", 1);
+ TestIdentifier tid = new TestIdentifier("class1", "name1");
+ mMockListener.testStarted(tid, 0l);
+ mMockListener.testEnded(tid, 0l, Collections.emptyMap());
+ mMockListener.testRunEnded(0l, Collections.emptyMap());
+ mMockListener.invocationEnded(0l);
+
+ EasyMock.replay(mMockListener, mMockDevice);
+ mShardListener.invocationStarted(mContext);
+ mShardListener.testRunStarted("run1", 1);
+ mShardListener.testStarted(tid, 0l);
+ mShardListener.testEnded(tid, 0l, Collections.emptyMap());
+ mShardListener.testRunEnded(0l, Collections.emptyMap());
+ mShardListener.invocationEnded(0l);
+ EasyMock.verify(mMockListener, mMockDevice);
+ }
+
+ /** Test that the buffering of events is properly done in respect to the modules too. */
+ @Test
+ public void testBufferAndReplay_withModule() {
+ IInvocationContext module1 = new InvocationContext();
+ IInvocationContext module2 = new InvocationContext();
+ mMockListener.invocationStarted(mContext);
+ mMockListener.testModuleStarted(module1);
+ mMockListener.testRunStarted("run1", 1);
+ TestIdentifier tid = new TestIdentifier("class1", "name1");
+ mMockListener.testStarted(tid, 0l);
+ mMockListener.testEnded(tid, 0l, Collections.emptyMap());
+ mMockListener.testRunEnded(0l, Collections.emptyMap());
+ mMockListener.testRunStarted("run2", 1);
+ mMockListener.testStarted(tid, 0l);
+ mMockListener.testEnded(tid, 0l, Collections.emptyMap());
+ mMockListener.testRunEnded(0l, Collections.emptyMap());
+ mMockListener.testModuleEnded();
+ // expectation on second module
+ mMockListener.testModuleStarted(module2);
+ mMockListener.testRunStarted("run3", 1);
+ mMockListener.testStarted(tid, 0l);
+ mMockListener.testEnded(tid, 0l, Collections.emptyMap());
+ mMockListener.testRunEnded(0l, Collections.emptyMap());
+ mMockListener.testModuleEnded();
+ mMockListener.invocationEnded(0l);
+
+ EasyMock.replay(mMockListener, mMockDevice);
+ mShardListener.invocationStarted(mContext);
+ // 1st module
+ mShardListener.testModuleStarted(module1);
+ mShardListener.testRunStarted("run1", 1);
+ mShardListener.testStarted(tid, 0l);
+ mShardListener.testEnded(tid, 0l, Collections.emptyMap());
+ mShardListener.testRunEnded(0l, Collections.emptyMap());
+ mShardListener.testRunStarted("run2", 1);
+ mShardListener.testStarted(tid, 0l);
+ mShardListener.testEnded(tid, 0l, Collections.emptyMap());
+ mShardListener.testRunEnded(0l, Collections.emptyMap());
+ mShardListener.testModuleEnded();
+ // 2nd module
+ mShardListener.testModuleStarted(module2);
+ mShardListener.testRunStarted("run3", 1);
+ mShardListener.testStarted(tid, 0l);
+ mShardListener.testEnded(tid, 0l, Collections.emptyMap());
+ mShardListener.testRunEnded(0l, Collections.emptyMap());
+ mShardListener.testModuleEnded();
+
+ mShardListener.invocationEnded(0l);
+ EasyMock.verify(mMockListener, mMockDevice);
+ }
+}
diff --git a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
index a8bc729..b03a479 100644
--- a/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
+++ b/tests/src/com/android/tradefed/invoker/TestInvocationTest.java
@@ -18,6 +18,7 @@
import static org.mockito.Mockito.doReturn;
import com.android.ddmlib.IDevice;
+import com.android.ddmlib.testrunner.TestIdentifier;
import com.android.tradefed.build.BuildInfo;
import com.android.tradefed.build.BuildRetrievalError;
import com.android.tradefed.build.IBuildInfo;
@@ -44,6 +45,9 @@
import com.android.tradefed.device.ITestDevice.RecoveryMode;
import com.android.tradefed.device.StubDevice;
import com.android.tradefed.device.TestDeviceOptions;
+import com.android.tradefed.device.metric.BaseDeviceMetricCollector;
+import com.android.tradefed.device.metric.IMetricCollector;
+import com.android.tradefed.device.metric.DeviceMetricData;
import com.android.tradefed.invoker.shard.IShardHelper;
import com.android.tradefed.invoker.shard.ShardHelper;
import com.android.tradefed.invoker.shard.StrictShardHelper;
@@ -72,6 +76,7 @@
import com.android.tradefed.testtype.IRetriableTest;
import com.android.tradefed.testtype.IShardableTest;
import com.android.tradefed.testtype.IStrictShardableTest;
+import com.android.tradefed.testtype.StubTest;
import com.android.tradefed.util.FileUtil;
import com.google.common.util.concurrent.SettableFuture;
@@ -1657,4 +1662,54 @@
FileUtil.recursiveDelete(tmpExternalTestsDir);
}
}
+
+ private class TestableCollector extends BaseDeviceMetricCollector {
+
+ private String mName;
+
+ public TestableCollector(String name) {
+ mName = name;
+ }
+
+ @Override
+ public void onTestRunEnd(DeviceMetricData runData) {
+ runData.addStringMetric(mName, mName);
+ }
+ }
+
+ /**
+ * Test that when {@link IMetricCollector} are used, they wrap and call in sequence the listener
+ * so all metrics end up on the final receiver.
+ */
+ public void testMetricCollectionChain() throws Exception {
+ IConfiguration configuration = new Configuration("test", "description");
+ StubTest test = new StubTest();
+ OptionSetter setter = new OptionSetter(test);
+ setter.setOptionValue("run-a-test", "true");
+ configuration.setTest(test);
+
+ List<IMetricCollector> collectors = new ArrayList<>();
+ collectors.add(new TestableCollector("collector1"));
+ collectors.add(new TestableCollector("collector2"));
+ collectors.add(new TestableCollector("collector3"));
+ collectors.add(new TestableCollector("collector4"));
+ configuration.setDeviceMetricCollectors(collectors);
+
+ mMockTestListener.testRunStarted("TestStub", 1);
+ TestIdentifier testId = new TestIdentifier("StubTest", "StubMethod");
+ mMockTestListener.testStarted(EasyMock.eq(testId), EasyMock.anyLong());
+ mMockTestListener.testEnded(
+ EasyMock.eq(testId), EasyMock.anyLong(), EasyMock.eq(Collections.emptyMap()));
+ Capture<Map<String, String>> captured = new Capture<>();
+ mMockTestListener.testRunEnded(EasyMock.anyLong(), EasyMock.capture(captured));
+ EasyMock.replay(mMockTestListener);
+ mTestInvocation.runTests(mStubInvocationMetadata, configuration, mMockTestListener);
+ EasyMock.verify(mMockTestListener);
+ // The collectors are called in sequence
+ List<String> listKeys = new ArrayList<>(captured.getValue().keySet());
+ assertEquals("collector4", listKeys.get(0));
+ assertEquals("collector3", listKeys.get(1));
+ assertEquals("collector2", listKeys.get(2));
+ assertEquals("collector1", listKeys.get(3));
+ }
}
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteMultiTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteMultiTest.java
index d2bafe1..0dc0fda 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteMultiTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteMultiTest.java
@@ -111,7 +111,7 @@
mTestSuite.setInvocationContext(mContext);
mTestSuite.setSystemStatusChecker(new ArrayList<>());
-
+ mMockListener.testModuleStarted(EasyMock.anyObject());
mMockListener.testRunStarted("test1", 2);
TestIdentifier test1 =
new TestIdentifier(MultiDeviceStubTest.class.getSimpleName(), "test0");
@@ -122,7 +122,7 @@
mMockListener.testStarted(test2, 0l);
mMockListener.testEnded(test2, 5l, Collections.emptyMap());
mMockListener.testRunEnded(EasyMock.anyLong(), EasyMock.anyObject());
-
+ mMockListener.testModuleEnded();
EasyMock.replay(
mMockListener, mMockBuildInfo1, mMockBuildInfo2, mMockDevice1, mMockDevice2);
mTestSuite.run(mMockListener);
diff --git a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
index 3750c4d..f691558 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ITestSuiteTest.java
@@ -166,11 +166,13 @@
/** Helper to expect the test run callback. */
private void expectTestRun(ITestInvocationListener listener) {
+ listener.testModuleStarted(EasyMock.anyObject());
listener.testRunStarted(TEST_CONFIG_NAME, 1);
TestIdentifier test = new TestIdentifier(EMPTY_CONFIG, EMPTY_CONFIG);
listener.testStarted(test, 0);
listener.testEnded(test, 5, Collections.emptyMap());
listener.testRunEnded(EasyMock.anyLong(), EasyMock.anyObject());
+ listener.testModuleEnded();
}
/** Test for {@link ITestSuite#run(ITestInvocationListener)}. */
@@ -307,11 +309,13 @@
setter.setOptionValue("skip-all-system-status-check", "true");
setter.setOptionValue("reboot-per-module", "true");
EasyMock.expect(mMockDevice.getProperty("ro.build.type")).andReturn("user");
+ mMockListener.testModuleStarted(EasyMock.anyObject());
mMockListener.testRunStarted(TEST_CONFIG_NAME, 1);
EasyMock.expectLastCall().times(1);
mMockListener.testRunFailed("Module test only ran 0 out of 1 expected tests.");
mMockListener.testRunEnded(EasyMock.anyLong(), EasyMock.anyObject());
EasyMock.expectLastCall().times(1);
+ mMockListener.testModuleEnded();
replayMocks();
mTestSuite.run(mMockListener);
verifyMocks();
@@ -352,11 +356,13 @@
setter.setOptionValue("skip-all-system-status-check", "true");
setter.setOptionValue("reboot-per-module", "true");
EasyMock.expect(mMockDevice.getProperty("ro.build.type")).andReturn("user");
+ mMockListener.testModuleStarted(EasyMock.anyObject());
mMockListener.testRunStarted(TEST_CONFIG_NAME, 1);
EasyMock.expectLastCall().times(1);
mMockListener.testRunFailed("Module test only ran 0 out of 1 expected tests.");
mMockListener.testRunEnded(EasyMock.anyLong(), EasyMock.anyObject());
EasyMock.expectLastCall().times(1);
+ mMockListener.testModuleEnded();
replayMocks();
mTestSuite.run(mMockListener);
verifyMocks();
diff --git a/tests/src/com/android/tradefed/testtype/suite/TfSuiteRunnerTest.java b/tests/src/com/android/tradefed/testtype/suite/TfSuiteRunnerTest.java
index b67d878..240e508 100644
--- a/tests/src/com/android/tradefed/testtype/suite/TfSuiteRunnerTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/TfSuiteRunnerTest.java
@@ -182,8 +182,10 @@
mRunner.setSystemStatusChecker(new ArrayList<>());
mRunner.setInvocationContext(new InvocationContext());
// runs the expanded suite
+ listener.testModuleStarted(EasyMock.anyObject());
listener.testRunStarted("suite/stub1", 0);
listener.testRunEnded(EasyMock.anyLong(), EasyMock.anyObject());
+ listener.testModuleEnded();
EasyMock.replay(listener);
mRunner.run(listener);
EasyMock.verify(listener);