Add ability to parse test package results from xml.
Also fix xml generation for stack traces.
Bug 5171576
Change-Id: I3cbcdb69c6887bace2b1bd0610aeba7f1f8b8884
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/AbstractXmlPullParser.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/AbstractXmlPullParser.java
new file mode 100644
index 0000000..c5f1e2d
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/AbstractXmlPullParser.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2011 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.cts.tradefed.result;
+
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlPullParserFactory;
+
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Helper abstract class for XmlPullParser
+ *
+ * TODO: move to com.android.tradefed.util.xml
+ */
+public abstract class AbstractXmlPullParser {
+
+ /**
+ * Parse the summary data from the given input data.
+ *
+ * @param xmlReader the input XML
+ * @throws ParseException if failed to parse the summary data.
+ */
+ public void parse(Reader xmlReader) throws ParseException {
+ try {
+ XmlPullParserFactory fact = org.xmlpull.v1.XmlPullParserFactory.newInstance();
+ XmlPullParser parser = fact.newPullParser();
+ parser.setInput (xmlReader);
+ parse(parser);
+ } catch (XmlPullParserException e) {
+ throw new ParseException(e);
+ } catch (IOException e) {
+ throw new ParseException(e);
+ }
+ }
+
+ abstract void parse(XmlPullParser parser) throws XmlPullParserException, IOException;
+
+ /**
+ * Parse an integer value from an XML attribute
+ *
+ * @param parser the {@link XmlPullParser}
+ * @param name the attribute name
+ * @return the parsed value or 0 if it could not be parsed
+ */
+ protected int parseIntAttr(XmlPullParser parser, String name) {
+ try {
+ String value = parser.getAttributeValue(null, name);
+ if (value != null) {
+ return Integer.parseInt(value);
+ }
+ } catch (NumberFormatException e) {
+ // ignore
+ }
+ return 0;
+ }
+
+ /**
+ * Parse a boolean attribute value
+ */
+ protected boolean parseBooleanAttr(XmlPullParser parser, String name) {
+ String stringValue = parser.getAttributeValue(null, name);
+ return stringValue != null &&
+ Boolean.parseBoolean(stringValue);
+ }
+
+ /**
+ * Helper method for retrieving attribute value with given name
+ *
+ * @param parser the XmlPullParser
+ * @param name the attribute name
+ * @return the attribute value
+ */
+ protected String getAttribute(XmlPullParser parser, String name) {
+ return parser.getAttributeValue(null, name);
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
index ab6cb85..184b8ab 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
@@ -20,13 +20,15 @@
import com.android.tradefed.result.TestResult.TestStatus;
import org.kxml2.io.KXmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
/**
* Data structure that represents a "Test" result XML element.
*/
-class Test {
+class Test extends AbstractXmlPullParser {
static final String TAG = "Test";
private static final String NAME_ATTR = "name";
@@ -35,6 +37,7 @@
private static final String STARTTIME_ATTR = "starttime";
private static final String RESULT_ATTR = "result";
private static final String SCENE_TAG = "FailedScene";
+ private static final String STACK_TAG = "StackTrace";
private String mName;
private String mResult;
@@ -81,6 +84,26 @@
return mName;
}
+ public String getResult() {
+ return mResult;
+ }
+
+ public String getMessage() {
+ return mMessage;
+ }
+
+ public String getStartTime() {
+ return mStartTime;
+ }
+
+ public String getEndTime() {
+ return mEndTime;
+ }
+
+ public String getStackTrace() {
+ return mStackTrace;
+ }
+
/**
* Serialize this object and all its contents to XML.
*
@@ -98,7 +121,11 @@
if (mMessage != null) {
serializer.startTag(CtsXmlResultReporter.ns, SCENE_TAG);
serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR, mMessage);
- serializer.text(mStackTrace);
+ if (mStackTrace != null) {
+ serializer.startTag(CtsXmlResultReporter.ns, STACK_TAG);
+ serializer.text(mStackTrace);
+ serializer.endTag(CtsXmlResultReporter.ns, STACK_TAG);
+ }
serializer.endTag(CtsXmlResultReporter.ns, SCENE_TAG);
}
serializer.endTag(CtsXmlResultReporter.ns, TAG);
@@ -146,4 +173,34 @@
}
return stack;
}
+
+ /**
+ * Populates this class with test result data parsed from XML.
+ *
+ * @param parser the {@link XmlPullParser}. Expected to be pointing at start
+ * of a Test tag
+ */
+ @Override
+ void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
+ if (!parser.getName().equals(TAG)) {
+ throw new XmlPullParserException(String.format(
+ "invalid XML: Expected %s tag but received %s", TAG, parser.getName()));
+ }
+ setName(getAttribute(parser, NAME_ATTR));
+ mResult = getAttribute(parser, RESULT_ATTR);
+ mStartTime = getAttribute(parser, STARTTIME_ATTR);
+ mEndTime = getAttribute(parser, ENDTIME_ATTR);
+
+ int eventType = parser.next();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(SCENE_TAG)) {
+ mMessage = getAttribute(parser, MESSAGE_ATTR);
+ } else if (eventType == XmlPullParser.START_TAG && parser.getName().equals(STACK_TAG)) {
+ mStackTrace = parser.nextText();
+ } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
+ return;
+ }
+ eventType = parser.next();
+ }
+ }
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java
index 43db56e..13b793b 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestCase.java
@@ -18,15 +18,18 @@
import com.android.tradefed.result.TestResult;
import org.kxml2.io.KXmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Data structure that represents a "TestCase" XML element and its children.
*/
-class TestCase {
+class TestCase extends AbstractXmlPullParser {
static final String TAG = "TestCase";
@@ -54,6 +57,13 @@
}
/**
+ * Gets the child tests
+ */
+ public Collection<Test> getTests() {
+ return mChildTestMap.values();
+ }
+
+ /**
* Inserts given test result
*
* @param testName
@@ -90,4 +100,30 @@
}
serializer.endTag(CtsXmlResultReporter.ns, TAG);
}
+
+ /**
+ * Populates this class with test case result data parsed from XML.
+ *
+ * @param parser the {@link XmlPullParser}. Expected to be pointing at start
+ * of a TestCase tag
+ */
+ @Override
+ void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
+ if (!parser.getName().equals(TAG)) {
+ throw new XmlPullParserException(String.format(
+ "invalid XML: Expected %s tag but received %s", TAG, parser.getName()));
+ }
+ setName(getAttribute(parser, "name"));
+ int eventType = parser.next();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(Test.TAG)) {
+ Test test = new Test();
+ test.parse(parser);
+ insertTest(test);
+ } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
+ return;
+ }
+ eventType = parser.next();
+ }
+ }
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
index c2d7bd9..bf9ba31 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
@@ -20,8 +20,11 @@
import com.android.tradefed.result.TestResult;
import org.kxml2.io.KXmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
@@ -31,7 +34,7 @@
* <p/>
* Provides methods to serialize to XML.
*/
-class TestPackageResult {
+class TestPackageResult extends AbstractXmlPullParser {
static final String TAG = "TestPackage";
private static final String DIGEST_ATTR = "digest";
@@ -71,6 +74,13 @@
}
/**
+ * Return the {@link TestSuite}s
+ */
+ public Collection<TestSuite> getTestSuites() {
+ return mSuiteRoot.getTestSuites();
+ }
+
+ /**
* Adds a test result to this test package
*
* @param testId
@@ -106,4 +116,33 @@
mSuiteRoot.serialize(serializer);
serializer.endTag(ns, TAG);
}
+
+ /**
+ * Populates this class with package result data parsed from XML.
+ *
+ * @param parser the {@link XmlPullParser}. Expected to be pointing at start
+ * of TestPackage tag
+ */
+ @Override
+ void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
+ if (!parser.getName().equals(TAG)) {
+ throw new XmlPullParserException(String.format(
+ "invalid XML: Expected %s tag but received %s", TAG, parser.getName()));
+ }
+ setAppPackageName(getAttribute(parser, APP_PACKAGE_NAME_ATTR));
+ setName(getAttribute(parser, NAME_ATTR));
+ setDigest(getAttribute(parser, DIGEST_ATTR));
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TestSuite.TAG)) {
+ TestSuite suite = new TestSuite();
+ suite.parse(parser);
+ mSuiteRoot.insertSuite(suite);
+ }
+ if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
+ return;
+ }
+ eventType = parser.next();
+ }
+ }
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
new file mode 100644
index 0000000..6900e58
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestResults.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2010 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.cts.tradefed.result;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Data structure for the detailed CTS test results.
+ * <p/>
+ * Can deserialize results for test packages from XML
+ */
+class TestResults extends AbstractXmlPullParser {
+
+ private List<TestPackageResult> mPackages = new ArrayList<TestPackageResult>();
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
+ int eventType = parser.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(
+ TestPackageResult.TAG)) {
+ TestPackageResult pkg = new TestPackageResult();
+ pkg.parse(parser);
+ mPackages.add(pkg);
+ }
+ eventType = parser.next();
+ }
+ }
+
+ /**
+ * @return the list of parsed {@link TestPackageResult}.
+ */
+ public List<TestPackageResult> getPackages() {
+ return mPackages;
+ }
+}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java
index f7a44fc..2de1d13 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestSuite.java
@@ -18,8 +18,11 @@
import com.android.tradefed.result.TestResult;
import org.kxml2.io.KXmlSerializer;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException;
+import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -27,7 +30,7 @@
/**
* Data structure that represents a "TestSuite" XML element and its children.
*/
-class TestSuite {
+class TestSuite extends AbstractXmlPullParser {
static final String TAG = "TestSuite";
@@ -83,6 +86,20 @@
}
/**
+ * Gets all the child {@link TestSuite}s
+ */
+ public Collection<TestSuite> getTestSuites() {
+ return mChildSuiteMap.values();
+ }
+
+ /**
+ * Gets all the child {@link TestCase}s
+ */
+ public Collection<TestCase> getTestCases() {
+ return mChildTestCaseMap.values();
+ }
+
+ /**
* Get the child {@link TestSuite} with given name, creating if necessary.
*
* @param suiteName
@@ -132,4 +149,49 @@
serializer.endTag(CtsXmlResultReporter.ns, TAG);
}
}
+
+ /**
+ * Populates this class with suite result data parsed from XML.
+ *
+ * @param parser the {@link XmlPullParser}. Expected to be pointing at start
+ * of a TestSuite tag
+ */
+ @Override
+ void parse(XmlPullParser parser) throws XmlPullParserException, IOException {
+ if (!parser.getName().equals(TAG)) {
+ throw new XmlPullParserException(String.format(
+ "invalid XML: Expected %s tag but received %s", TAG, parser.getName()));
+ }
+ setName(getAttribute(parser, "name"));
+ int eventType = parser.next();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG && parser.getName().equals(TestSuite.TAG)) {
+ TestSuite suite = new TestSuite();
+ suite.parse(parser);
+ insertSuite(suite);
+ } else if (eventType == XmlPullParser.START_TAG && parser.getName().equals(
+ TestCase.TAG)) {
+ TestCase testCase = new TestCase();
+ testCase.parse(parser);
+ insertTestCase(testCase);
+ } else if (eventType == XmlPullParser.END_TAG && parser.getName().equals(TAG)) {
+ return;
+ }
+ eventType = parser.next();
+ }
+ }
+
+ /**
+ * Adds a child {@link TestCase}.
+ */
+ public void insertTestCase(TestCase testCase) {
+ mChildTestCaseMap.put(testCase.getName(), testCase);
+ }
+
+ /**
+ * Adds a child {@link TestSuite}.
+ */
+ public void insertSuite(TestSuite suite) {
+ mChildSuiteMap.put(suite.getName(), suite);
+ }
}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
index 1897387..4e6b914 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
@@ -141,7 +141,7 @@
assertTrue(output.contains(
"<Summary failed=\"1\" notExecuted=\"0\" timeout=\"0\" pass=\"0\" />"));
final String failureTag =
- "<FailedScene message=\"this is a trace\">this is a tracemore trace";
+ "<FailedScene message=\"this is a trace\"> <StackTrace>this is a tracemore trace";
assertTrue(output.contains(failureTag));
}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java
new file mode 100644
index 0000000..a0360cd
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/TestResultsTest.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2011 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.cts.tradefed.result;
+
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.StringReader;
+
+/**
+ * Unit tests for {@link TestResults} parsing.
+ */
+public class TestResultsTest extends junit.framework.TestCase {
+
+ private static final String RESULT_START = "<TestResult>";
+ private static final String RESULT_END = "</TestResult>";
+ private static final String TEST_PACKAGE_START =
+ "<TestPackage name=\"pkgName\" appPackageName=\"appPkgName\" digest=\"digValue\" >";
+ private static final String TEST_PACKAGE_END = "</TestPackage>";
+
+ private static final String TEST_PACKAGE_FULL =
+ RESULT_START +TEST_PACKAGE_START + TEST_PACKAGE_END + RESULT_END;
+
+ private static final String TEST_FULL =
+ RESULT_START + TEST_PACKAGE_START +
+ "<TestSuite name=\"com\" >" +
+ "<TestSuite name=\"example\" >" +
+ "<TestCase name=\"ExampleTest\" >" +
+ "<Test name=\"testExample\" endtime=\"et\" starttime=\"st\" result=\"fail\" >" +
+ "<FailedScene message=\"msg\" >" +
+ "<StackTrace>at ExampleTest.testExample()" +
+ "</StackTrace>" +
+ "</FailedScene>" +
+ "</Test>" +
+ "</TestCase>" +
+ "</TestSuite>" +
+ "</TestSuite>";
+
+ /**
+ * Test parsing data with no result content
+ */
+ public void testParse_empty() throws Exception {
+ TestResults parser = new TestResults();
+ parser.parse(new StringReader("<Empty/>"));
+ assertEquals(0, parser.getPackages().size());
+ }
+
+ /**
+ * Test parsing data with a single test package
+ */
+ public void testParse_package() throws Exception {
+ TestResults parser = new TestResults();
+ parser.parse(new StringReader(TEST_PACKAGE_FULL));
+ assertEquals(1, parser.getPackages().size());
+ TestPackageResult pkg = parser.getPackages().get(0);
+ assertEquals("pkgName", pkg.getName());
+ assertEquals("appPkgName", pkg.getAppPackageName());
+ assertEquals("digValue", pkg.getDigest());
+ }
+
+ /**
+ * Test parsing not well formed XML data
+ */
+ public void testParse_corrupt() throws Exception {
+ TestResults parser = new TestResults();
+ // missing TEST_PACKAGE_END
+ try {
+ parser.parse(new StringReader(RESULT_START + TEST_PACKAGE_START + RESULT_END));
+ fail("ParseException not thrown");
+ } catch (ParseException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test parsing a result with a single failed test
+ */
+ public void testParse_test() throws Exception {
+ TestResults parser = new TestResults();
+ parser.parse(new StringReader(TEST_FULL));
+ assertEquals(1, parser.getPackages().size());
+ TestPackageResult pkg = parser.getPackages().get(0);
+ TestSuite comSuite = pkg.getTestSuites().iterator().next();
+ assertEquals("com", comSuite.getName());
+ TestSuite exampleSuite = comSuite.getTestSuites().iterator().next();
+ assertEquals("example", exampleSuite.getName());
+ TestCase exampleCase = exampleSuite.getTestCases().iterator().next();
+ assertEquals("ExampleTest", exampleCase.getName());
+ Test exampleTest = exampleCase.getTests().iterator().next();
+ assertEquals("testExample", exampleTest.getName());
+ assertEquals("msg", exampleTest.getMessage());
+ assertEquals("at ExampleTest.testExample()", exampleTest.getStackTrace());
+ }
+}