Add --test parameter to cts.

So a test can be run with:
run cts --test android.example.cts.ExampleTest#testMyMethod

Change-Id: Ic1c325aa8a4fb43eaab060b8afde39ae0bf45557
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 2505318..a91fdca 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
@@ -78,6 +78,7 @@
     private static final String PACKAGE_OPTION = "package";
     private static final String CLASS_OPTION = "class";
     private static final String METHOD_OPTION = "method";
+    private static final String TEST_OPTION = "test";
     public static final String CONTINUE_OPTION = "continue-session";
     public static final String RUN_KNOWN_FAILURES_OPTION = "run-known-failures";
 
@@ -107,6 +108,10 @@
             importance = Importance.IF_UNSET)
     private String mMethodName = null;
 
+    @Option(name = TEST_OPTION, shortName = 't', description = "run a specific test",
+            importance = Importance.IF_UNSET)
+    private String mTestName = null;
+
     @Option(name = CONTINUE_OPTION,
             description = "continue a previous test session.",
             importance = Importance.IF_UNSET)
@@ -365,6 +370,15 @@
     }
 
     /**
+     * Set the test name to run e.g. android.test.cts.SampleTest#testSample
+     * <p/>
+     * Exposed for unit testing
+     */
+    void setTestName(String testName) {
+        mTestName = testName;
+    }
+
+    /**
      * Sets the test session id to continue.
      * <p/>
      * Exposed for unit testing
@@ -663,6 +677,30 @@
                 Log.logAndDisplay(LogLevel.WARN, LOG_TAG, String.format(
                         "Could not find package for test class %s", mClassName));
             }
+        } else if (mTestName != null) {
+            Log.i(LOG_TAG, String.format("Executing CTS test %s", mTestName));
+            String [] split = mTestName.split("#");
+            if (split.length != 2) {
+                Log.logAndDisplay(LogLevel.WARN, LOG_TAG, String.format(
+                        "Could not parse class and method from test %s", mTestName));
+            } else {
+                String className = split[0];
+                String methodName = split[1];
+                // try to find packages to run from class name
+                List<String> packageIds = testRepo.findPackageIdsForTest(className);
+                if (!packageIds.isEmpty()) {
+                    for (String packageId: packageIds) {
+                        ITestPackageDef testPackageDef = testRepo.getTestPackage(packageId);
+                        if (testPackageDef != null) {
+                            testPackageDef.setClassName(className, methodName);
+                            testPkgDefs.add(testPackageDef);
+                        }
+                    }
+                } else {
+                    Log.logAndDisplay(LogLevel.WARN, LOG_TAG, String.format(
+                            "Could not find package for test class %s", mTestName));
+                }
+            }
         } else if (mContinueSessionId != null) {
             // create an in-memory derived plan that contains the notExecuted tests from previous
             // session use timestamp as plan name so it will hopefully be unique
@@ -869,10 +907,10 @@
     }
 
     private void checkFields() {
-        // for simplicity of command line usage, make --plan, --package, and --class mutually
+        // for simplicity of command line usage, make --plan, --package, --test and --class mutually
         // exclusive
         boolean mutualExclusiveArgs = xor(mPlanName != null, mPackageNames.size() > 0,
-                mClassName != null, mContinueSessionId != null);
+                mClassName != null, mContinueSessionId != null, mTestName != null);
 
         if (!mutualExclusiveArgs) {
             throw new IllegalArgumentException(String.format(
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 6c3c0c3..42c2ed1 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
@@ -189,6 +189,26 @@
     }
 
     /**
+     * Test normal case {@link CtsTest#run(ITestInvocationListener)} when running a class.
+     */
+    @SuppressWarnings("unchecked")
+    public void testRun_test() throws DeviceNotAvailableException {
+        final String className = "className";
+        final String methodName = "methodName";
+        final String testName = String.format("%s#%s", className, methodName);
+        mCtsTest.setTestName(testName);
+
+        EasyMock.expect(mMockRepo.findPackageIdsForTest(className)).andReturn(IDS);
+        mMockPackageDef.setClassName(className, methodName);
+
+        setCreateAndRunTestExpectations();
+
+        replayMocks();
+        mCtsTest.run(mMockListener);
+        verifyMocks();
+    }
+
+    /**
      * Test {@link CtsTest#run(ITestInvocationListener)} when --excluded-package is specified
      */
     public void testRun_excludedPackage() throws DeviceNotAvailableException, ParseException {