Merge "Allow noisy-dry-run and dry-run to use the DryRunKeyStore" into pi-dev
diff --git a/atest/test_finders/test_finder_utils.py b/atest/test_finders/test_finder_utils.py
index db6d218..d613e52 100644
--- a/atest/test_finders/test_finder_utils.py
+++ b/atest/test_finders/test_finder_utils.py
@@ -76,6 +76,7 @@
# VTS xml parsing constants.
_VTS_TEST_MODULE = 'test-module-name'
+_VTS_MODULE = 'module-name'
_VTS_BINARY_SRC = 'binary-test-source'
_VTS_PUSH_GROUP = 'push-group'
_VTS_PUSH = 'push'
@@ -499,7 +500,7 @@
for tag in option_tags:
value = tag.attrib[_XML_VALUE].strip()
name = tag.attrib[_XML_NAME].strip()
- if name == _VTS_TEST_MODULE:
+ if name in [_VTS_TEST_MODULE, _VTS_MODULE]:
if module_info.is_module(value):
targets.add(value)
else:
diff --git a/prod-tests/res/config/longevity/metrics-collector.xml b/prod-tests/res/config/longevity/metrics-collectors.xml
similarity index 60%
rename from prod-tests/res/config/longevity/metrics-collector.xml
rename to prod-tests/res/config/longevity/metrics-collectors.xml
index 0a27514..3c328b7 100644
--- a/prod-tests/res/config/longevity/metrics-collector.xml
+++ b/prod-tests/res/config/longevity/metrics-collectors.xml
@@ -26,8 +26,23 @@
<!-- Collect graphicsstats dump every 4 minutes. -->
<option name="metric-collection-intervals" key="jank" value="240000" />
- <option name="metric-collector-command-classes" value="com.android.tradefed.device.metric.GfxInfoMetricCollector" />
+ <option name="metric-collector-command-classes" value="com.android.tradefed.device.metric.GraphicsStatsMetricCollector" />
+ <!-- Collect bugreport every 1 hour. -->
+ <option name="metric-collection-intervals" key="bugreportz" value="3600000" />
+ <option name="metric-collector-command-classes" value="com.android.tradefed.device.metric.BugreportzMetricCollector" />
+
+ <!-- Collect ion audio and system heap info every 15 minutes. -->
+ <option name="metric-collection-intervals" key="ion" value="900000" />
+ <option name="metric-collector-command-classes" value="com.android.tradefed.device.metric.IonHeapInfoMetricCollector" />
+
+ <!-- Collect pagetype info every 10 minutes. -->
+ <option name="metric-collection-intervals" key="pagetypeinfo" value="600000" />
+ <option name="metric-collector-command-classes" value="com.android.tradefed.device.metric.PagetypeInfoMetricCollector" />
+
+ <!-- Collect trace every 20 minutes. -->
+ <option name="metric-collection-intervals" key="trace" value="1200000" />
+ <option name="metric-collector-command-classes" value="com.android.tradefed.device.metric.TraceMetricCollector" />
<!-- Add more if there are requests. -->
</metrics_collector>
</configuration>
diff --git a/prod-tests/res/config/longevity/preparers.xml b/prod-tests/res/config/longevity/preparers.xml
new file mode 100644
index 0000000..5168a62
--- /dev/null
+++ b/prod-tests/res/config/longevity/preparers.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2018 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!-- Preparer configuration for longevity runs. -->
+<configuration description="Preparer configuration for longevity runs.">
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="test-file-name" value="PhoneLongevityTests.apk" />
+ </target_preparer>
+</configuration>
diff --git a/prod-tests/res/config/longevity/launch.xml b/prod-tests/res/config/longevity/test.xml
similarity index 70%
rename from prod-tests/res/config/longevity/launch.xml
rename to prod-tests/res/config/longevity/test.xml
index c200a57..b5db8b1 100644
--- a/prod-tests/res/config/longevity/launch.xml
+++ b/prod-tests/res/config/longevity/test.xml
@@ -13,14 +13,8 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<!-- Base configuration for longevity runs. -->
-<configuration description="Base configuration for longevity runs.">
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="test-file-name" value="PhoneLongevityTests.apk" />
- </target_preparer>
-
- <template-include name="preparers" default="empty" />
-
+<!-- Test configuration for longevity runs. -->
+<configuration description="Test configuration for longevity runs.">
<!-- Shuffle and run the longevity tests 100 times. The test timeout is
5 minutes and the test suite timeout is 10 mins.
-->
@@ -29,15 +23,10 @@
<option name="class" value="android.longevity.platform.PhoneSuite" />
<option name="instrumentation-arg" key="iterations" value="100" />
<option name="instrumentation-arg" key="shuffle" value="true" />
+ <!-- Test timeouts in 5 minutes. -->
<option name="instrumentation-arg" key="timeout_msec" value="300000" />
+ <!-- Suite timeouts in 10 hours. -->
<option name="instrumentation-arg" key="suite-timeout_msec" value="36000000" />
<option name="instrumentation-arg" key="min-battery" value="0.05" />
</test>
-
- <logger class="com.android.tradefed.log.FileLogger">
- <option name="log-level" value="VERBOSE" />
- <option name="log-level-display" value="VERBOSE" />
- </logger>
-
- <template-include name="metrics_collector" default="empty" />
</configuration>
diff --git a/src/com/android/tradefed/result/suite/SuiteResultHolder.java b/src/com/android/tradefed/result/suite/SuiteResultHolder.java
index 510f32a..63b80e2 100644
--- a/src/com/android/tradefed/result/suite/SuiteResultHolder.java
+++ b/src/com/android/tradefed/result/suite/SuiteResultHolder.java
@@ -29,8 +29,6 @@
/** The collection of all results from the invocation. */
public Collection<TestRunResult> runResults;
- /** A map of logged file by <file name, file url>. */
- public Map<String, String> loggedFiles;
/** A map of each module's abi. */
public Map<String, IAbi> modulesAbi;
diff --git a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
index 20d5e18..3a71fa3 100644
--- a/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
+++ b/src/com/android/tradefed/result/suite/XmlSuiteResultFormatter.java
@@ -16,6 +16,7 @@
package com.android.tradefed.result.suite;
import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.tradefed.result.LogFile;
import com.android.tradefed.result.TestDescription;
import com.android.tradefed.result.TestResult;
import com.android.tradefed.result.TestRunResult;
@@ -213,7 +214,7 @@
serializer.attribute(NS, DONE_ATTR, Boolean.toString(module.isRunComplete()));
serializer.attribute(
NS, PASS_ATTR, Integer.toString(module.getNumTestsInState(TestStatus.PASSED)));
- serializeTestCases(serializer, module.getTestResults(), holder.loggedFiles);
+ serializeTestCases(serializer, module.getTestResults());
serializer.endTag(NS, MODULE_TAG);
}
serializer.endDocument();
@@ -221,9 +222,7 @@
}
private static void serializeTestCases(
- XmlSerializer serializer,
- Map<TestDescription, TestResult> results,
- Map<String, String> loggedFiles)
+ XmlSerializer serializer, Map<TestDescription, TestResult> results)
throws IllegalArgumentException, IllegalStateException, IOException {
// We reformat into the same format as the ResultHandler from CTS to be compatible for now.
Map<String, Map<String, TestResult>> format = new LinkedHashMap<>();
@@ -252,7 +251,7 @@
handleTestFailure(serializer, individualResult.getValue().getStackTrace());
- HandleLoggedFiles(serializer, loggedFiles, className, individualResult.getKey());
+ HandleLoggedFiles(serializer, individualResult);
for (Entry<String, String> metric :
individualResult.getValue().getMetrics().entrySet()) {
@@ -290,31 +289,32 @@
/** Add files captured by {@link TestFailureListener} on test failures. */
private static void HandleLoggedFiles(
- XmlSerializer serializer,
- Map<String, String> loggedFiles,
- String className,
- String testName)
+ XmlSerializer serializer, Entry<String, TestResult> testResult)
throws IllegalArgumentException, IllegalStateException, IOException {
- if (loggedFiles == null) {
+ Map<String, LogFile> loggedFiles = testResult.getValue().getLoggedFiles();
+ if (loggedFiles == null || loggedFiles.isEmpty()) {
return;
}
- // TODO: If possible handle a little more generically.
- String testId = new TestDescription(className, testName).toString();
for (String key : loggedFiles.keySet()) {
- if (key.startsWith(testId)) {
- if (key.endsWith("bugreport")) {
+ switch (loggedFiles.get(key).getType()) {
+ case BUGREPORT:
serializer.startTag(NS, BUGREPORT_TAG);
- serializer.text(loggedFiles.get(key));
+ serializer.text(loggedFiles.get(key).getUrl());
serializer.endTag(NS, BUGREPORT_TAG);
- } else if (key.endsWith("logcat")) {
+ break;
+ case LOGCAT:
serializer.startTag(NS, LOGCAT_TAG);
- serializer.text(loggedFiles.get(key));
+ serializer.text(loggedFiles.get(key).getUrl());
serializer.endTag(NS, LOGCAT_TAG);
- } else if (key.endsWith("screenshot")) {
+ break;
+ case PNG:
+ case JPEG:
serializer.startTag(NS, SCREENSHOT_TAG);
- serializer.text(loggedFiles.get(key));
+ serializer.text(loggedFiles.get(key).getUrl());
serializer.endTag(NS, SCREENSHOT_TAG);
- }
+ break;
+ default:
+ break;
}
}
}
diff --git a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
index a016ab5..cd1dbc8 100644
--- a/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
+++ b/src/com/android/tradefed/testtype/suite/ModuleDefinition.java
@@ -22,6 +22,7 @@
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.StubDevice;
import com.android.tradefed.device.metric.IMetricCollector;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.InvocationContext;
@@ -441,6 +442,14 @@
private void reportFailure(ITestInvocationListener listener, String errorMessage) {
listener.testRunFailed(errorMessage);
+ for (ITestDevice device : mModuleInvocationContext.getDevices()) {
+ if (device.getIDevice() instanceof StubDevice) {
+ continue;
+ }
+ device.logBugreport(
+ String.format("module-failure-bugreport-%s", device.getSerialNumber()),
+ listener);
+ }
}
/** Helper to log the device events. */
diff --git a/tests/src/com/android/tradefed/UnitTests.java b/tests/src/com/android/tradefed/UnitTests.java
index 232f2bc..b03c28a 100644
--- a/tests/src/com/android/tradefed/UnitTests.java
+++ b/tests/src/com/android/tradefed/UnitTests.java
@@ -118,6 +118,7 @@
import com.android.tradefed.result.TestSummaryTest;
import com.android.tradefed.result.XmlResultReporterTest;
import com.android.tradefed.result.suite.FormattedGeneratorReporterTest;
+import com.android.tradefed.result.suite.XmlSuiteResultFormatterTest;
import com.android.tradefed.sandbox.SandboxConfigDumpTest;
import com.android.tradefed.sandbox.SandboxConfigUtilTest;
import com.android.tradefed.sandbox.SandboxInvocationRunnerTest;
@@ -410,6 +411,7 @@
// result.suite
FormattedGeneratorReporterTest.class,
+ XmlSuiteResultFormatterTest.class,
// targetprep
AllTestAppsInstallSetupTest.class,
diff --git a/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
new file mode 100644
index 0000000..144a1d7
--- /dev/null
+++ b/tests/src/com/android/tradefed/result/suite/XmlSuiteResultFormatterTest.java
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.tradefed.result.suite;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tradefed.invoker.IInvocationContext;
+import com.android.tradefed.invoker.InvocationContext;
+import com.android.tradefed.result.LogDataType;
+import com.android.tradefed.result.LogFile;
+import com.android.tradefed.result.TestDescription;
+import com.android.tradefed.result.TestRunResult;
+import com.android.tradefed.testtype.Abi;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.util.FileUtil;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.InputSource;
+
+import java.io.File;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.xml.xpath.XPath;
+import javax.xml.xpath.XPathConstants;
+import javax.xml.xpath.XPathExpressionException;
+import javax.xml.xpath.XPathFactory;
+
+/** Unit tests for {@link XmlSuiteResultFormatter}. */
+@RunWith(JUnit4.class)
+public class XmlSuiteResultFormatterTest {
+ private XmlSuiteResultFormatter mFormatter;
+ private SuiteResultHolder mResultHolder;
+ private IInvocationContext mContext;
+ private File mResultDir;
+
+ @Before
+ public void setUp() throws Exception {
+ mFormatter = new XmlSuiteResultFormatter();
+ mResultHolder = new SuiteResultHolder();
+ mContext = new InvocationContext();
+ mResultDir = FileUtil.createTempDir("result-dir");
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ FileUtil.recursiveDelete(mResultDir);
+ }
+
+ /** Check that the basic overall structure is good an contains all the information. */
+ @Test
+ public void testBasicFormat() throws Exception {
+ mResultHolder.context = mContext;
+
+ Collection<TestRunResult> runResults = new ArrayList<>();
+ runResults.add(createFakeResult("module1", 2, 0));
+ runResults.add(createFakeResult("module2", 1, 0));
+ mResultHolder.runResults = runResults;
+
+ Map<String, IAbi> modulesAbi = new HashMap<>();
+ modulesAbi.put("module1", new Abi("armeabi-v7a", "32"));
+ modulesAbi.put("module2", new Abi("armeabi-v7a", "32"));
+ mResultHolder.modulesAbi = modulesAbi;
+
+ mResultHolder.completeModules = 2;
+ mResultHolder.totalModules = 2;
+ mResultHolder.passedTests = 2;
+ mResultHolder.failedTests = 0;
+ mResultHolder.startTime = 0L;
+ mResultHolder.endTime = 10L;
+ File res = mFormatter.writeResults(mResultHolder, mResultDir);
+ String content = FileUtil.readStringFromFile(res);
+ assertXmlContainsNode(content, "Result");
+ // Verify that the summary has been populated
+ assertXmlContainsNode(content, "Result/Summary");
+ assertXmlContainsAttribute(content, "Result/Summary", "pass", "2");
+ assertXmlContainsAttribute(content, "Result/Summary", "failed", "0");
+ assertXmlContainsAttribute(content, "Result/Summary", "modules_done", "2");
+ assertXmlContainsAttribute(content, "Result/Summary", "modules_total", "2");
+ // Verify that each module results are available
+ assertXmlContainsNode(content, "Result/Module");
+ assertXmlContainsAttribute(content, "Result/Module", "name", "module1");
+ assertXmlContainsAttribute(content, "Result/Module", "abi", "armeabi-v7a");
+ assertXmlContainsAttribute(content, "Result/Module", "runtime", "10");
+ assertXmlContainsAttribute(content, "Result/Module", "done", "true");
+ assertXmlContainsAttribute(content, "Result/Module", "pass", "2");
+ // Verify the test cases that passed are present
+ assertXmlContainsNode(content, "Result/Module/TestCase");
+ assertXmlContainsAttribute(content, "Result/Module/TestCase", "name", "com.class.module1");
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test", "name", "module1.method0");
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test", "name", "module1.method1");
+
+ assertXmlContainsAttribute(content, "Result/Module", "name", "module2");
+ assertXmlContainsAttribute(content, "Result/Module", "pass", "1");
+ assertXmlContainsAttribute(content, "Result/Module/TestCase", "name", "com.class.module2");
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test", "name", "module2.method0");
+ }
+
+ /** Check that the test failures are properly reported. */
+ @Test
+ public void testFailuresReporting() throws Exception {
+ mResultHolder.context = mContext;
+
+ Collection<TestRunResult> runResults = new ArrayList<>();
+ runResults.add(createFakeResult("module1", 2, 1));
+ mResultHolder.runResults = runResults;
+
+ Map<String, IAbi> modulesAbi = new HashMap<>();
+ modulesAbi.put("module1", new Abi("armeabi-v7a", "32"));
+ mResultHolder.modulesAbi = modulesAbi;
+
+ mResultHolder.completeModules = 2;
+ mResultHolder.totalModules = 1;
+ mResultHolder.passedTests = 1;
+ mResultHolder.failedTests = 1;
+ mResultHolder.startTime = 0L;
+ mResultHolder.endTime = 10L;
+ File res = mFormatter.writeResults(mResultHolder, mResultDir);
+ String content = FileUtil.readStringFromFile(res);
+
+ assertXmlContainsNode(content, "Result/Module");
+ assertXmlContainsAttribute(content, "Result/Module/TestCase", "name", "com.class.module1");
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test", "name", "module1.method0");
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test", "name", "module1.method1");
+ // Check that failures are showing in the xml for the test cases
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test", "name", "module1.failed0");
+ assertXmlContainsAttribute(
+ content, "Result/Module/TestCase/Test/Failure", "message", "module1 failed.");
+ assertXmlContainsValue(
+ content,
+ "Result/Module/TestCase/Test/Failure/StackTrace",
+ "module1 failed.\nstack\nstack");
+ }
+
+ /** Check that the logs for each test case are reported. */
+ @Test
+ public void testLogReporting() throws Exception {
+ mResultHolder.context = mContext;
+
+ Collection<TestRunResult> runResults = new ArrayList<>();
+ runResults.add(createResultWithLog("module1", 1, LogDataType.LOGCAT));
+ runResults.add(createResultWithLog("module2", 1, LogDataType.BUGREPORT));
+ runResults.add(createResultWithLog("module3", 1, LogDataType.PNG));
+ mResultHolder.runResults = runResults;
+
+ Map<String, IAbi> modulesAbi = new HashMap<>();
+ modulesAbi.put("module1", new Abi("armeabi-v7a", "32"));
+ mResultHolder.modulesAbi = modulesAbi;
+
+ mResultHolder.completeModules = 2;
+ mResultHolder.totalModules = 2;
+ mResultHolder.passedTests = 2;
+ mResultHolder.failedTests = 0;
+ mResultHolder.startTime = 0L;
+ mResultHolder.endTime = 10L;
+ File res = mFormatter.writeResults(mResultHolder, mResultDir);
+ String content = FileUtil.readStringFromFile(res);
+ // One logcat and one bugreport are found in the report
+ assertXmlContainsValue(content, "Result/Module/TestCase/Test/Logcat", "http:url/module1");
+ assertXmlContainsValue(
+ content, "Result/Module/TestCase/Test/BugReport", "http:url/module2");
+ assertXmlContainsValue(
+ content, "Result/Module/TestCase/Test/Screenshot", "http:url/module3");
+ }
+
+ private TestRunResult createResultWithLog(String runName, int count, LogDataType type) {
+ TestRunResult fakeRes = new TestRunResult();
+ fakeRes.testRunStarted(runName, count);
+ for (int i = 0; i < count; i++) {
+ TestDescription description =
+ new TestDescription("com.class." + runName, runName + ".method" + i);
+ fakeRes.testStarted(description);
+ fakeRes.testLogSaved(
+ runName + "log" + i, new LogFile("path", "http:url/" + runName, type));
+ fakeRes.testEnded(description, new HashMap<>());
+ }
+ fakeRes.testRunEnded(10L, new HashMap<>());
+ return fakeRes;
+ }
+
+ private TestRunResult createFakeResult(String runName, int passed, int failed) {
+ TestRunResult fakeRes = new TestRunResult();
+ fakeRes.testRunStarted(runName, passed + failed);
+ for (int i = 0; i < passed; i++) {
+ TestDescription description =
+ new TestDescription("com.class." + runName, runName + ".method" + i);
+ fakeRes.testStarted(description);
+ fakeRes.testEnded(description, new HashMap<>());
+ }
+ for (int i = 0; i < failed; i++) {
+ TestDescription description =
+ new TestDescription("com.class." + runName, runName + ".failed" + i);
+ fakeRes.testStarted(description);
+ fakeRes.testFailed(description, runName + " failed.\nstack\nstack");
+ fakeRes.testEnded(description, new HashMap<>());
+ }
+ fakeRes.testRunEnded(10L, new HashMap<>());
+ return fakeRes;
+ }
+
+ /** Return all XML nodes that match the given xPathExpression. */
+ private NodeList getXmlNodes(String xml, String xPathExpression)
+ throws XPathExpressionException {
+
+ InputSource inputSource = new InputSource(new StringReader(xml));
+ XPath xpath = XPathFactory.newInstance().newXPath();
+ return (NodeList) xpath.evaluate(xPathExpression, inputSource, XPathConstants.NODESET);
+ }
+
+ /** Assert that the XML contains a node matching the given xPathExpression. */
+ private NodeList assertXmlContainsNode(String xml, String xPathExpression)
+ throws XPathExpressionException {
+ NodeList nodes = getXmlNodes(xml, xPathExpression);
+ assertNotNull(
+ String.format("XML '%s' returned null for xpath '%s'.", xml, xPathExpression),
+ nodes);
+ assertTrue(
+ String.format(
+ "XML '%s' should have returned at least 1 node for xpath '%s', "
+ + "but returned %s nodes instead.",
+ xml, xPathExpression, nodes.getLength()),
+ nodes.getLength() >= 1);
+ return nodes;
+ }
+
+ /**
+ * Assert that the XML contains a node matching the given xPathExpression and that the node has
+ * a given value.
+ */
+ private void assertXmlContainsValue(String xml, String xPathExpression, String value)
+ throws XPathExpressionException {
+ NodeList nodes = assertXmlContainsNode(xml, xPathExpression);
+ boolean found = false;
+
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element element = (Element) nodes.item(i);
+ if (element.getTextContent().equals(value)) {
+ found = true;
+ break;
+ }
+ }
+
+ assertTrue(
+ String.format(
+ "xPath '%s' should contain value '%s' but does not. XML: '%s'",
+ xPathExpression, value, xml),
+ found);
+ }
+
+ /**
+ * Assert that the XML contains a node matching the given xPathExpression and that the node has
+ * a given value.
+ */
+ private void assertXmlContainsAttribute(
+ String xml, String xPathExpression, String attributeName, String attributeValue)
+ throws XPathExpressionException {
+ NodeList nodes = assertXmlContainsNode(xml, xPathExpression);
+ boolean found = false;
+
+ for (int i = 0; i < nodes.getLength(); i++) {
+ Element element = (Element) nodes.item(i);
+ String value = element.getAttribute(attributeName);
+ if (attributeValue.equals(value)) {
+ found = true;
+ break;
+ }
+ }
+
+ assertTrue(
+ String.format(
+ "xPath '%s' should contain attribute '%s' but does not. XML: '%s'",
+ xPathExpression, attributeName, xml),
+ found);
+ }
+}
diff --git a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
index 3c36056..a1bf887 100644
--- a/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
+++ b/tests/src/com/android/tradefed/testtype/suite/ModuleDefinitionTest.java
@@ -18,6 +18,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
+import com.android.ddmlib.IDevice;
import com.android.tradefed.build.IBuildInfo;
import com.android.tradefed.command.remote.DeviceDescriptor;
import com.android.tradefed.config.Configuration;
@@ -25,6 +26,7 @@
import com.android.tradefed.config.IConfiguration;
import com.android.tradefed.config.OptionSetter;
import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.DeviceUnresponsiveException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.invoker.IInvocationContext;
import com.android.tradefed.invoker.TestInvocation;
@@ -95,6 +97,7 @@
private String mRunName;
private int mNumTest;
private boolean mShouldThrow;
+ private boolean mDeviceUnresponsive = false;
public TestObject(String runName, int numTest, boolean shouldThrow) {
mRunName = runName;
@@ -102,6 +105,14 @@
mShouldThrow = shouldThrow;
}
+ public TestObject(
+ String runName, int numTest, boolean shouldThrow, boolean deviceUnresponsive) {
+ mRunName = runName;
+ mNumTest = numTest;
+ mShouldThrow = shouldThrow;
+ mDeviceUnresponsive = deviceUnresponsive;
+ }
+
@Override
public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
listener.testRunStarted(mRunName, mNumTest);
@@ -111,6 +122,9 @@
if (mShouldThrow && i == mNumTest / 2) {
throw new DeviceNotAvailableException();
}
+ if (mDeviceUnresponsive) {
+ throw new DeviceUnresponsiveException();
+ }
listener.testEnded(test, Collections.emptyMap());
}
listener.testRunEnded(0, Collections.emptyMap());
@@ -169,7 +183,7 @@
* Helper for replaying mocks.
*/
private void replayMocks() {
- EasyMock.replay(mMockListener, mMockLogSaver, mMockLogSaverListener);
+ EasyMock.replay(mMockListener, mMockLogSaver, mMockLogSaverListener, mMockDevice);
for (IRemoteTest test : mTestList) {
EasyMock.replay(test);
}
@@ -186,7 +200,7 @@
* Helper for verifying mocks.
*/
private void verifyMocks() {
- EasyMock.verify(mMockListener, mMockLogSaver, mMockLogSaverListener);
+ EasyMock.verify(mMockListener, mMockLogSaver, mMockLogSaverListener, mMockDevice);
for (IRemoteTest test : mTestList) {
EasyMock.verify(test);
}
@@ -661,9 +675,64 @@
// Only a screenshot is capture, logcat for that module was disabled.
mMockListener.testLog(
EasyMock.anyObject(), EasyMock.eq(LogDataType.PNG), EasyMock.anyObject());
- EasyMock.replay(mMockDevice);
replayMocks();
mModule.run(mMockListener, failureListener);
- EasyMock.verify(mMockDevice);
+ verifyMocks();
+ }
+
+ /** Test when the test yields a DeviceUnresponsive exception. */
+ @Test
+ public void testRun_partialRun_deviceUnresponsive() throws Exception {
+ final int testCount = 4;
+ List<IRemoteTest> testList = new ArrayList<>();
+ testList.add(new TestObject("run1", testCount, false, true));
+ mModule =
+ new ModuleDefinition(
+ MODULE_NAME,
+ testList,
+ mMapDeviceTargetPreparer,
+ mMultiTargetPrepList,
+ new Configuration("", ""));
+ mModule.getModuleInvocationContext().addAllocatedDevice(DEFAULT_DEVICE_NAME, mMockDevice);
+ mModule.getModuleInvocationContext()
+ .addDeviceBuildInfo(DEFAULT_DEVICE_NAME, mMockBuildInfo);
+ mModule.setBuild(mMockBuildInfo);
+ mModule.setDevice(mMockDevice);
+ EasyMock.expect(mMockPrep.isDisabled()).andReturn(false);
+ // no isTearDownDisabled() expected for setup
+ mMockPrep.setUp(EasyMock.eq(mMockDevice), EasyMock.eq(mMockBuildInfo));
+ EasyMock.expect(mMockCleaner.isDisabled()).andStubReturn(false);
+ mMockCleaner.setUp(EasyMock.eq(mMockDevice), EasyMock.eq(mMockBuildInfo));
+ EasyMock.expect(mMockCleaner.isTearDownDisabled()).andStubReturn(false);
+ mMockCleaner.tearDown(
+ EasyMock.eq(mMockDevice), EasyMock.eq(mMockBuildInfo), EasyMock.isNull());
+ mMockListener.testRunStarted(MODULE_NAME, testCount);
+ for (int i = 0; i < 1; i++) {
+ mMockListener.testStarted((TestDescription) EasyMock.anyObject(), EasyMock.anyLong());
+ mMockListener.testEnded(
+ (TestDescription) EasyMock.anyObject(),
+ EasyMock.anyLong(),
+ EasyMock.anyObject());
+ }
+ mMockListener.testFailed(EasyMock.anyObject(), EasyMock.anyObject());
+ mMockListener.testRunFailed(EasyMock.anyObject());
+ mMockListener.testRunEnded(EasyMock.anyLong(), (Map<String, String>) EasyMock.anyObject());
+
+ // There was a module failure so a bugreport should be captured.
+ EasyMock.expect(mMockDevice.getIDevice()).andStubReturn(EasyMock.createMock(IDevice.class));
+ EasyMock.expect(mMockDevice.getSerialNumber()).andReturn("SERIAL");
+ EasyMock.expect(
+ mMockDevice.logBugreport(
+ EasyMock.eq("module-failure-bugreport-SERIAL"),
+ EasyMock.anyObject()))
+ .andReturn(true);
+
+ replayMocks();
+ // DeviceUnresponsive should not throw since it indicates that the device was recovered.
+ mModule.run(mMockListener);
+ // Only one module
+ assertEquals(1, mModule.getTestsResults().size());
+ assertEquals(0, mModule.getTestsResults().get(0).getNumCompleteTests());
+ verifyMocks();
}
}