Merge "Add --class and --method options to cts tradefed." into honeycomb
diff --git a/tools/tradefed-host/res/config/cts.xml b/tools/tradefed-host/res/config/cts.xml
index d98c7fb..165ba96 100644
--- a/tools/tradefed-host/res/config/cts.xml
+++ b/tools/tradefed-host/res/config/cts.xml
@@ -19,7 +19,7 @@
<build_provider class="com.android.cts.tradefed.targetsetup.CtsBuildProvider" />
<device_recovery class="com.android.tradefed.device.WaitDeviceRecovery" />
<target_preparer class="com.android.cts.tradefed.targetsetup.CtsSetup" />
- <test class="com.android.cts.tradefed.testtype.PlanTest" />
+ <test class="com.android.cts.tradefed.testtype.CtsTest" />
<logger class="com.android.tradefed.log.FileLogger" />
<result_reporter class="com.android.cts.tradefed.result.CtsXmlResultReporter" />
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 c160aa5..702956a 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
@@ -18,6 +18,7 @@
import com.android.cts.tradefed.device.DeviceInfoCollector;
import com.android.ddmlib.Log;
+import com.android.ddmlib.Log.LogLevel;
import com.android.tradefed.config.Option;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
@@ -53,6 +54,8 @@
public static final String TEST_PLANS_DIR_OPTION = "test-plans-path";
private static final String PLAN_OPTION = "plan";
private static final String PACKAGE_OPTION = "package";
+ private static final String CLASS_OPTION = "class";
+ private static final String METHOD_OPTION = "method";
private ITestDevice mDevice;
@@ -65,6 +68,13 @@
@Option(name = "exclude-package", description = "the test packages(s) to exclude from the run")
private Collection<String> mExcludedPackageNames = new ArrayList<String>();
+ @Option(name = CLASS_OPTION, shortName = 'c', description = "run a specific test class")
+ private String mClassName = null;
+
+ @Option(name = METHOD_OPTION, shortName = 'm',
+ description = "run a specific test method, from given --class")
+ private String mMethodName = null;
+
@Option(name = TEST_CASES_DIR_OPTION, description =
"file path to directory containing CTS test cases")
private File mTestCaseDir = null;
@@ -146,6 +156,24 @@
}
/**
+ * Set the test class name to run.
+ * <p/>
+ * Exposed for unit testing
+ */
+ void setClassName(String className) {
+ mClassName = className;
+ }
+
+ /**
+ * Set the test method name to run.
+ * <p/>
+ * Exposed for unit testing
+ */
+ void setMethodName(String methodName) {
+ mMethodName = methodName;
+ }
+
+ /**
* {@inheritDoc}
*/
public void run(List<ITestInvocationListener> listeners) throws DeviceNotAvailableException {
@@ -154,8 +182,8 @@
Log.i(LOG_TAG, String.format("Executing CTS test plan %s", mPlanName));
try {
- Collection<String> testUris = getTestsToRun();
ITestCaseRepo testRepo = createTestCaseRepo();
+ Collection<String> testUris = getTestsToRun(testRepo);
collectDeviceInfo(getDevice(), mTestCaseDir, listeners);
for (String testUri : testUris) {
ITestPackageDef testPackage = testRepo.getTestPackage(testUri);
@@ -179,31 +207,49 @@
* @throws ParseException
* @throws FileNotFoundException
*/
- private Collection<String> getTestsToRun() throws ParseException, FileNotFoundException {
+ private Collection<String> getTestsToRun(ITestCaseRepo testRepo) throws ParseException,
+ FileNotFoundException {
Set<String> testUris = new HashSet<String>();
- if (mPlanName == null) {
- testUris.addAll(mPackageNames);
- } else {
+ if (mPlanName != null) {
String ctsPlanRelativePath = String.format("%s.xml", mPlanName);
File ctsPlanFile = new File(mTestPlanDir, ctsPlanRelativePath);
IPlanXmlParser parser = createXmlParser();
parser.parse(createXmlStream(ctsPlanFile));
testUris.addAll(parser.getTestUris());
+ } else if (mPackageNames.size() > 0){
+ testUris.addAll(mPackageNames);
+ } else if (mClassName != null) {
+ // try to find package to run from class name
+ String packageUri = testRepo.findPackageForTest(mClassName);
+ if (packageUri != null) {
+ testUris.add(packageUri);
+ } else {
+ Log.logAndDisplay(LogLevel.WARN, LOG_TAG, String.format(
+ "Could not find package for test class %s", mClassName));
+ }
+ } else {
+ // should never get here - was checkFields() not called?
+ throw new IllegalStateException("nothing to run?");
}
testUris.removeAll(mExcludedPackageNames);
return testUris;
}
private void checkFields() {
- if (mPlanName == null && mPackageNames.size() <= 0) {
+ // for simplicity of command line usage, make --plan, --package, and --class mutually
+ // exclusive
+ boolean mutualExclusiveArgs = xor(mPlanName != null, mPackageNames.size() > 0,
+ mClassName != null);
+
+ if (!mutualExclusiveArgs) {
throw new IllegalArgumentException(String.format(
- "Missing the --%s or --%s(s) to run", PLAN_OPTION, PACKAGE_OPTION));
+ "Ambiguous or missing arguments. " +
+ "One and only of --%s --%s(s) or --%s to run can be specified",
+ PLAN_OPTION, PACKAGE_OPTION, CLASS_OPTION));
}
- // for simplicity of command line usage, don't allow both --plan and --package
- if (mPlanName != null && mPackageNames.size() > 0) {
+ if (mMethodName != null && mClassName == null) {
throw new IllegalArgumentException(String.format(
- "Only one of a --%s or --%s(s) to run can be specified", PLAN_OPTION,
- PACKAGE_OPTION));
+ "Must specify --%s when --%s is used", CLASS_OPTION, METHOD_OPTION));
}
if (getDevice() == null) {
throw new IllegalArgumentException("missing device");
@@ -218,6 +264,24 @@
}
/**
+ * Helper method to perform exclusive or on list of boolean arguments
+ *
+ * @param args set of booleans on which to perform exclusive or
+ * @return <code>true</code> if one and only one of <var>args</code> is <code>true</code>.
+ * Otherwise return <code>false</code>.
+ */
+ private boolean xor(boolean... args) {
+ boolean currentVal = args[0];
+ for (int i=1; i < args.length; i++) {
+ if (currentVal && args[i]) {
+ return false;
+ }
+ currentVal |= args[i];
+ }
+ return currentVal;
+ }
+
+ /**
* Runs the test.
*
* @param listeners
@@ -226,7 +290,7 @@
*/
private void runTest(List<ITestInvocationListener> listeners, ITestPackageDef testPackage)
throws DeviceNotAvailableException {
- IRemoteTest test = testPackage.createTest(mTestCaseDir);
+ IRemoteTest test = testPackage.createTest(mTestCaseDir, mClassName, mMethodName);
if (test != null) {
if (test instanceof IDeviceTest) {
((IDeviceTest)test).setDevice(getDevice());
@@ -278,5 +342,4 @@
InputStream createXmlStream(File xmlFile) throws FileNotFoundException {
return new BufferedInputStream(new FileInputStream(xmlFile));
}
-
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java
index 96546d0..ffcde46 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/ITestCaseRepo.java
@@ -30,4 +30,12 @@
*/
public ITestPackageDef getTestPackage(String testUri);
+ /**
+ * Attempt to find the package uri for a given test class name
+ *
+ * @param testClassName the test class name
+ * @return the package uri or <code>null</code> if the package cannot be found
+ */
+ public String findPackageForTest(String testClassName);
+
}
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 f6febdb..057e803 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
@@ -38,10 +38,14 @@
* Creates a runnable {@link IRemoteTest} from info stored in this definition.
*
* @param testCaseDir {@link File} representing directory of test case data
+ * @param className the test class to restrict this run to or <code>null</code> to run all tests
+ * in package
+ * @param methodName the optional test method to restrict this run to, or <code>null</code> to
+ * run all tests in class/package
* @return a {@link IRemoteTest} with all necessary data populated to run the test or
* <code>null</code> if test could not be created
*/
- public IRemoteTest createTest(File testCaseDir);
+ public IRemoteTest createTest(File testCaseDir, String className, String methodName);
/**
* Determine if given test is defined in this package.
@@ -51,4 +55,12 @@
*/
public boolean isKnownTest(TestIdentifier testDef);
+ /**
+ * Determine if given test class is defined in this package.
+ *
+ * @param testClassName the fully qualified test class name
+ * @return <code>true</code> if test class is defined
+ */
+ public boolean isKnownTestClass(String testClassName);
+
}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java
index fc20314..404da4d 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/TestCaseRepo.java
@@ -116,4 +116,17 @@
public ITestPackageDef getTestPackage(String testUri) {
return mTestMap.get(testUri);
}
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public String findPackageForTest(String testClassName) {
+ for (Map.Entry<String, TestPackageDef> entry : mTestMap.entrySet()) {
+ if (entry.getValue().isKnownTestClass(testClassName)) {
+ return entry.getKey();
+ }
+ }
+ return null;
+ }
}
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 cf67365..79723c1 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
@@ -23,6 +23,7 @@
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.LinkedHashSet;
/**
* Container for CTS test info.
@@ -42,10 +43,10 @@
private boolean mIsSignatureTest = false;
private boolean mIsReferenceAppTest = false;
- private Collection<TestIdentifier> mTests = new ArrayList<TestIdentifier>();
-
- /** the cached {@link IRemoteTest} */
- private IRemoteTest mRemoteTest;
+ // use a LinkedHashSet for predictable iteration insertion-order, and fast lookups
+ private Collection<TestIdentifier> mTests = new LinkedHashSet<TestIdentifier>();
+ // also maintain an index of known test classes
+ private Collection<String> mTestClasses = new LinkedHashSet<String>();
void setUri(String uri) {
mUri = uri;
@@ -118,25 +119,14 @@
/**
* {@inheritDoc}
*/
- public IRemoteTest createTest(File testCaseDir) {
- if (mRemoteTest == null) {
- mRemoteTest = doCreateTest(testCaseDir);
- }
- return mRemoteTest;
- }
-
- /**
- * @param testCaseDir
- * @return
- */
- private IRemoteTest doCreateTest(File testCaseDir) {
+ public IRemoteTest createTest(File testCaseDir, String className, String methodName) {
if (mIsHostSideTest) {
Log.d(LOG_TAG, String.format("Creating host test for %s", mName));
JarHostTest hostTest = new JarHostTest();
hostTest.setRunName(mName);
hostTest.setJarFile(new File(testCaseDir, mJarPath));
hostTest.setTestAppPath(testCaseDir.getAbsolutePath());
- hostTest.setTests(mTests);
+ hostTest.setTests(filterTests(mTests, className, methodName));
return hostTest;
} else if (mIsSignatureTest) {
// TODO: implement this
@@ -153,6 +143,8 @@
InstrumentationTest instrTest = new InstrumentationTest();
instrTest.setPackageName(mAppNameSpace);
instrTest.setRunnerName(mRunner);
+ instrTest.setClassName(className);
+ instrTest.setMethodName(methodName);
// mName means 'apk file name' for instrumentation tests
File apkFile = new File(testCaseDir, String.format("%s.apk", mName));
if (!apkFile.exists()) {
@@ -166,6 +158,27 @@
}
/**
+ * Filter the tests to run based on class and method name
+ *
+ * @param tests the full set of tests in package
+ * @param className the test class name filter. <code>null</code> to run all test classes
+ * @param methodName the test method name. <code>null</code> to run all test methods
+ * @return the filtered collection of tests
+ */
+ private Collection<TestIdentifier> filterTests(Collection<TestIdentifier> tests,
+ String className, String methodName) {
+ Collection<TestIdentifier> filteredTests = new ArrayList<TestIdentifier>(tests.size());
+ for (TestIdentifier test : tests) {
+ if (className == null || test.getClassName().equals(className)) {
+ if (methodName == null || test.getTestName().equals(methodName)) {
+ filteredTests.add(test);
+ }
+ }
+ }
+ return filteredTests;
+ }
+
+ /**
* {@inheritDoc}
*/
public boolean isKnownTest(TestIdentifier testDef) {
@@ -173,12 +186,20 @@
}
/**
+ * {@inheritDoc}
+ */
+ public boolean isKnownTestClass(String className) {
+ return mTestClasses.contains(className);
+ }
+
+ /**
* Add a {@link TestDef} to the list of tests in this package.
*
* @param testdef
*/
void addTest(TestIdentifier testDef) {
mTests.add(testDef);
+ mTestClasses.add(testDef.getClassName());
}
/**
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java
index a8c2b8d..e884365 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java
@@ -87,13 +87,14 @@
* Test normal case {@link CtsTest#run(java.util.List)} when running a plan.
*/
@SuppressWarnings("unchecked")
- public void testRun__plan() throws DeviceNotAvailableException, ParseException {
+ public void testRun_plan() throws DeviceNotAvailableException, ParseException {
setParsePlanExceptations();
ITestPackageDef mockPackageDef = EasyMock.createMock(ITestPackageDef.class);
IRemoteTest mockTest = EasyMock.createMock(IRemoteTest.class);
EasyMock.expect(mMockRepo.getTestPackage(PACKAGE_NAME)).andReturn(mockPackageDef);
- EasyMock.expect(mockPackageDef.createTest((File)EasyMock.anyObject())).andReturn(mockTest);
+ EasyMock.expect(mockPackageDef.createTest((File)EasyMock.anyObject(),
+ (String)EasyMock.anyObject(), (String)EasyMock.anyObject())).andReturn(mockTest);
mockTest.run((ITestInvocationListener)EasyMock.anyObject());
replayMocks(mockTest, mockPackageDef);
@@ -107,12 +108,37 @@
* Test normal case {@link CtsTest#run(java.util.List)} when running a package.
*/
@SuppressWarnings("unchecked")
- public void testRun__package() throws DeviceNotAvailableException {
+ public void testRun_package() throws DeviceNotAvailableException {
mCtsTest.addPackageName(PACKAGE_NAME);
ITestPackageDef mockPackageDef = EasyMock.createMock(ITestPackageDef.class);
IRemoteTest mockTest = EasyMock.createMock(IRemoteTest.class);
EasyMock.expect(mMockRepo.getTestPackage(PACKAGE_NAME)).andReturn(mockPackageDef);
- EasyMock.expect(mockPackageDef.createTest((File)EasyMock.anyObject())).andReturn(mockTest);
+ EasyMock.expect(mockPackageDef.createTest((File)EasyMock.anyObject(),
+ (String)EasyMock.anyObject(), (String)EasyMock.anyObject())).andReturn(mockTest);
+ mockTest.run((ITestInvocationListener)EasyMock.anyObject());
+
+ replayMocks(mockTest, mockPackageDef);
+ mCtsTest.run(mMockListener);
+ verifyMocks(mockTest, mockPackageDef);
+ }
+
+ /**
+ * Test normal case {@link CtsTest#run(java.util.List)} when running a class.
+ */
+ @SuppressWarnings("unchecked")
+ public void testRun_class() throws DeviceNotAvailableException {
+ final String className = "className";
+ final String methodName = "methodName";
+ mCtsTest.setClassName(className);
+ mCtsTest.setMethodName(methodName);
+
+
+ EasyMock.expect(mMockRepo.findPackageForTest(className)).andReturn(PACKAGE_NAME);
+ ITestPackageDef mockPackageDef = EasyMock.createMock(ITestPackageDef.class);
+ EasyMock.expect(mMockRepo.getTestPackage(PACKAGE_NAME)).andReturn(mockPackageDef);
+ IRemoteTest mockTest = EasyMock.createMock(IRemoteTest.class);
+ EasyMock.expect(mockPackageDef.createTest((File)EasyMock.anyObject(),
+ EasyMock.eq(className), EasyMock.eq(methodName))).andReturn(mockTest);
mockTest.run((ITestInvocationListener)EasyMock.anyObject());
replayMocks(mockTest, mockPackageDef);
@@ -173,6 +199,52 @@
}
}
+ /**
+ * Test {@link CtsTest#run(java.util.List)} when --plan and --class options have been
+ * specified
+ */
+ public void testRun_planClass() throws DeviceNotAvailableException {
+ mCtsTest.setPlanName(PLAN_NAME);
+ mCtsTest.setClassName("class");
+ try {
+ mCtsTest.run(mMockListener);
+ fail("IllegalArgumentException not thrown");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test {@link CtsTest#run(java.util.List)} when --package and --class options have been
+ * specified
+ */
+ public void testRun_packageClass() throws DeviceNotAvailableException {
+ mCtsTest.addPackageName(PACKAGE_NAME);
+ mCtsTest.setClassName("class");
+ try {
+ mCtsTest.run(mMockListener);
+ fail("IllegalArgumentException not thrown");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
+ /**
+ * Test {@link CtsTest#run(java.util.List)} when --plan, --package and --class options have been
+ * specified
+ */
+ public void testRun_planPackageClass() throws DeviceNotAvailableException {
+ mCtsTest.setPlanName(PLAN_NAME);
+ mCtsTest.addPackageName(PACKAGE_NAME);
+ mCtsTest.setClassName("class");
+ try {
+ mCtsTest.run(mMockListener);
+ fail("IllegalArgumentException not thrown");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ }
+
private void replayMocks(Object... mocks) {
EasyMock.replay(mMockRepo, mMockPlanParser, mMockDevice, mMockListener);
EasyMock.replay(mocks);