Round 2 of cts-tradefed XML tweaks
- add deviceID
- add TestPackage name and digest
- remove erroneous extra TestCase result
- default testPlan to 'NA' instead of 'unknown'
Change-Id: I20acde9654ba61a488b17248713d3bb592692c70
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
index 14ed359..d6225d0 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
@@ -76,7 +76,7 @@
// listen in on the plan option provided to CtsTest
@Option(name = CtsTest.PLAN_OPTION, description = "the test plan to run.")
- private String mPlanName = "unknown";
+ private String mPlanName = "NA";
protected IBuildInfo mBuildInfo;
@@ -273,6 +273,7 @@
for (Map.Entry<String, String> metricEntry : metricsCopy.entrySet()) {
serializer.attribute(ns, metricEntry.getKey(), metricEntry.getValue());
}
+ serializer.attribute(ns, "deviceID", getBuildInfo().getDeviceSerial());
serializer.endTag(ns, "BuildInfo");
serializeFeatureInfo(serializer, featureData);
@@ -445,9 +446,9 @@
return;
}
serializer.startTag(ns, "TestPackage");
- serializer.attribute(ns, "name", runResult.getName());
+ serializer.attribute(ns, "name", getMetric(runResult, CtsTest.PACKAGE_NAME_METRIC));
serializer.attribute(ns, "appPackageName", runResult.getName());
- serializer.attribute(ns, "digest", getMetric(runResult, "digest"));
+ serializer.attribute(ns, "digest", getMetric(runResult, CtsTest.PACKAGE_DIGEST_METRIC));
// Dump the results.
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 5d149c8..65e00ff 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
@@ -77,7 +77,6 @@
result.getStartTime()));
serializer.attribute(CtsXmlResultReporter.ns, "endtime", TimeUtil.getTimestamp(
result.getEndTime()));
- serializer.attribute(CtsXmlResultReporter.ns, "result", convertStatus(result.getStatus()));
if (result.getStackTrace() != null) {
String sanitizedStack = sanitizeStackTrace(result.getStackTrace());
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
index b42d7f0..cc5dd37 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
@@ -43,9 +43,11 @@
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
+import java.util.Map;
import java.util.Queue;
import java.util.Set;
@@ -57,7 +59,6 @@
* Supports running all the tests contained in a CTS plan, or individual test packages.
*/
public class CtsTest implements IDeviceTest, IResumableTest, IShardableTest, IBuildReceiver {
-
private static final String LOG_TAG = "CtsTest";
public static final String PLAN_OPTION = "plan";
@@ -65,13 +66,16 @@
private static final String CLASS_OPTION = "class";
private static final String METHOD_OPTION = "method";
+ public static final String PACKAGE_NAME_METRIC = "packageName";
+ public static final String PACKAGE_DIGEST_METRIC = "packageDigest";
+
private ITestDevice mDevice;
@Option(name = PLAN_OPTION, description = "the test plan to run.",
importance = Importance.IF_UNSET)
private String mPlanName = null;
- @Option(name = PACKAGE_OPTION, description = "the test packages(s) to run.",
+ @Option(name = PACKAGE_OPTION, shortName = 'p', description = "the test packages(s) to run.",
importance = Importance.IF_UNSET)
private Collection<String> mPackageNames = new ArrayList<String>();
@@ -110,8 +114,11 @@
private class KnownTests {
private final IRemoteTest mTestForPackage;
private final Collection<TestIdentifier> mKnownTests;
+ private final ITestPackageDef mPackageDef;
- KnownTests(IRemoteTest testForPackage, Collection<TestIdentifier> knownTests) {
+ KnownTests(ITestPackageDef packageDef, IRemoteTest testForPackage,
+ Collection<TestIdentifier> knownTests) {
+ mPackageDef = packageDef;
mTestForPackage = testForPackage;
mKnownTests = knownTests;
}
@@ -123,6 +130,10 @@
Collection<TestIdentifier> getKnownTests() {
return mKnownTests;
}
+
+ ITestPackageDef getPackageDef() {
+ return mPackageDef;
+ }
}
/** list of remaining tests to execute */
@@ -245,9 +256,9 @@
collectDeviceInfo(getDevice(), mCtsBuild, listener);
while (!mRemainingTests.isEmpty()) {
- KnownTests testPair = mRemainingTests.get(0);
+ KnownTests knownTests = mRemainingTests.get(0);
- IRemoteTest test = testPair.getTestForPackage();
+ IRemoteTest test = knownTests.getTestForPackage();
if (test instanceof IDeviceTest) {
((IDeviceTest)test).setDevice(getDevice());
}
@@ -255,8 +266,9 @@
((IBuildReceiver)test).setBuild(mBuildInfo);
}
- ResultFilter filter = new ResultFilter(listener, testPair.getKnownTests());
+ ResultFilter filter = new ResultFilter(listener, knownTests.getKnownTests());
test.run(filter);
+ forwardPackageDetails(knownTests.getPackageDef(), listener);
mRemainingTests.remove(0);
}
@@ -310,7 +322,7 @@
mClassName, mMethodName);
if (testForPackage != null) {
Collection<TestIdentifier> knownTests = testPackage.getTests();
- testList.add(new KnownTests(testForPackage, knownTests));
+ testList.add(new KnownTests(testPackage, testForPackage, knownTests));
}
} else {
Log.e(LOG_TAG, String.format("Could not find test package uri %s", testUri));
@@ -471,4 +483,17 @@
}
return currentVal;
}
+
+ /**
+ * Forward the digest and package name to the listener as a metric
+ *
+ * @param listener
+ */
+ private void forwardPackageDetails(ITestPackageDef def, ITestInvocationListener listener) {
+ Map<String, String> metrics = new HashMap<String, String>(2);
+ metrics.put(PACKAGE_NAME_METRIC, def.getName());
+ metrics.put(PACKAGE_DIGEST_METRIC, def.getDigest());
+ listener.testRunStarted(def.getUri(), 0);
+ listener.testRunEnded(0, metrics);
+ }
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
index f4258d1..0348660 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestPackageDef.java
@@ -30,7 +30,7 @@
public interface ITestPackageDef {
/**
- * Get the unique URI of the test package.
+ * Get the unique URI, aka the appPackageName, of the test package.
* @return the {@link String} uri
*/
public String getUri();
@@ -69,4 +69,19 @@
*/
public Collection<TestIdentifier> getTests();
+ /**
+ * Return the sha1sum of the binary file for this test package.
+ * <p/>
+ * Will only return a valid value after {@link #createTest(File, String, String)} has been
+ * called.
+ *
+ * @return the sha1sum in {@link String} form
+ */
+ public String getDigest();
+
+ /**
+ * @return the name of this test package.
+ */
+ public String getName();
+
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
index 20396f7..58f2c00 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestPackageDef.java
@@ -17,10 +17,20 @@
import com.android.ddmlib.Log;
import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.tradefed.log.LogUtil.CLog;
import com.android.tradefed.testtype.IRemoteTest;
import com.android.tradefed.testtype.InstrumentationTest;
+import com.android.tradefed.util.StreamUtil;
+import java.io.BufferedInputStream;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.DigestInputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
@@ -48,6 +58,7 @@
private String mPackageToTest = null;
private String mApkToTestName = null;
private String mTestPackageName = null;
+ private String mDigest = null;
// use a LinkedHashSet for predictable iteration insertion-order, and fast lookups
private Collection<TestIdentifier> mTests = new LinkedHashSet<TestIdentifier>();
@@ -77,7 +88,11 @@
mName = name;
}
- String getName() {
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getName() {
return mName;
}
@@ -149,16 +164,18 @@
if (mIsHostSideTest) {
Log.d(LOG_TAG, String.format("Creating host test for %s", mName));
JarHostTest hostTest = new JarHostTest();
- hostTest.setRunName(mName);
+ hostTest.setRunName(getUri());
hostTest.setJarFileName(mJarPath);
hostTest.setTests(filterTests(mTests, className, methodName));
+ mDigest = generateDigest(testCaseDir, mJarPath);
return hostTest;
} else if (mIsVMHostTest) {
Log.d(LOG_TAG, String.format("Creating vm host test for %s", mName));
VMHostTest vmHostTest = new VMHostTest();
- vmHostTest.setRunName(mName);
+ vmHostTest.setRunName(getUri());
vmHostTest.setJarFileName(mJarPath);
vmHostTest.setTests(filterTests(mTests, className, methodName));
+ mDigest = generateDigest(testCaseDir, mJarPath);
return vmHostTest;
} else if (mIsSignatureTest) {
// TODO: hardcode the runner/class/method for now, since current package xml
@@ -174,19 +191,22 @@
addTest(new TestIdentifier(SIGNATURE_TEST_CLASS, SIGNATURE_TEST_METHOD));
// mName means 'apk file name' for instrumentation tests
instrTest.addInstallApk(String.format("%s.apk", mName), mAppNameSpace);
+ mDigest = generateDigest(testCaseDir, String.format("%s.apk", mName));
return instrTest;
} else if (mIsReferenceAppTest) {
// a reference app test is just a InstrumentationTest with one extra apk to install
InstrumentationApkTest instrTest = new InstrumentationApkTest();
instrTest.addInstallApk(String.format("%s.apk", mApkToTestName), mPackageToTest);
- return setInstrumentationTest(className, methodName, instrTest);
+ return setInstrumentationTest(className, methodName, instrTest, testCaseDir);
} else {
Log.d(LOG_TAG, String.format("Creating instrumentation test for %s", mName));
InstrumentationApkTest instrTest = new InstrumentationApkTest();
- return setInstrumentationTest(className, methodName, instrTest);
+ return setInstrumentationTest(className, methodName, instrTest, testCaseDir);
}
}
+
+
/**
* Populates given {@link InstrumentationApkTest} with data from the package xml
*
@@ -196,8 +216,9 @@
* @param instrTest
* @return the populated {@link InstrumentationTest} or <code>null</code>
*/
- private InstrumentationApkTest setInstrumentationTest(String className,
- String methodName, InstrumentationApkTest instrTest) {
+ private InstrumentationTest setInstrumentationTest(String className,
+ String methodName, InstrumentationApkTest instrTest, File testCaseDir) {
+ instrTest.setRunName(getUri());
instrTest.setPackageName(mAppNameSpace);
instrTest.setRunnerName(mRunner);
instrTest.setTestPackageName(mTestPackageName);
@@ -205,6 +226,7 @@
instrTest.setMethodName(methodName);
// mName means 'apk file name' for instrumentation tests
instrTest.addInstallApk(String.format("%s.apk", mName), mAppNameSpace);
+ mDigest = generateDigest(testCaseDir, String.format("%s.apk", mName));
if (mTests.size() > 1000) {
// TODO: hack, large test suites can take longer to collect tests, increase timeout
instrTest.setCollectsTestsShellTimeout(10*60*1000);
@@ -264,4 +286,68 @@
public Collection<TestIdentifier> getTests() {
return mTests;
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String getDigest() {
+ return mDigest;
+ }
+
+ /**
+ * Generate a sha1sum digest for a file.
+ * <p/>
+ * Exposed for unit testing.
+ *
+ * @param fileDir the directory of the file
+ * @param fileName the name of the file
+ * @return a hex {@link String} of the digest
+ */
+ String generateDigest(File fileDir, String fileName) {
+ final String algorithm = "SHA-1";
+ InputStream fileStream = null;
+ DigestInputStream d = null;
+ try {
+ fileStream = getFileStream(fileDir, fileName);
+ MessageDigest md = MessageDigest.getInstance(algorithm);
+ d = new DigestInputStream(fileStream, md);
+ byte[] buffer = new byte[8196];
+ while (d.read(buffer) != -1);
+ return toHexString(md.digest());
+ } catch (NoSuchAlgorithmException e) {
+ return algorithm + " not found";
+ } catch (IOException e) {
+ CLog.e(e);
+ } finally {
+ StreamUtil.closeStream(d);
+ StreamUtil.closeStream(fileStream);
+ }
+ return "failed to generate digest";
+ }
+
+ /**
+ * Retrieve an input stream for given file
+ * <p/>
+ * Exposed so unit tests can mock.
+ */
+ InputStream getFileStream(File fileDir, String fileName) throws FileNotFoundException {
+ InputStream fileStream;
+ fileStream = new BufferedInputStream(new FileInputStream(new File(fileDir, fileName)));
+ return fileStream;
+ }
+
+ /**
+ * Convert the given byte array into a lowercase hex string.
+ *
+ * @param arr The array to convert.
+ * @return The hex encoded string.
+ */
+ private String toHexString(byte[] arr) {
+ StringBuffer buf = new StringBuffer(arr.length * 2);
+ for (byte b : arr) {
+ buf.append(String.format("%02x", b & 0xFF));
+ }
+ return buf.toString();
+ }
}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java
index 2ff00d3..badccad 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/UnitTests.java
@@ -17,9 +17,10 @@
import com.android.cts.tradefed.result.CtsXmlResultReporterTest;
import com.android.cts.tradefed.targetprep.CtsSetupTest;
-import com.android.cts.tradefed.testtype.JarHostTestTest;
import com.android.cts.tradefed.testtype.CtsTestTest;
+import com.android.cts.tradefed.testtype.JarHostTestTest;
import com.android.cts.tradefed.testtype.PlanXmlParserTest;
+import com.android.cts.tradefed.testtype.TestPackageDefTest;
import com.android.cts.tradefed.testtype.TestPackageXmlParserTest;
import junit.framework.Test;
@@ -41,6 +42,7 @@
addTestSuite(CtsTestTest.class);
addTestSuite(PlanXmlParserTest.class);
addTestSuite(TestPackageXmlParserTest.class);
+ addTestSuite(TestPackageDefTest.class);
}
public static Test 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 2ce3ad6..577ecb4 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
@@ -113,7 +113,7 @@
// TODO: consider doing xml based compare
assertTrue(output.contains(
"<Summary failed=\"0\" notExecuted=\"0\" timeout=\"0\" pass=\"1\" />"));
- assertTrue(output.contains("<TestPackage name=\"run\" appPackageName=\"run\" digest=\"\">"));
+ assertTrue(output.contains("<TestPackage name=\"\" appPackageName=\"run\" digest=\"\">"));
assertTrue(output.contains("<TestCase name=\"FooTest\" priority=\"\">"));
final String testCaseTag = String.format(
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPackageDefTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPackageDefTest.java
new file mode 100644
index 0000000..2a5777b
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/TestPackageDefTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.testtype;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
+
+/**
+ * Unit tests for {@link TestPackageDef}.
+ */
+public class TestPackageDefTest extends TestCase {
+
+ /**
+ * Regression test for {@link TestPackageDef#generateDigest(File, String)} that ensures expected
+ * digest is generated for fixed data.
+ */
+ public void testGenerateDigest() {
+ TestPackageDef def = new TestPackageDef() {
+ @Override
+ InputStream getFileStream(File dir, String fileName) {
+ return new ByteArrayInputStream("test data for digest".getBytes());
+ }
+ };
+ String digest = def.generateDigest(new File("unused"), "alsounused");
+ assertNotNull(digest);
+ assertEquals("58c222b5f5f81b4b58891ec59924b9b2f530452e", digest);
+
+ }
+
+}